diff --git a/.claude/probe-axes.md b/.claude/probe-axes.md index 6b3f5908..b19a06b0 100644 --- a/.claude/probe-axes.md +++ b/.claude/probe-axes.md @@ -12,9 +12,16 @@ scratch copy, one file per claim, class name ending in `Probe`, rows fed through the covering array (`prober rows`), read from a JSON file beside the probe. That project is the closest thing to a consumer: it references every package, weaves value objects through the real fabric, and loads the analyzer as an analyzer. A probe file may declare its own value objects -(`readonly partial record struct ProbeId : IValue;`); generate the declarations of the -type-shape catalogue with a script into a `Probes/Shapes.g.cs`, not by hand. +(`readonly partial record struct ProbeId : IValue;`); the declarations of the type-shape +catalogue, `tools/probe/corpus/harness/*.g.cs`, are written by the file-based apps of +`tools/probe/generators/` (`dotnet run tools/probe/generators/.cs -- ...`, its README), never by hand, +and `GeneratedHarnessTests` in the conventions tests holds each file to its app's output. +- Start a probe with `prober new --claim ` and iterate with `prober try --probe

[--rows i,j,...]` + (tools/Prober/README.md, "Writing a probe"): `new` stages the skeleton in this harness's form, with the + claim's obligations quoted and a throwing check each, the probe's own rows and the registry entry; `try` + builds the corpus and the staging in the replay's worktree and runs that one probe, printing each row's + outcome and recording nothing. Never copy the corpus into a worktree or write a build or sync script. - Build and run only through the verdict tool, which pins English output, builds before it tests, names the exit codes and prints failing tests by name with their assertion: `dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll build test/CodoMetis.TypeKit.Samples` @@ -36,6 +43,43 @@ type-shape catalogue with a script into a `Probes/Shapes.g.cs`, not by hand. every other host in the project. A host with controllers must put its own converters and resolvers on MVC's `JsonOptions` too, or the document is refused on purpose (`DescribedOptionsAgreement`). `test/CodoMetis.TypeKit.AspNetCore.Tests/ProbeHost.cs` is a working example to copy from. +- How a row ends: a check returns null or the failure text, and runs through `Rec.Run`, which fails the + test for a violation or a harness error and skips it, with its reasons, for any other outcome but a + pass; a check that throws ends as a harness error, never a violation, and no seed counts it caught. A check + says how it compared with `r.Note(NoteKeys.Name, detail)`. The key, a member of `NoteKeys` in `harness/Row.cs`, + alone decides the note's outcome: every key is a pass key but `framework-refuses`, the three-sided + differential's expected refusal, and a check that did not apply, refused or could not tell says so through + its scope with a citation (below), never a note key. Keys are constants the compiler checks: a new one goes + into `NoteKeys` with its outcome on one line, and a string, a misspelled member or a key built from text does + not compile (decision 103). A probe outside the namespace `Probes.Shapes` writes `using NoteKeys = Probes.Shapes.NoteKeys;`. + Never match a failure's text to excuse it. A difference the framework makes on its own is shown by a + baseline in the same row (the document as ASP.NET writes it without `AddTypeKit()`, + `Hx.DocRefusedByAspNetAlone`, a refusal compared by exception type and stage as `Sides.Alike` compares one), + not by a substring. A document both refuse alike is the framework's refusal for a check that compared nothing + else, and leaves a check that compared an answer on the wire first to be judged by that answer. A row that would fail for an open finding calls + `r.Skip("F7", why)`, the finding as a string literal: `register` and `publish` refuse a skip whose finding + is built from text or is not open in the ledger, naming the file and line. The gate refuses while any row + skips, and the replay fails a skip whose finding the ledger has fixed or dismissed. +- Checks: a comparison is `r.Check("obligation", Baseline.Twin, c => ...)` (or the `async` form), one per + obligation of the claim (`claim` for a claim without them), against one baseline of `Baseline` (`Twin`, + `Framework`, `RoundTrip`, `Model`, `Refusal`, `Cost`, `Fact("KB-031")`, `Docs("file#anchor")`). The check reads + through `c["axis"]` (or the row, inside the body: both read for the check), notes how it compared with + `c.Note(NoteKeys.Compared, ...)`, and says why it did not compare with `c.Inapplicable(Cite...)`, + `c.Refused(Cite...)` or `c.Inconclusive(reason)`, each returning null. A citation is `Cite.NotPromised("id")` (an + entry of the claim's `not-promised`), `Cite.Docs("file#anchor")`, `Cite.Fact("KB-031")` or `Cite.Record("0094")`, + written with a string literal; `register`, `publish` and the conventions tests refuse anything else and a + citation that names nothing. A failure returned is the check's violation, an exception its harness error (the + row's other checks still run), and a pass with no baseline a harness error. A row passes when no check failed + and one passed, so a check that did not apply voids no other; the ledger credits each passing check with the + axes it read and the row's, never a pair put together from two checks. A seed naming an obligation is caught + only by a check naming it. +- Two-sided checks: the twin is `Entry.Plain`, the wrapped type at the value object's nullability (a class value object over a + struct faces the struct made nullable; the closed set faces a string), written by `shapes.cs` beside the value object. Build both + sides with `Sides.Twins(position, containing, entry, plain, vo)`, which puts the row's position and containing type around each and + throws, a harness error, where the two do not hold the same shape. Under `json.TypeInfoResolver=source-generated-context` the options + resolve through `ProbeContext` alone, which lists every shape, position and containing type and each twin, with no reflection behind + it; `json.TypeInfoResolverChain=context-then-default` puts the reflection fallback after it. A shape the context does not list is a + harness error, never a fallback (tools/probe/generators/README.md, "The twin and the context"). - EF probes: SQLite in memory (`Data Source=:memory:` with the connection held open). PostgreSQL needs a server and is out of reach. - Analyzer probes (what compiles and what CMTK reports): in `test/CodoMetis.TypeKit.Analyzers.Tests`, @@ -44,6 +88,45 @@ type-shape catalogue with a script into a `Probes/Shapes.g.cs`, not by hand. - Native AOT is measured only by `./test/consumer-smoke-test.sh` (minutes, needs clang). Probe it only when the caller asks for it. +## Seeds: every probe shows it can fail + +A probe that passes proves something only if it can fail. Every registered probe names one seeded +product defect ([decision 100](../design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)): +a patch against `src/` that breaks what its claim promises, in `tools/probe/corpus/seeds/.patch`, +and a sentence saying what it breaks, in its registry entry: + +```json +"seed": { "patch": "seeds/L01-json-bytes.patch", "defect": "A dictionary key is written with ToString(), not the wrapped type's key converter and DictionaryKeyPolicy." } +``` + +- Write the seed with the probe. Make the defect in `src/` of the scratch copy, take `git diff -- ` + as the patch, undo it by inverse edit (`git apply -R `), and register with `prober register ... + --seed seeds/.patch --defect ""`. An environment toggle (`"env": {...}`, set for the + build and every run) is a seed too, where the product reads one; none does today. +- Seed the defect the claim is about, the one a consumer would meet: a facet dropped, a refusal that + quotes its input, a rule skipped, a key no longer generated. A seed whose build fails proves nothing, + and the command counts it as a failure. +- `prober seeds [--probe P]` applies each seed in a scratch worktree of its own (`replay --where` names + it), builds, replays the probe there and requires a row that ends as a violation the unseeded probe + does not have; a missed seed fails it. It records no evidence and takes no lock. The worktree is per + checkout, so runs in two checkouts (a linked worktree each) never meet, but two runs at once in one + checkout share it. One build per seed, so it runs locally, never in CI: after writing a probe or + changing its harness, and before a release. +- Without a build, the prober tests hold the rest on every run (`SeedsTests`): every registered probe + has a seed, and its patch touches only `src/` and still applies. A product change that moves the + patched lines fails there; make the seed again against the new code. +- A seed per obligation ([decision 104](../design/adr/0104-a-claim-states-its-obligations-and-each-has-a-seed.md)). + A seed that breaks some assertion of a probe shows the probe can fail, not that the claim is checked, so + a probe whose claim lists `obligations` carries `seeds`, one per obligation it checks, each a seed as + above with an `obligation` naming it; `seed` may name one too. A seed that names none counts only for a + claim without `obligations`. Register each with `prober register --probe P --claim C --filter "*P" + --obligation --seed seeds/-.patch --defect ""`, the command `prober new` prints + for each obligation: it replaces a seed of the entry that names the same obligation and refuses an id the + claim does not list. Never edit the staged registry by hand. `prober seeds` runs each seed of a probe on its own and reports it as + `P/`, in its output and in `seeds-last.json`; `prober status` shows per claim its obligations + and the ones no seed names; and `SeedsTests` refuses, without a build, a seed naming an obligation its + claim does not list and a listed obligation of a claim with probes that no seed of them names. + ## Claim sources In this order: the root `README.md` ("What TypeKit guarantees" first), `docs/*.md`, the package @@ -65,11 +148,50 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo reason. The script reports any other as **unassigned**: a setting a new framework version added, which needs a decision here. - `shape.*` is the type-shape catalogue: the forms a consumer's own type takes. Generate the - declarations a round needs with a script, once per round, as one pairwise set shared by every claim. + declarations a round needs with an app of `tools/probe/generators/` (extend one, or add one and its row + in `GeneratedHarnessTests`), once per round, as one pairwise set shared by every claim. - A claim may drop a surface axis only by quoting the document sentence that limits it (`narrow`). -- `refused` drops a value the framework itself refuses on its own, so no row wastes its pairs on it. +- A claim may drop pairs no row can hold, or that the documents do not promise, only with the reason + (`exclude`: a list of `{"reason": "...", "pairs": [["a=1", "b=2"], ...]}`): one sentence, citing the docs + sentence, the record or the KB fact where one applies. Nothing checks a reason mechanically, so + `prober universe` prints each one beside the claim's narrowings for a reviewer, and the prober refuses a + claims.json with any field its schema (`tools/Prober/Schemas/claims.schema.json`) does not have + ([decision 102](../design/adr/0102-an-exclusion-carries-its-reason.md)). +- An obligation may say where it does not apply, by the claim's own axis values and a citation + (`inapplicable`: a list of `{"axes": {"shape.kind": ["closed-set"]}, "cite": "not-promised:closed-set-has-no-scale"}`, + the cite `not-promised:id`, `docs:file#anchor`, `fact:KB-NNN` or `record:NNNN`). `Row.Check` reads the same + declarations and records such a check inapplicable before its body runs; a pair no obligation wants is never + put in a generated row, and the gate leaves it out as it leaves out an exclusion. A condition only the shape + instance decides at run time stays in the check's body as `c.Inapplicable(cite)`. +- `refused` drops a value the framework itself refuses on its own, so no row wastes its pairs on it. A reason that + rests on a verified fact names it (`fact:KB-NNN`), and that fact has a test: `docs-query check` fails + `fact-not-executable-cited` for a refused entry, a not-promised entry, an applicability condition or a harness + citation of a fact without one ([decision 107](../design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)). `shape.refused-kind` holds the declarations the generators refuse; only the generation surface takes - it, where the refusal is the claim. + it, where the refusal is the claim. Each value is a declaration and the id the Generators README's + "Build errors" table gives it, declared as C22's probe does (`harness/BuildRefusals.cs`): an unsealed + record class; a value object over another value object; over `List`; an abstract record or class with + a marker (CMTK1022 since `1ef30be`: the label keeps the id it had when C09's and C11's rows were + generated, which carry it); over `object`; over `System.Enum`; a value object named as a member the + generators add (`From`, `Parse`, `MinValue`, ...); a closed set named as one of its generated members + (`Name`, `All`, `Match`, ...); a value object with a hand-written `operator ==` and `!=` between two of + the type. The last five are the refusals of the fixes `53188ed` and `e4b751c`, which no probe reached + until C22 (design/finder-benchmark-2026-10-16.md). +- A claim lists what it promises in `obligations`, one entry per promise a probe can check on its own: an + `id`, the `sentence` quoted verbatim from the claim's source and checked as a narrowing is, and the `axes` + values it concerns where it is not the whole claim. A claim without the field has one obligation, the + claim itself, under the id `claim`. Each obligation of a claim with probes has a seed that names it + ([decision 104](../design/adr/0104-a-claim-states-its-obligations-and-each-has-a-seed.md)). +- What a claim does not promise goes in `not-promised`: an `id`, the `source` (`file#heading`) of the + document or record that makes the difference intended, its `sentence`, quoted verbatim and checked against + that source, and the `axes` values it concerns. A note of outcome expected-refusal, a narrowing or an + applicability condition cites the entry by its id. `prober universe` refuses an obligation or an entry + whose sentence its document does not hold, and an id listed twice, naming the claim and the id. +- `out-of-reach` names a surface no probe reaches, with the reason: one that needs a server, a toolchain + or minutes a replay does not have. `prober gaps` prints it beside the settings no surface reaches, the + settings without values, the refused shapes and values, and every not-promised entry and narrowing, each + with its recorded reason. It is a report for the release checklist and a feature-research round; it + decides nothing and runs no model. - `http.host` has two values without `[ApiController]`, where MVC infers no `[FromBody]` and answers no automatic 400. `controller-without-ApiController` is for **runtime claims only**: ASP.NET's API explorer leaves such a controller out of the document (docs/aspnet-core.md, "Bind value objects in MVC"), so a @@ -350,7 +472,12 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo "unsealed-record-class (CMTK1006)", "over-value-object (CMTK1005)", "generic-wrapped (CMTK1005)", - "abstract (CMTK1005)" + "abstract (CMTK1005)", + "over-object (CMTK1005)", + "over-System.Enum (CMTK1005)", + "named-as-generated-member (CMTK1005)", + "closed-set-named-as-generated-member (CMTK1021)", + "equality-operator (CMTK1011)" ], "process.converter-lookup": [ "none", @@ -468,7 +595,14 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo ] } }, + "out-of-reach": { + "postgresql": "needs a server; the EF probes run SQLite in memory, and the EF translation claims are checked by translation alone", + "native-aot": "measured only by the consumer smoke test (tools/ConsumerSmokeTest), minutes and a platform linker; no probe publishes with Native AOT" + }, "refused": { + "shape.site": { + "in-generic-type": "CMTK1005: the generators refuse a value object nested in a generic type at build (Generators README, Build errors)" + }, "json.ReadCommentHandling": { "Allow": "System.Text.Json refuses it on the options object" }, @@ -500,19 +634,42 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo "dotnet", "tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll", "test", + "--no-build", "--project", "{project}", "--filter-class", "{filter}", "--", + "--results-directory", + "{results}" + ], + "coverage": [ "--coverage", "--coverage-output-format", "cobertura", "--coverage-output", - "{cobertura}", - "--results-directory", - "{results}" + "{cobertura}" ], + "families": { + "http": [ + "http" + ], + "ef": [ + "ef" + ], + "json": [ + "json" + ], + "text": [ + "text", + "logging" + ], + "build": [ + "generation", + "analyzers", + "option-result" + ] + }, "include": [ "src" ], @@ -578,8 +735,8 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo - A struct value object refuses a JSON `null`. - A rejected dictionary key appears in `JsonException.Path`, and so in MVC's 400. - Under a source-generated context, a value object over an enum needs the enum listed in the context. -- A validated value object has no `From`; an abstract value object and a generic wrapped type fail - with CMTK1005. +- A validated value object has no `From`; an abstract value object fails with CMTK1022, and a generic + wrapped type with CMTK1005. - A missing `Option` property, and an empty collection or dictionary of `Option` or `Result`, pass through System.Text.Json without an exception, since no converter runs (a wording question for the README, raised 2026-10-15, not a finding). diff --git a/.claude/settings.json b/.claude/settings.json index fa3bf981..5429aeeb 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -6,13 +6,13 @@ "hooks": [ { "type": "command", - "command": "dotnet build \"$CLAUDE_PROJECT_DIR/tools/BashGuard/BashGuard.csproj\" -c Release -nologo -v q -tl:off -clp:NoSummary", + "command": "dotnet build \"$CLAUDE_PROJECT_DIR/tools/BashGuard/BashGuard.csproj\" -c Release -nologo -v q -tl:off -clp:NoSummary >/dev/null 2>&1 || PATH=\"/usr/local/share/dotnet:$PATH\" dotnet build \"$CLAUDE_PROJECT_DIR/tools/BashGuard/BashGuard.csproj\" -c Release -nologo -v q -tl:off -clp:NoSummary || echo \"Session start: the Bash guard did not build with the dotnet on PATH, nor with the installer's in /usr/local/share/dotnet; a dotnet without the SDK global.json pins is the usual cause. Run dotnet build \\\"$CLAUDE_PROJECT_DIR/tools/BashGuard\\\" -c Release to see why: until it builds, the guard checks nothing, or refuses as stale.\"", "timeout": 180 }, { "type": "command", - "command": "dotnet build \"$CLAUDE_PROJECT_DIR/tools/BuildVerdict/BuildVerdict.csproj\" -c Release -nologo -v q -tl:off -clp:NoSummary", - "timeout": 180 + "command": "dotnet build tools/AgentTools.slnf -c Release -nologo -v q -tl:off -clp:NoSummary >/dev/null 2>&1 || PATH=\"/usr/local/share/dotnet:$PATH\" dotnet build tools/AgentTools.slnf -c Release -nologo -v q -tl:off -clp:NoSummary || echo \"Session start: the tools in $PWD (build-verdict, prober, docs-query, consumer-smoke-test) did not build with the dotnet on PATH, nor with the installer's in /usr/local/share/dotnet; a dotnet without the SDK global.json pins is the usual cause. Run dotnet build tools/AgentTools.slnf -c Release to see why, and before using them.\"", + "timeout": 300 } ] } diff --git a/.claude/skills/probe-triage/SKILL.md b/.claude/skills/probe-triage/SKILL.md new file mode 100644 index 00000000..1373245c --- /dev/null +++ b/.claude/skills/probe-triage/SKILL.md @@ -0,0 +1,261 @@ +--- +name: probe-triage +description: Decide what one failing probe row means, by four questions in a fixed order — a harness error, a pinned framework fact, an intended difference the claim does not promise, or a finding for the code-analyst — stopping at the first that applies and recording the step where the corpus keeps it. Use for every failing row at the end of a probing round, for a `prober status` finding (`R--`, `F`), for a row a replay or `prober try` printed as failed, and whenever someone asks whether a probe failure is real. +--- + +# Probe triage + +A failing row is a question, not a finding. Most of what the prober has cost went into answering it by +judgement, mid-round: the gate round of 2026-10-16 wrote 18 probes, found two product defects and about +ten harness traps, and left five failing rows of C07 untriaged. This skill answers the question by +lookup, in a fixed order, so two people (or a person and an agent) reach the same answer for the same +row, and the answer is written where the next replay and the next reader find it. + +**The order is the rule.** Ask the four questions in turn and stop at the first that applies. Do not skip +ahead because a row "looks like" a product bug: a harness that compares unlike things produces the most +convincing product bugs there are. Every outcome names its step, `triage 1` to `triage 4`. + +The writer of a probe never decides what a refusal means. The probe checks; this skill decides. + +## Before the steps: get the row, and make it fail again + +You start from one of these: + +- **A finding id** from `prober status`: `R--` (the replay saw a probe that passed with the + same identity fail) or `F`. Its last line in the branch's `findings.jsonl` names the probe and the + commit. The state directory, `$STATE` below, is `PROBE_STATE_DIR` when set, else the main checkout's + `.claude/agent-memory-local/prober/`; the branch's directory is `branches//`. +- **A row a run printed:** `prober try` and `prober replay` print the failed probe and how each row ended. + A replay that says "fails after a change to its corpus, axes, environment or this tool; triage the probe, + not the product" opened no finding: start at step 1 and expect to stop there. +- **A row's evidence line:** the line `Rec.Run` writes, or the probe's evidence after a replay: + + ```bash + jq '.rows["358"] | {category, reasons, applied}' "$STATE/branches/release-2.2.0/evidence/BaseProbe.json" + ``` + +From it, write down: the claim, the probe, the row's index, its category (`violation`, `harness-error`, +`inconclusive`), its reasons, and the settings that applied. Then run the row at HEAD, three times: + +```bash +dotnet tools/Prober/bin/Release/net10.0/prober.dll try --probe BaseProbe --rows 358 +dotnet tools/Prober/bin/Release/net10.0/prober.dll try --probe BaseProbe --rows 358 --no-build # twice more +``` + +A row that passes at HEAD was fixed meanwhile: say so with the commit, and stop. A row that does not +fail every time is flaky, and flaky is not a finding: record it as `flaky` (a round's ledger line, or +your report) and stop. Only a +row that fails every time goes on to step 1. + +## 1. Is it a harness error? + +**It applies when** the row's reasons carry `harness-error`, or the failure comes from the harness rather +than from the product: + +- the category is `harness-error`: a check threw (`UNEXPECTED : ...`, which decision 103 makes + a harness error and never a violation), a check `prober new` staged and nobody wrote, or the equivalence + precondition (`inputs not equivalent: wrapped [...] vs value object [...]`), which says the two sides + were not the same shape and so were never comparable; +- a 404 on a bind route, a context type the source-generated `ProbeContext` does not list, a host that did + not start for a reason the row's settings do not explain; +- a `violation` whose whole difference lies in something the harness built or named rather than something + TypeKit wrote: a component or route name made from the twin's own type name, a twin at another + nullability, kind or position than the value object (concept H2), an input the harness placed + differently on each side. Read the two sides' answers in the reasons, or in the full log of the `try` + run, and find the first byte that differs. If it is the harness's, this step applies. + +**Then** fix the harness so the row compares like with like, and run the row again. No finding is filed. +A row that now fails with a different difference starts again at step 1. + +**Record:** + +- the harness fix, as a `test:` commit whose body names the row and says `triage 1`: what the harness + did and what it does now (94554b3 is the model). In a round, the fix goes to the staging's `harness/` + and the round's report says it; the session that publishes commits it; +- with a finding id, a line appended to the branch's `findings.jsonl`: + + ```json + {"schemaVersion":1,"id":"R-BaseProbe-1a2b3c4","status":"withdrawn","reason":"triage 1 harness-error: the /schema/ endpoint had no twin at the value object's nullability; fixed in 94554b3"} + ``` + +## 2. Does a fact at the pinned version explain it? + +**It applies when** a fact in `design/knowledge/` says the framework, without TypeKit, does what the row +saw: the same refusal at the same operation, or the same document, at the version the repository pins. +A fact about the framework explains a row only where the framework alone makes the difference. A fact +that describes how the framework treats the wrapped type, while the value object differs from it, does +not explain the row; it is the evidence of step 4. + +**Check with** the framework, the setting and the operation the row names: + +```bash +dotnet tools/DocsQuery/bin/Release/net10.0/docs-query.dll find controller API explorer document visible --kind fact +dotnet tools/DocsQuery/bin/Release/net10.0/docs-query.dll entry KB-002 +dotnet tools/DocsQuery/bin/Release/net10.0/docs-query.dll check # no fact-behind or fact-ahead for it +``` + +A miss of `find` proves nothing: try the setting's own name and the exception type too. Read the fact's +frontmatter. It is **executable** when its `test:` line names a test under `test/CodoMetis.TypeKit.Facts` +(`test: test/CodoMetis.TypeKit.Facts/KB002.cs`). Run that test: + +```bash +dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test --project test/CodoMetis.TypeKit.Facts --filter-class "*KB002" +``` + +**Then:** + +- **An executable fact that passes and explains the row** makes it a framework refusal. The check is + changed to cite the fact, no finding is filed. +- **A fact whose pin is stale**, or whose test fails, settles nothing until it is verified again + (decision 93). Verify it first. +- **A fact without a test** (`test: none, ...` or `prose`) that would explain the row is a research task: + measure the behaviour in `test/CodoMetis.TypeKit.Facts` as `KB.cs`, give the fact its `test:` + line, then cite it. A fact without a test may be cited in a reason and nothing more: it never prunes a + row and never excuses one. +- **No fact, but the row's own baseline shows the framework alone doing it** (the document + `Hx.DocRefusedByAspNetAlone` refuses for ASP.NET alone too, or the wrapped type alone refuses under the + same options): that is a new framework behaviour. It becomes a new fact with its test under + `design/knowledge/` (its README, "Adding one"), never a new note key. Then cite it. +- Otherwise go to step 3. + +**Record:** + +- the citation, where the corpus holds one: a `not-promised` entry of the claim whose `source` is the + fact's file and whose `sentence` is quoted from it (step 3's form), an `exclude` group whose `reason` + names `KB-NNN`, or a value in the axes file's `refused` object whose text names `KB-NNN`, or the check's + own `Cite.Fact("KB-NNN")`; +- with a finding id: + + ```bash + dotnet tools/Prober/bin/Release/net10.0/prober.dll dismiss --id R-C03ComposedProbe-1a2b3c4 --reason "triage 2 fact KB-002: a controller without [ApiController] is missing from the document for ASP.NET alone, so the row's document part has nothing to check" + ``` + + `dismiss` stages the line; `prober publish` puts it in `tools/probe/corpus/dismissed.jsonl`; +- in a round, a fact to measure goes in the report under "Facts to measure", with the row and the + configuration. The agent writes no test; the session that takes the report does. + +## 3. Does the claim say it does not promise this? + +**It applies when** a `not-promised` entry of the row's claim covers the row's settings, or a sentence of +the docs or of a decision record says the difference is intended. + +**Check with** the claim's entries and narrowings, and the docs: + +```bash +dotnet tools/Prober/bin/Release/net10.0/prober.dll gaps | grep 'C07-fault-responses-blanket' +jq '.[] | select(.id == "C07-fault-responses-blanket") | {narrow, "not-promised"}' tools/probe/corpus/claims.json +dotnet tools/DocsQuery/bin/Release/net10.0/docs-query.dll find nullable value object schema null --top 5 +``` + +An entry covers the row when each of its `axes` values is among the row's applied settings. A docs +sentence counts only when it says this difference, in this place, is intended. Your sense that it "must be +on purpose" does not count, and neither does the axes file's "Intended" list alone: an item there that no +document or record states is a gap of this step, not an answer. + +**Then:** + +- **An entry covers it:** the difference is intended. The row's check cites the entry by its id, no + finding is filed. +- **A document or record says so, but the claim has no entry:** that is a docs or claims gap, filed as + `docs:` work, no finding. Add the entry; if the consumer docs do not say it plainly, add the sentence + first (the `plain-docs` skill), then quote it. +- **Two documents disagree,** or a record calls intended what a consumer page promises: not this step's + to settle. It goes to the code-analyst as a question (step 4's hand-off, "a contradiction"). +- Otherwise go to step 4. + +**Record:** + +- the entry, in `claims.json` (in a round, the staging's `claims.json`), in the form of + `tools/Prober/Schemas/claims.schema.json`. `universe` refuses a sentence its `source` does not hold: + + ```json + "not-promised": [ + { + "id": "invariant-globalization-refuses-as-text", + "source": "docs/text.md#globalization-invariant-mode", + "sentence": "So `AsText()` throws `PlatformNotSupportedException` there, on its first call, whatever the string.", + "axes": { "text.culture": ["globalization-invariant"], "entry": ["AsText", "AsOptionalText"] } + } + ] + ``` + + committed as `docs:` with the sentence it quotes, beside any docs change; +- with a finding id: + + ```bash + dotnet tools/Prober/bin/Release/net10.0/prober.dll dismiss --id R-C02OptionalTextProbe-1a2b3c4 --reason "triage 3 not-promised C02-optional-text/invariant-globalization-refuses-as-text" + ``` + + and for a gap, `--reason "triage 3 docs-gap: ; entry to add"`. + +## 4. A finding: hand it to the code-analyst + +Only a row that none of steps 1 to 3 explains gets here. It is still not a finding until the +`code-analyst` agent, which reads the product path and seeds the defect in a scratch copy, confirms it. + +**Prepare the hand-off:** + +- **The row**: claim, probe, index, commit, category, the reasons, the settings that applied. +- **The shrunk row**, from the harness's `Shrinker`. There is no prober command for it yet. Run it in the + replay's worktree, which the `try` above has just built with the corpus overlaid: + + ```bash + WT=$(dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --where | awk '/^replay:/ {print $2}') + cd "$WT" && PROBE_SHRINK="C07-fault-responses-blanket:358:baseline" \ + dotnet "$OLDPWD/tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll" test --no-build \ + --project test/CodoMetis.TypeKit.Samples --filter-class "Probes.ShrinkProbe" + cat /tmp/shrink.txt + ``` + + `PROBE_SHRINK` is `::`: the rows file the probe reads (`Rows.Data("...")` in the + probe), the rows, and the check, `baseline` for the HTTP calibration or a name registered in + `ShrinkChecks.All` (C03, C05, L02, L03, L04). A check with no name there is handed over unshrunk, and + the hand-off says so. +- **The obligation** the failing check serves: its id and sentence from the claim's `obligations` (the + probe's header quotes them). When the check serves none of the claim's obligations, say so: "none + listed; the check holds : ". That is itself something for the analyst to + report (below). +- **Each side's answer**: the wrapped twin's with TypeKit, the value object's with TypeKit, and the + framework alone where the row ran it, each as the reasons or the log give it. +- **Why steps 1 to 3 did not apply**, one line each, with the facts and entries you looked at. + +**Hand it over** with the Agent tool, `subagent_type: code-analyst`, and a prompt that starts +`Target: triage hand-off` and carries the five items above. The analyst reads the path the row exercised, +reproduces it outside the probe, seeds the defect, and answers Confirmed, Likely or Question. Where you +cannot start an agent (the `prober` agent started as a subagent), put the hand-off in your report under +"For code-analyst", marked "awaiting confirmation", file nothing, and let the caller hand it over. + +**Then:** + +- **Confirmed:** file `F`, the next number not used in the branch's `findings.jsonl` or the corpus's + `dismissed.jsonl`. The fix is a `fix:` commit with the regression test the analyst named (the + `verify-the-guard` skill: revert it, then the variants), ending with `Defect-Class:` and + `Found-By: PR`, and its body names the analyst's confirmation. 6866822 is the model. +- **Likely or Question:** no `F`. The row stays failing, and the question goes to the maintainer in + the round's report under "For code-analyst", with the analyst's answer. +- **A missing obligation** the analyst reports (a check that holds a promise no claim lists): it is filed + as claims work, the obligation added to the claim whose source states it, or a new claim, with its seed + (decision 104). + +**Record**, only after the analyst confirms, a line appended to the branch's `findings.jsonl`, with the +shrunk probe kept in the memory's `open/`: + +```json +{"schemaVersion":1,"id":"F3","claim":"C07-fault-responses-blanket","status":"open","commit":"00d3b8f6...","probe":"open/F3NullableBodySchemaProbe.cs","reason":"triage 4 finding: an MVC [FromBody] VO? over ulong writes oneOf[null, {type:[integer,string]}] where ulong? writes [null,integer,string]; confirmed by code-analyst 2026-10-16"} +``` + +An `R-` finding that became `F` gets a `withdrawn` line whose reason is `triage 4 finding: filed as F`, +so one defect is open once. + +## What each step leaves behind + +| Step | Finding | Change | Commit | +|---|---|---|---| +| 1 harness error | `withdrawn`, `triage 1 harness-error: ...` | the harness | `test:` | +| 2 fact | `prober dismiss`, `triage 2 fact KB-NNN: ...` | the check cites the fact; a new fact gets its test first | `test:` for the citation, `docs:` or `test:` for the fact | +| 3 not promised | `prober dismiss`, `triage 3 not-promised /` or `triage 3 docs-gap: ...` | the `not-promised` entry, the docs sentence | `docs:` | +| 4 finding | `F` open, `triage 4 finding: ...; confirmed by code-analyst` | the fix and its test | `fix:` with `Found-By: PR` | + +A row without a finding id leaves the change and the commit alone. Every reason starts with +`triage `, so `grep '"triage [123]' findings.jsonl dismissed.jsonl` counts what the rounds cost +and what they found. diff --git a/.claude/skills/verify-the-guard/SKILL.md b/.claude/skills/verify-the-guard/SKILL.md index 6e81feab..1db2c705 100644 --- a/.claude/skills/verify-the-guard/SKILL.md +++ b/.claude/skills/verify-the-guard/SKILL.md @@ -1,6 +1,6 @@ --- name: verify-the-guard -description: Prove a guard actually fails without the thing it guards — revert the source fix, or seed the defect for a convention test where there is no source fix to revert. Use whenever adding a test alongside a bug fix or a new validation in this repo, whenever a check's subject is packaging or release wiring rather than code, and before reporting the work as done. +description: Prove a guard actually fails without the thing it guards — revert the source fix, or seed the defect for a convention test where there is no source fix to revert — and test a fix across its input class from a variant catalogue, so it covers the defect's siblings. Use whenever adding a test alongside a bug fix or a new validation in this repo, whenever a check's subject is packaging or release wiring rather than code, and before reporting the work as done. --- # Verify the guard @@ -32,6 +32,46 @@ applied and the test compares a stub against itself. Step 1 is often skipped when the fix is already written. Step 3 recovers the same guarantee after the fact, so do step 3 always, even when you did step 1. +## The variants: the defect's neighbours + +The loop proves the guard catches the input the review named. It says nothing about the next shape of +the same input, and that is where most of this repository's fixes came from: 64 of 133 were a later +member of a chain on the same file and rule, found by the next review one shape at a time. CMTK0010 +took fourteen fixes in two days that way ([the fix history](../../../design/fix-history.md), chain A). +So every fix also runs this step ([decision 95](../../../design/adr/0095-a-fix-is-tested-across-its-input-class.md)), +before it is reported done: + +1. **Name the input class.** The `Defect-Class` trailer's code, and one sentence for the input inside + it: "a type argument reaching a constrained type parameter", not "the method-group case". +2. **Run the fix's test across that class's catalogue.** + + | Class | Catalogue | How | + |---|---|---| + | `AIS`, a type argument | `TypeArgumentSites` in `test/CodoMetis.TypeKit.Analyzers.Tests/Variants/` | A `TypeArgumentSubject` per constraint the rule reads (the constraint, an argument that breaks it, one that meets it), run as `TypeArgumentConstraintVariantTests` runs CMTK0010 | + | `AIS`, what a type argument is | `TypeArgumentKinds`, beside it, 79 kinds and 37 pairs: classes to delegates, arrays, `dynamic`, interfaces, the abstract bases, a type built from a parameter, a parameter under each constraint and attribute, and pairs | An oracle per constraint over what each kind is, from the rule's docs, run as `TypeArgumentConstraintKindTests` runs CMTK0010 | + | `AIS`, any other input | The kinds the rule registers (`Register*Action`), plus method groups, crefs, extension blocks (method, property, static member, operator, the lowered call), generic attributes, `nameof`, null-conditional access, lambdas, local functions and generated code (`GeneratedCodeTests`) | No catalogue yet: build it in `Variants/` on `TypeArgumentSites`' pattern, the subject from the rule and the shapes from the catalogue | + | `EFT`, a key or a query of the EF satellite | `KeyShapes`, `QueryShapes` and `WrappedKinds` in `test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/`: 13 key shapes (alone, each mapping's root, abstract or not, the dependents whose key is a foreign key, composite, configured) by 13 wrapped types on three providers; 22 wrapped types, own orderings included, by accessor, site (column, nullable, collection element, captured list, navigation, JSON, query filter), converter (TypeKit's, named, three foreign forms) and operation | A differential against the same entity over the wrapped type, with the docs' departures as the oracle, run as `KeyGenerationVariantTests` and `QueryTranslationVariantTests` do; a new case is a new value of an axis | + | `GTS` | The `shape.*` axes of `.claude/probe-axes.md` and the Samples' value objects | One declaration per value of each axis the fixed code reads (kind, site, inheritance at least), or the rows of the claim that covers it: `prober rows --claim C --full` | + | `FCI` | The settings of the API the fix touches: the `json.*`, `openapi.*`, `mvc.*` axes | The claim's pairwise rows, `prober rows --claim C`, as a theory or as the claim's probe | + + Run every shape both ways where the catalogue has two: the breaking input reported (or refused) + once, the meeting one never. A false report is a sibling as much as a silence is. +3. **Fix what fails.** Same cause, same commit. Another cause, its own `fix:` commit on the same + branch with `Found-By: VA`. A shape the rule leaves alone on purpose is named in the rule's test + with the decision that says so (`SilentAt`), never dropped from the catalogue. +4. **Teach the catalogue.** A shape the catalogue lacked, found by you or by a later review, goes into + it in the commit that fixes it. The next rule over the same input is then held to it for free. +5. **Say it in the commit body**: the input class, the catalogue and its size, and the siblings found. + +A catalogue that is new is held to the same standard as a guard: seed a defect it should catch, and +when a past chain exists, check out the chain's origin fix and run the catalogue there. Decision 95 +records that run for `TypeArgumentSites`: at chain A's origin it fails on 20 sites, each green at +the later fix that repaired it. + +A catalogue of sites proves breadth, not semantics. What an input *is* (an enum, an array, `dynamic`, a +type built from a type parameter) is a second axis, `TypeArgumentKinds` for a type argument; for another +input, where the fix's defect lives on it, enumerate it in the test as well. + ## Reverting cleanly ```bash @@ -120,5 +160,8 @@ State the counterfactual result explicitly, with the failure output. "Added a te about whether the bug is caught; "reverting the fix makes these three tests fail, with this output" is the claim that matters. +State the variant run too: "`TypeArgumentSites`, 62 sites under `[Sealed]`, `[Concrete]` and +`[ReadOnlyStruct]`, green; two siblings found and fixed" is the claim that the fix covers its class. + When several fixes land together, revert them all at once and confirm the failure count matches the number of guards — a fix whose test still passes stands out immediately. diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 9b6d6da3..0924e153 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,4 +1,7 @@ -# Build, test and pack on every push to main and every PR. +# Build, test and pack on every push to main or a release branch and every PR into one; a PR packs +# only when its diff can change a package (the changes job). Topics +# merge into release/ and the release branch into main +# (design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md). # # The tests run on Linux, Windows and macOS: a contributor needs git and the pinned SDK on any of # them (design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md), and the second and third @@ -20,9 +23,9 @@ name: .NET on: push: - branches: [ "main" ] + branches: [ "main", "release/**" ] pull_request: - branches: [ "main" ] + branches: [ "main", "release/**" ] workflow_dispatch: permissions: @@ -43,6 +46,57 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true jobs: + # Whether this run can change a package or a trimmed consumer: on a push and in the release always, on + # a pull request only when the diff against the base branch touches a shipping project, a build + # property file, a project file, the SDK pin, the package versions, a workflow or the smoke test + # itself. The pack and consumer jobs below run on that answer; the deciding paths are in this job's log. + changes: + name: Changes + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + product: ${{ steps.product.outputs.product }} + + steps: + # The whole history: the diff is against the base branch, which a shallow clone has not fetched. + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 + + # Bash runs with -e; `git diff` of a base branch that was not fetched fails the job rather than + # answering false, so a broken diff can never skip the pack. + - name: Can the diff change a package? + id: product + run: | + if [ "$GITHUB_EVENT_NAME" != pull_request ]; then + echo "product=true" >> "$GITHUB_OUTPUT" + echo "A $GITHUB_EVENT_NAME: the packages and the consumer are always built." + exit 0 + fi + git diff --name-only "origin/$GITHUB_BASE_REF...HEAD" > changed-paths.txt + product=false + while IFS= read -r path; do + case "$path" in + global.json) reason='global.json' ;; + Directory.Packages.props) reason='Directory.Packages.props' ;; + src/*) reason='src/' ;; + Directory.*.props|*/Directory.*.props) reason='Directory.*.props' ;; + Directory.*.targets|*/Directory.*.targets) reason='Directory.*.targets' ;; + *.csproj) reason='*.csproj' ;; + .github/workflows/*) reason='.github/workflows/' ;; + tools/ConsumerSmokeTest/*) reason='tools/ConsumerSmokeTest/' ;; + *) continue ;; + esac + echo "$path ($reason)" + product=true + done < changed-paths.txt + echo "product=$product" >> "$GITHUB_OUTPUT" + if [ "$product" = true ]; then + echo "The paths above can change a package or a trimmed consumer: the packages and the consumer are built." + else + echo "No changed path can change a package or a trimmed consumer: the pack and consumer jobs are skipped." + fi + test: name: Test (${{ matrix.os }}) runs-on: ${{ matrix.os }} @@ -52,7 +106,11 @@ jobs: os: [ ubuntu-latest, windows-latest, macos-latest ] steps: + # The whole history and its tags: CommitTrailerTests reads the fix commits since v2.1.0, and + # refuses a shallow clone rather than pass on one (design/adr/0094). - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 @@ -82,10 +140,153 @@ jobs: path: TestResults/**/*.trx if-no-files-found: ignore + # The probe corpus (tools/probe/corpus), replayed with no model, split by family into legs that run at + # once, each running its probes as many at a time as the runner has cores (design/adr/0101, which amends + # 0096). On a push the whole corpus is replayed from an empty state, with coverage, and the merged state + # is saved for the pull requests into this branch. On a pull request the base branch's saved state is + # restored and only the probes whose identity or footprint the branch changed are replayed, judged + # against the base's footprints, without coverage, since nothing of that state is kept; with no saved + # state every probe runs, as from an empty state. A failing probe fails its leg and the run. Linux only: + # the probes test documented claims, not the platform, and the test job's three legs test the prober. + probes: + name: Probe replay (${{ matrix.family }}) + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + # The families of the axes file's replay configuration, every one: ReleaseWiringTests holds this + # list to that, and `prober replay --family` refuses while a probe's surface is in no family. + family: [ http, ef, json, text, build ] + + steps: + # The whole history: the replay adds a git worktree at HEAD, and `universe` reads the claims + # against origin/main. + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 + with: + global-json-file: global.json + + # The prober's state and its replay worktree, in this job's own temporary directory, never a + # developer's (the default without PROBE_STATE_DIR). The state is the branch's: on a pull request + # the base branch's, so that its saved footprints decide what the branch stales. + - name: Prober state directory + run: | + echo "PROBE_STATE_DIR=$RUNNER_TEMP/probe-state" >> "$GITHUB_ENV" + echo "PROBE_CACHE_DIR=$RUNNER_TEMP/probe-cache" >> "$GITHUB_ENV" + echo "PROBE_BRANCH=${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}" >> "$GITHUB_ENV" + + # The base branch's state as its last green push saved it; the newest entry under the prefix. With + # none (the first run, or an evicted entry) the replay starts empty and runs every probe. + - name: Restore the base branch's probe state + if: github.event_name == 'pull_request' + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ runner.temp }}/probe-state + key: probe-state-${{ github.base_ref }}-${{ github.sha }} + restore-keys: probe-state-${{ github.base_ref }}- + + # The prober is a tool project, run as its built dll (design/adr/0092). + - name: Build the prober + run: dotnet build tools/Prober -c Release + + # Bash runs with -e, so the exit code of the replay is the step's. + - name: Replay the family's probes + run: | + if [ "$GITHUB_EVENT_NAME" = pull_request ]; then + dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --branch "$PROBE_BRANCH" --family "${{ matrix.family }}" --no-footprint + else + dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --all --branch "$PROBE_BRANCH" --family "${{ matrix.family }}" + fi + + # The leg's state, for the merge below and for triage of a failure. + - name: Upload the leg's state + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + if: always() + with: + name: probe-state-${{ matrix.family }} + path: ${{ runner.temp }}/probe-state + if-no-files-found: error + + # The legs' states joined into one: `prober merge` refuses while a registered probe was in no leg and + # fails when a leg did, so this job is red whenever the replay is. Then the status in the job summary, + # and on a push the merged state saved for the branch's pull requests. + probe-status: + name: Probe status + runs-on: ubuntu-latest + needs: probes + if: always() + timeout-minutes: 15 + + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 + with: + global-json-file: global.json + + - name: Prober state directory + run: | + echo "PROBE_STATE_DIR=$RUNNER_TEMP/probe-state" >> "$GITHUB_ENV" + echo "PROBE_CACHE_DIR=$RUNNER_TEMP/probe-cache" >> "$GITHUB_ENV" + echo "PROBE_BRANCH=${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}" >> "$GITHUB_ENV" + + - name: Download the legs' states + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + pattern: probe-state-* + path: ${{ runner.temp }}/probe-legs + + - name: Build the prober + run: dotnet build tools/Prober -c Release + + - name: Merge the legs + run: dotnet tools/Prober/bin/Release/net10.0/prober.dll merge --branch "$PROBE_BRANCH" --from "$RUNNER_TEMP"/probe-legs/probe-state-* + + # A report: the documented claims' pair coverage and the open findings, in the job summary. It + # fails only when the prober cannot read the claims. `universe` builds what `status` reads. + - name: Probe status + if: always() + run: | + dotnet tools/Prober/bin/Release/net10.0/prober.dll universe --branch "$PROBE_BRANCH" > /dev/null + dotnet tools/Prober/bin/Release/net10.0/prober.dll status --branch "$PROBE_BRANCH" | tee status.txt + { echo '### Probe status'; echo; echo '```text'; cat status.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY" + + # The branch's state after a green push: what its pull requests replay against. A red push saves + # nothing, so a pull request judges against the last green state and replays more. + - name: Save the branch's probe state + if: github.event_name == 'push' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ runner.temp }}/probe-state + key: probe-state-${{ github.ref_name }}-${{ github.sha }} + + # The merged evidence of every probe, for triage of a failure. + - name: Upload the probe state + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + if: always() + with: + name: probe-state + path: ${{ runner.temp }}/probe-state + if-no-files-found: ignore + + # The pack catches a package that names a README it does not pack and a broken SBOM step, and the two + # smoke tests a consumer that no longer restores, compiles or publishes with Native AOT against the + # packed feed and against one packed on the spot. A pull request that touches none of the paths the + # changes job lists cannot change any of them, so it skips both, while every push and the release + # (release.yml) still run them. pack: name: Pack runs-on: ubuntu-latest - needs: test + needs: [ test, changes ] + if: needs.changes.outputs.product == 'true' steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 @@ -141,7 +342,8 @@ jobs: consumer: name: Consumer smoke test runs-on: ubuntu-latest - needs: test + needs: [ test, changes ] + if: needs.changes.outputs.product == 'true' steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25504b84..2d9df4a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,7 +77,10 @@ jobs: version: ${{ steps.version.outputs.version }} steps: + # The whole history and its tags, for the suite's CommitTrailerTests (design/adr/0094). - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 @@ -132,6 +135,43 @@ jobs: - name: Test run: dotnet test --solution CodoMetis.TypeKit.slnx -c Release --no-build + # The prober's state and its replay worktree, in this job's own temporary directory, never a + # developer's (the default without PROBE_STATE_DIR). + - name: Prober state directory + run: | + echo "PROBE_STATE_DIR=$RUNNER_TEMP/probe-state" >> "$GITHUB_ENV" + echo "PROBE_CACHE_DIR=$RUNNER_TEMP/probe-cache" >> "$GITHUB_ENV" + + # Every probe of the corpus, with no model, as CI replays it on every push (design/adr/0096). A probe + # that fails fails the release, like a test. The Build above built the prober. Every probe's seeds are + # shown caught before the tag, locally, by `prober seeds --all` (AGENTS.md, Releasing): a build per seed, + # which this job does not pay (design/adr/0100), and --all because a run without it skips the probes + # unchanged since their seeds were caught (design/adr/0107). + - name: Replay every probe + run: dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --all + + # The release gate, against the previous release: on a tag push `git describe` alone would name the + # tag being released and compare the commit with itself. It binds from 2.2.0 (design/adr/0097): a + # refusal fails the release, as any other exit does. A refusal is answered by a probe, a fix or a + # reviewed waiver in tools/probe/corpus/waivers.json, never by editing this step. + - name: Probe gate + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + since="$(git describe --tags --abbrev=0 --exclude "v$VERSION")" + code=0 + dotnet tools/Prober/bin/Release/net10.0/prober.dll gate --since "$since" > gate.txt 2>&1 || code=$? + cat gate.txt + case "$code" in + 0) verdict="passes" ;; + 1) verdict="refused" + echo "::error::The probe gate refused $VERSION since $since; see the job summary." ;; + *) verdict="did not run (exit $code)" + echo "::error::The probe gate did not run (exit $code)." ;; + esac + { echo "### Probe gate since $since: $verdict"; echo; echo '```text'; cat gate.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY" + exit "$code" + - name: Pack run: dotnet pack CodoMetis.TypeKit.slnx -c Release --no-build -o dist diff --git a/AGENTS.md b/AGENTS.md index e9ca8fb6..06300ff6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,11 @@ dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--project ``` The probing tool, prober (`tools/Prober`, started as its built dll like docs-query). These need no -model, no login and no API key, and run in CI as anywhere: +model, no login and no API key, and run in CI as anywhere: the `probes` legs of `dotnet.yml` replay +the corpus by family, every probe on a push and the probes the branch stales on a pull request, judged +against the base branch's saved state, and `probe-status` merges the legs and reports +([decision 101](design/adr/0101-ci-replays-by-family-and-footprint.md)); `release.yml` runs `replay --all` +and `gate` before it packs ([decision 96](design/adr/0096-ci-replays-every-probe-and-the-release-runs-the-gate.md)): ```bash dotnet tools/Prober/bin/Release/net10.0/prober.dll status # coverage of the branch's documented claims, open findings @@ -73,6 +77,15 @@ dotnet tools/Prober/bin/Release/net10.0/prober.dll replay # re-run the probes dotnet tools/Prober/bin/Release/net10.0/prober.dll gate # the release gate, from the tracked corpus and a replay ``` +Proving that each probe can fail needs no model either, but builds once per seed, so it runs locally +only ([decision 100](design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)), and only for the +probes changed since their seeds were last caught; a release runs `--all` +([decision 107](design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)): + +```bash +dotnet tools/Prober/bin/Release/net10.0/prober.dll seeds # each probe's seeded defect, applied in a scratch worktree, must fail it +``` + Exploration needs a model and runs locally only, never in CI: ```bash @@ -84,19 +97,24 @@ An agent runs builds and tests through `tools/BuildVerdict`, run as its built dl from `tools/BuildVerdict/build-verdict-remedies.tsv`, and the failing tests with their assertion; the full log is `$GIT_DIR/build-verdict-last.log`. A code it prints as UNMAPPED gets a row once its fix is known. In an agent's Bash tool, `tools/BashGuard` (registered in `.claude/settings.json`) refuses -`dotnet build` of anything but a tool, `dotnet test` and `dotnet msbuild`, and the verdict piped into +`dotnet build` of anything but a tool, `dotnet test` and `dotnet msbuild` (a `-getProperty`/`-getItem` query +without a target excepted), and the verdict piped into `head` or `tail`, and prints the verdict's form of the command. The raw commands above are for a terminal. Both are tool projects under `tools/` (design/adr/0092): BCL only, in the solution, each with its tests (`tools/BuildVerdict.Tests`, `tools/BashGuard.Tests`), and run from `bin/Release`, which -`.claude/settings.json` builds at session start. Each records the sources it was built from and -refuses to run from others, naming the command that rebuilds it: `dotnet build tools/ -c -Release`, which the guard lets through. +`.claude/settings.json` builds at session start: the guard in the main checkout, every other tool +(`tools/AgentTools.slnf`) in the session's own, a worktree included, with the SDK installer's `dotnet` +when the one on PATH lacks the pinned SDK; a session start that builds nothing says so in your context +(design/adr/0105). Each records the sources it was built from and refuses to run from others, naming +the command that rebuilds it: `dotnet build tools/ -c Release`, which the guard lets through. The SDK is pinned in `global.json` (10.0.4xx band, `latestPatch`). Tests run on Microsoft Testing Platform (xunit.v3 4.x), so `dotnet test` takes `--solution`/`--project`, and TRX output is -`--report-xunit-trx`. The build treats warnings as errors, including CS1591 on shipping projects. +`--report-xunit-trx`. The build treats warnings as errors, including CS1591 on shipping projects, and +runs the CMTK analyzers on the shipping projects' own code (`src/Directory.Build.targets`), so a library +generic that breaks its own constraint attributes fails here, not in a consumer. ## Finding code @@ -209,17 +227,29 @@ the whole page. A ranked miss proves nothing. 2. Run `dotnet test` after each change. 3. **Prove every fix by reverting it.** See `.claude/skills/verify-the-guard`. The same applies to a new analyzer test or a convention test: seed the defect it claims to catch. -4. A change to an aspect, the fabric, the analyzer or a satellite gets a generation review +4. **Test every fix across its input class.** Before a fix is done, name the input class its defect + lives in and run the fix's test over that class's variant catalogue: `TypeArgumentSites` and + `TypeArgumentKinds` (`test/CodoMetis.TypeKit.Analyzers.Tests/Variants/`) for a type argument, where + it is written and what it is, `KeyShapes` and `QueryShapes` + (`test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/`) for an EF key or query, the `shape.*` + axes for a generated type, the settings axes for a framework option. Fix the siblings it finds, add a missing + shape to the catalogue, and say so in the commit body; a sibling found this way is `Found-By: VA` + ([decision 95](design/adr/0095-a-fix-is-tested-across-its-input-class.md), the skill's "The variants"). +5. A change to an aspect, the fabric, the analyzer or a satellite gets a generation review (`.claude/skills/weaving-review`). -5. Commit with conventional format: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `build:`, - `spike:`. -6. **Probes replay themselves; exploring is a separate, capped, local job.** Consumer probes check the +6. Commit with conventional format: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `build:`, + `spike:`. A `fix:` ends with the trailers `Defect-Class:` and `Found-By:`, one of each, from the + tables of [decision 94](design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md) + (`CommitTrailerTests`). Work happens on a `/` branch from `release/`, reaches it + by a PR, and every merge is a merge commit: never squash or rebase a pushed branch. +7. **Probes replay themselves; exploring is a separate, capped, local job.** Consumer probes check the claims in `docs/` and the READMEs, pairwise over the axes in [.claude/probe-axes.md](.claude/probe-axes.md); they, their rows, the claims and the waivers are the corpus in `tools/probe/corpus/`, tracked like tests. - - **Replay** runs after every commit from a git `post-commit` hook, in the background and with no - model: `prober replay` re-runs only the probes whose identity or coverage footprint the commit - changed, and blocks nothing. Read `prober status` before your next change. A probe that passed with + - **Replay** runs in CI on every push and pull request, every probe from an empty state, and a probe + that fails fails the job. Locally it runs after every commit from a git `post-commit` hook, in the + background and with no model: `prober replay` re-runs only the probes whose identity or coverage + footprint the commit changed, and blocks nothing. Read `prober status` before your next change. A probe that passed with the same identity and fails now is an open finding `R--`, fixed like any failing test. The hook is opt-in, per clone and untracked: put this in `$(git rev-parse --git-common-dir)/hooks/post-commit` and make it executable (Git for Windows runs it through its own `sh`): @@ -234,27 +264,31 @@ the whole page. A ranked miss proves nothing. probes land in its staging; `prober publish` moves them into the corpus, committed as `test:`. - **Full sweep** on request (`/probe full`) or nightly, locally: the whole corpus replayed, the axes derived again, then exploration up to the cap. A report, not a gate. - - A probe that found a bug becomes a test in the suite (step 3); a dismissed finding is recorded with + - A probe that found a bug becomes a test in the suite (steps 3 and 4); a dismissed finding is recorded with `prober dismiss` and the reason. An intended difference a probe accepts cites the sentence of the docs, the record, or the verified fact (`KB-NNN`, `design/knowledge/`) that makes it intended. ## Releasing -Before `git tag`, replay the whole probe corpus and run the gate: +Before `git tag`, replay the whole probe corpus and run the gate, and show every probe's seeds caught (locally, a +build per seed; without `--all` it runs only the probes changed since their seeds were last caught, decision 107): ```bash dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --all dotnet tools/Prober/bin/Release/net10.0/prober.dll gate +dotnet tools/Prober/bin/Release/net10.0/prober.dll seeds --all ``` -The gate never starts a model and needs nothing but the tracked corpus and a replay, so CI runs it -with `PROBE_STATE_DIR` in its own workspace. It refuses while a finding is open, a probe has not +The gate never starts a model and needs nothing but the tracked corpus and a replay, so the release +workflow runs it after `replay --all`, with `PROBE_STATE_DIR` in the job's temporary directory and +`--since` the previous release's tag, reports its verdict in the job summary, and fails the release on a +refusal. It refuses while a finding is open, a probe has not passed at its current identity, or a claim touched since the last tag has pairwise combinations no probe covers; it lists what is missing. Every obligation is met by a probe in the corpus, written by hand or by a round, or by a reviewed waiver in `tools/probe/corpus/waivers.json` (claim, reason, reviewer). -**The gate is advisory until the user names the first release it binds.** Until then, run it, report -its result with the release, and tag regardless; a refusal is a list of work, not a stop. The binding -release, once named, is written here. From that release on, never tag over a refusal without the -user's word. +**The gate binds from 2.2.0** +([decision 97](design/adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md)). Never tag +2.2.0 or a later version over a refusal without the user's word, which is a waiver naming them as its +reviewer. Answer a refusal with a probe, a fix or such a waiver, never by editing the workflow's step. diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ee30d4..9a9278d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,23 @@ A version's heading reads `## — Unreleased` until the release is pre the release date. The release workflow refuses a tag whose section still says `Unreleased`, and the section is the GitHub release's notes. -## 2.2.0 — Unreleased +## 2.2.0 — 2026-10-18 + +### Fixed + +- Generators: a value object over `string` implements `ISpanParsable`, as `string` does, and its span `Parse` runs `Create` as `Parse` does; a span `Parse` you wrote on one now fails the build (CMTK1011) ([What is generated](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#what-is-generated)). +- EF Core: a query or a query filter that orders a value object with its own `CompareTo` through `EF.Functions.Greatest` or `Least`, or through a row-value comparison such as Npgsql's `EF.Functions.GreaterThan((o.Priority, o.Id), (priority, id))`, is refused and names the value object, where it used to order by the column ([Queries](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries)). +- OpenAPI: a route, query or header parameter of a type a converter describes, such as a `decimal` under `DecimalStringJsonConverter`, is what ASP.NET writes without the converter: an OpenAPI 2.0 document builds, and a nullable one no longer admits `null` ([Parameters aren't JSON](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a query or header array of a type a converter describes, such as `decimal[]` or `decimal?[]` under `DecimalStringJsonConverter`, keeps its `items`, which are what ASP.NET writes for the element without the converter ([Parameters aren't JSON](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a value object over a type a converter describes, such as `Money : IValue` under `DecimalStringJsonConverter`, is what ASP.NET writes for the underlying type without the converter as a route, query, header or form parameter and as a query or header array's items, no longer its JSON spelling; where value objects are components, such a parameter is written in place ([Parameters aren't JSON](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: the default of a parameter of a type a converter describes, or of a value object over one, such as `[DefaultValue(typeof(decimal), "1.5")]` under `DecimalStringJsonConverter`, is written as ASP.NET writes it without the converter, `1.5`, not `"1.5"` ([Parameters aren't JSON](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a non-abstract `[JsonPolymorphic]` base's own branch (`{Base}Base`) describes its value objects, patches, closed sets and described types as the derived types' branches do, where it had `{}` and a list without `items`, in OpenAPI 3.1, 3.0 and 2.0 ([What the document says](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a list or dictionary of a nullable value object whose converter a described one replaces, such as `List` under a converter that writes cents, admits `null` in its elements where value objects are components ([A described type](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a nullable value-object property where value objects are components is the component or `null` wherever the underlying type's property admits `null`: get-only, a field, typed by a type parameter, under `[MaybeNull]` or `[DisallowNull]`, or without nullable annotations, where it was the bare reference; under OpenAPI 2.0 it's ASP.NET's `x-nullable` without the reference there too, as a settable one already was ([What the document says](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a nullable value object is its wrapped type at the same nullability in every position: an MVC `[FromBody] Quantity?` admits `null` inside its schema as `int?` does, and, where value objects are inlined, a query, header or form parameter no longer admits `null`, nor does a body, response or element of a nullable struct over `string` or `Uri` ([What the document says](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a nullable value object over an enum written by name lists `null` among its values as a route, query, header or form parameter, as the enum's own `DayOfWeek?` does; where value objects alone are inlined, a nullable one over an enum no longer puts a second null-or inside a body's or a response's, and as an element or a dictionary value keeps its type in OpenAPI 2.0 ([What the document says](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says)). +- OpenAPI: a problem response an endpoint declares as `application/json`, such as `Produces(404)` or `[ProducesResponseType(404)]`, describes `fault` and `rule` and names its problem types, and keeps its media type; so does one of a type derived from `ProblemDetails`, such as `ProducesValidationProblem(404)` ([A Result as a response](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#a-result-as-a-response)). +- EF Core: a key over a `Guid` or a `string` configured `ValueGeneratedOnAdd()` is generated as the plain key is, not as a random Guid: a version 7 Guid, or its text, on PostgreSQL, and a Guid sequential in SQL Server's order there ([Keys](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)). ## 2.1.0 — 2026-10-15 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index acc65bfb..8f94af09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,12 @@ it fail, apply the fix, watch it pass. Then *revert the fix and confirm the test test that passes both with and without the fix is not a regression test. See [`.claude/skills/verify-the-guard`](.claude/skills/verify-the-guard/SKILL.md). +**A bug fix covers its siblings.** Name the class of input the defect lives in, and run the fix's test +across that class's variant catalogue: every shape through which a type argument reaches a parameter +(`test/CodoMetis.TypeKit.Analyzers.Tests/Variants/`), every declaration shape of `.claude/probe-axes.md`, +every setting of the framework option the fix touches. Fix what fails there too, rather than leave it +for the next review ([decision 95](design/adr/0095-a-fix-is-tested-across-its-input-class.md)). + **Every analyzer rule has a positive control per syntax form it registers.** An analyzer that silently stops firing is the worst outcome this repository can ship, because nothing turns red. The rule id and severity are asserted as literals: both are public contract. @@ -84,11 +90,26 @@ should fail the build or throw with an actionable message. ## Pull requests +Work for the next version goes into its release branch, `release/` (`release/2.2.0`), not +into `main` ([decision 94](design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md)). +Branch from it as `/`, where the type is your main commit's (`feat/…`, `fix/…`, `docs/…`, +`build/…`), and open the PR against it. PRs are merged with a merge commit, never squashed or rebased: +the design records cite commits by hash. + - One concern per PR, with the reasoning in the description rather than only in the diff. - Full suite green. - User-visible changes get a changelog entry. - Match the surrounding style. Comments explain *why*, especially where behaviour is load-bearing and non-obvious. +- A `fix:` commit ends with two trailers, from the tables in decision 94: the defect's class and who + found it. `CommitTrailerTests` fails without them. + + ``` + fix: a wrapped Parse returning a convertible type is generated again + + Defect-Class: GTS + Found-By: CA + ``` ## Releasing @@ -100,13 +121,15 @@ The packages share one version and are released together, only through 2. Move the rules in `src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Unshipped.md` to `AnalyzerReleases.Shipped.md`, under `## Release `. Their ids and severities are public contract from then on. `ChangelogTests` fails until steps 1 and 2 agree. -3. Commit, then tag and push: `git tag v1.0.0 && git push origin v1.0.0`. The workflow checks the tag +3. Commit on the release branch, open its PR into `main`, and merge it with a merge commit. Tag that + merge commit and push the tag: `git tag v1.0.0 && git push origin v1.0.0`. The workflow checks the tag against `Version` and the dated heading, runs the suite and the smoke test, and waits for approval - in the `nuget` environment before pushing. + in the `nuget` environment before pushing. Delete the release branch once the tag is pushed. 4. Afterwards: raise `Version` in `Directory.Build.props`, set `PackageValidationBaselineVersion` to the version just released, so ApiCompat guards the public surface from then on, and open a new `## — Unreleased` section. `ChangelogTests` fails while the baseline trails the last - version the changelog dates. + version the changelog dates. Once that is on `main`, cut the next `release/` from it and + push it. `workflow_dispatch` with `dry_run` (the default) rehearses everything but the push. diff --git a/design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md b/design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md index 29cdb9e2..94be2884 100644 --- a/design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md +++ b/design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md @@ -62,7 +62,8 @@ holds nothing: the prober's Python and the smoke test's bash are C# (`tools/Prob **Hooks are one-line shims.** A git hook or a Claude Code hook execs `dotnet` and holds no logic; Git for Windows runs a git hook through its bundled `sh`, so a one-line shim works on every platform. -Claude Code's hooks build the tool projects they run in a SessionStart hook, before any tool call. +Claude Code's hooks build the tool projects they run in a SessionStart hook, before any tool call +(where, and with which `dotnet`: [decision 105](0105-every-tool-is-built-in-the-session-s-checkout.md)). **Prerequisites.** A contributor needs git and the SDK that `global.json` pins. A task that needs more names it as its own prerequisite, and no other task needs it: the platform linker for the smoke diff --git a/design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md b/design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md new file mode 100644 index 00000000..576bb472 --- /dev/null +++ b/design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md @@ -0,0 +1,107 @@ +# 0094. From 2.2.0, topics merge into a release branch, and a fix names its defect class and its finder + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, the release of 2.1.0. Its 112 commits since v2.0.0 were made on one branch, +`feat/2.1.0`, and reached `main` in one pull request. Before merging, splitting them into branches by feature, +fix, tooling and docs was weighed and refused: 33 of the 109 commits then on the branch were cited by hash in +tracked files (the decision records, [the fix history](../fix-history.md), the prober's corpus), which a +rewrite would orphan, and 54 touched more than two areas, by design, a fix with its test and its changelog +line. What reorganising afterwards cannot buy, starting a cycle that way can. The same day the fix history +classified the 133 fixes to date by defect class and by who found each one, and had to reconstruct both: from +the commit bodies, the pasted reviews and 44 local session transcripts, which CI and other contributors cannot +read, matching 130 of the commits by the text of their subject. + +## Decision + +**A release branch per version.** Once the previous release is tagged and its successor opened on `main` +(CONTRIBUTING, "Releasing", step 4), `release/` is cut from `main` and pushed. 2.2.0's is +`release/2.2.0`, cut at 0450143. + +**A topic branch per concern.** Work starts on a branch from the release branch, named `/`, where +`` is the conventional type of its main commit (`feat/cause-on-fault`, `fix/serilog-max-length`, +`docs/…`, `build/…`). It reaches the release branch through a pull request, one concern each, with CI on +Linux, Windows and macOS, merged with a merge commit. + +**Merge commits only, everywhere.** No squash and no rebase of a pushed branch, into the release branch or +into `main`: a hash a record cites stays valid. A release branch that falls behind `main` (a patch release) +takes `main` by a merge. + +**The release.** The release branch reaches `main` in one pull request, merged with a merge commit, and the +tag goes on that merge commit, as `v2.0.0` and `v2.1.0` did. The release branch is deleted once tagged. + +**A patch release.** `release/` is cut from the tag it patches and follows the same rules; its fixes +reach the open release branch by merging `main` once it is tagged. + +**Two trailers on every fix.** A commit whose subject is `fix:` (with or without a scope or `!`) carries, +in the trailer block beside `Co-Authored-By`: + +``` +Defect-Class: GTS +Found-By: CA +``` + +exactly one of each, from the vocabulary the fix history counted with. A commit that fixes several defects +names its main one, as the baseline did. + +| `Defect-Class` | The defect's primary cause | +|---|---| +| `FCI` | Framework configuration interplay | +| `GTS` | Generator type shape | +| `AIS` | Analyzer input shape | +| `EFT` | EF Core translation and mapping | +| `API` | The public surface let a misuse compile | +| `POS` | Process order and state | +| `DOC` | Contract or docs drift | +| `TXT` | Text and globalization | +| `AOT` | Native AOT or trimming | +| `SDL` | Silent does-less, no more specific cause | +| `CRP` | Crash or performance | +| `TOOL` | Tooling or test infrastructure | + +| `Found-By` | Who or what found it | +|---|---| +| `RS` | A review session, its reviewer subagents included | +| `CA` | The `code-analyst` agent | +| `PR` | The prober | +| `ST` | The smoke test, a Native AOT publish, a clean-container run | +| `OR` | An outside review, pasted in | +| `US` | The maintainer's own use, or a downstream consumer's | +| `MS` | A measurement: a benchmark, a stress run | +| `FR` | Feature research | +| `SF` | The session that was fixing something else | +| `UN` | Unknown | + +A new code is added here, in a new record, and in the test below together, never in a commit alone. + +**Held by a test.** `CommitTrailerTests` in the conventions tests reads `git log v2.1.0..HEAD` without merges +and fails for a fix commit that lacks either trailer, repeats one, or names a code outside the tables. It +fails, rather than passes, on a shallow clone or without the `v2.1.0` tag, so CI's test job checks out the +full history (`fetch-depth: 0`). Its positive control builds a throwaway repository with a fix commit of +each kind and holds the reader to finding each. + +**CI on release branches.** `dotnet.yml` runs on a push to `main` or a `release/**` branch and on a pull +request into either. + +## Consequences + +- The fix history can be run again from `git log --format='%(trailers:key=Defect-Class,valueonly)'` alone, by + anyone with a clone, and the next classification reads the 2.2.0 cycle from the trailers, not from + transcripts. +- Which finder catches which class becomes visible per release: the question the baseline left with three + prober finds and ten from `code-analyst`. +- A topic's CI runs before it meets the others, not only on the release pull request. +- A fix commit without its trailers fails CI on its topic's pull request. Before the branch is pushed, the + commit is amended. Once pushed it is never rewritten, so its hash joins the test's exemptions, with the + trailers it should have carried, in the same pull request. + +## Evidence + +- Counted on `feat/2.1.0` at `dc459b0`, 2026-10-15: 109 commits since `v2.0.0`; 33 of them cited by hash in + tracked files; 54 touching more than two of `src/`, `test/`, `docs/`, `design/` and `tools/`. +- [The fix history](../fix-history.md): 133 fixes; 130 matched to their session by subject text, 3 by the + commit body; finder attribution inside a review session "firm where the session's report lists the item + and a guess where the fix agent had several reports". +- The repository's history is 10 MB (`du -sh .git`, 2026-10-15), so a full checkout in CI costs little. +- `v2.0.0` points at 09df07e, the merge of pull request #2, and `v2.1.0` at f0f5830, the merge of #5. diff --git a/design/adr/0095-a-fix-is-tested-across-its-input-class.md b/design/adr/0095-a-fix-is-tested-across-its-input-class.md new file mode 100644 index 00000000..4a833d65 --- /dev/null +++ b/design/adr/0095-a-fix-is-tested-across-its-input-class.md @@ -0,0 +1,90 @@ +# 0095. A fix is tested across its input class, from a variant catalogue + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, the opening of the 2.2.0 cycle. [The fix history](../fix-history.md) counted 133 fixes, and +64 of them are a later member of a tight chain: the same rule or function in the same file, repaired again for +another shape of the same input. CMTK0010 took 14 fixes in two days (chain A), the converter and +`ReferenceHandler.Preserve` checks 12 on one day (chains C and D), CMTK0003 7 over three weeks (chain B). Every one +of those fixes was proven by reverting it ([verify-the-guard](../../.claude/skills/verify-the-guard/SKILL.md)), +which shows that the guard catches the input the review named, and says nothing about that input's neighbours. The +next review found the neighbours, one at a time. + +## Decision + +**The variant step.** Before a fix is reported done, the fixer + +1. **names the input class** the defect lives in: its `Defect-Class` ([decision 94](0094-topic-branches-into-a-release-branch-and-fix-trailers.md)) + and, in one sentence, the input inside it ("a type argument reaching a constrained type parameter", "a value + object declared over a base record", "the converter `JsonSerializerOptions` puts in charge of a property"); +2. **runs the fix's test across that class's catalogue**, the input reached in every shape the catalogue lists. An + entry passes, or is silent by a decision the rule's test cites beside it; +3. **fixes what fails** as part of the same work: in the same commit when it has the same cause, otherwise in its + own `fix:` commit on the same topic branch, before the pull request; +4. **teaches the catalogue**: a shape the step missed and a review later found goes into the catalogue in the + commit that fixes it, so every rule over that input is tested on it from then on; +5. **says so** in the commit body: the input class, the catalogue run, and the siblings it found. + +**The catalogues.** + +| Class | Catalogue | Run | +|---|---|---| +| `AIS`, a type argument | `TypeArgumentSites` (`test/CodoMetis.TypeKit.Analyzers.Tests/Variants/`): 62 shapes through which an argument reaches a parameter, each run with an argument that breaks the rule's constraint and one that meets it | a `TypeArgumentSubject` per constraint, as `TypeArgumentConstraintVariantTests` does for CMTK0010 | +| `AIS`, any other input | The syntax and operation kinds the rule registers, plus method groups, crefs, extension blocks (method, property, static member, operator, lowered call), generic attributes, `nameof`, null-conditional access, lambdas and local functions, and generated code (`GeneratedCodeTests`) | Built in `Variants/` the first time a fix needs it, on `TypeArgumentSites`' pattern: the rule supplies a subject, the catalogue the shapes | +| `GTS` | The `shape.*` axes of `.claude/probe-axes.md` (kind, site, inheritance, wrapped, position, containing, refused kind) and the value objects of `test/CodoMetis.TypeKit.Samples` | A declaration per value of each axis the fixed code reads, in the fix's test project, or the rows of the claim that covers it (`prober rows --claim C --full`) | +| `FCI` | The settings of the API the fix touches: the generated `json.*`, `openapi.*` and `mvc.*` axes (`tools/probe/enumerate.cs`, values from `hand`) | The claim's pairwise rows (`prober rows --claim C`), as a theory or as the claim's probe | + +Another class names its catalogue the first time a chain of its own shows up in the fix history. + +**A new finder.** A defect found by the variant step of another fix is `Found-By: VA`, so that the next +classification counts what the step found against what reviews still find. `CommitTrailerTests` takes the code. + +**Not a trailer.** A `Variants:` trailer naming the catalogue run was weighed and refused: its value is free text, +and no test could tell a run from a claim. The commit body says it. + +## Consequences + +- A fix to a rule over type arguments is tested on 62 shapes and two directions (a breaking argument reported once, + a meeting one never) instead of the one the review named. +- `TypeArgumentSites` declares its own subjects, so a shape the language adds (a new member kind in an extension + block, a new pattern the compiler binds) is added once and holds every rule that reads type arguments. +- Where an input class has no catalogue, the first fix in it pays for one. The step asks for the smallest that + covers the forms named above, not for completeness. +- The step proves breadth across shapes, not semantics: what an argument *is* (an enum, an array, `dynamic`, a type + built from a type parameter) is a second axis, the one chain A's other seven fixes ran along, and has no + catalogue yet. + +## Evidence + +Measured 2026-10-15 by copying `TypeArgumentSites` and `TypeArgumentConstraintVariantTests` (the `[Sealed]` and +`[Concrete]` subjects; `[ReadOnlyStruct]` did not exist yet) into a checkout of each chain A commit and running them +there. The catalogue's shapes are the forms this record lists and the language's, with the reported locations +taken from CMTK0010's current tests; it was written knowing the chain, so the run measures what the catalogue +catches, not what a fixer would have thought of unaided. The receivers through `base.`, a constrained type +parameter, an unqualified call and a static implementation were added after a first run, as the call-resolution +shapes were thin. + +At the chain's origin, `a77bdda` (the method-group fix), 20 sites fail (46 of 249 cases), and each turns green at +the later fix that repaired it: + +| Fixed by | Sites failing at `a77bdda` | +|---|---| +| `37317de` | a cref to the generic type, a cref to the generic method (both reported for any argument), an interface method's implementation, an override | +| `e4361e9` | an extension block's method, property, static member, method group and operator; a generic attribute, also named in full; a base class's method implementing an interface for a derived class, and called unqualified there; a static method implementing a static abstract one | +| `a0ddf61` | a null-conditional call, written (reported twice); a `nameof` of the closed type and of its member; an argument that does not exist (CMTK0010 beside CS0246) | +| `370414a` | a base class's implementation through `base.` and through a constrained type parameter | + +Run at each later fix, it also catches two regressions those fixes introduced, which the fix history did not count +as repairs: from `e4361e9` on, a breach in generated code (an auto-generated header, a `.g.cs` file, +`#line hidden`) is reported, since `e4361e9` moved CMTK0010's reporting to `context.ReportDiagnostic`, until +`7c30840` (4 sites); and a default interface method called through a derived interface overflows the test host's +stack (exit 134) from `e4361e9` until `cdab810`. At `cdab810` and at `6368c1c`, the chain's last fix, every case +passes; on `release/2.2.0` all 373 do. + +So six of chain A's thirteen later fixes (`37317de`, `e4361e9`, `a0ddf61`, `370414a`, `7c30840`, `cdab810`) would +have been found by the fixer of the one before. The other seven (`0d6ba1a`, `2f8a854`, `7792801`, `d27cf2d`, +`1a9d7f2`, `75fb3aa`, `6368c1c`) are about what an argument is, the axis above that has no catalogue. + +Seeded on `release/2.2.0`: without CMTK0010's `MethodReference` and `PropertyReference` registrations, 15 cases +fail, the five method-group and extension-property sites under each of the three subjects. diff --git a/design/adr/0096-ci-replays-every-probe-and-the-release-runs-the-gate.md b/design/adr/0096-ci-replays-every-probe-and-the-release-runs-the-gate.md new file mode 100644 index 00000000..f48358af --- /dev/null +++ b/design/adr/0096-ci-replays-every-probe-and-the-release-runs-the-gate.md @@ -0,0 +1,100 @@ +# 0096. CI replays every probe on every push and pull request, and the release runs the gate, advisory + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, the bug-finding review of the 2.1.0 cycle. [The fix history](../fix-history.md) found +that 12 of 133 fixes repaired or finished an earlier fix ("Fixes that repair fixes"). Replaying the probe +corpus after each change catches part of that, but the replay ran only from a `post-commit` hook that is +opt-in, per clone and untracked. AGENTS.md said that `replay` and `gate` "run in CI as anywhere" and that +"CI runs it with `PROBE_STATE_DIR` in its own workspace", while neither `.github/workflows/dotnet.yml` nor +`release.yml` called the prober. Neither command starts a model, so [decision 92](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) +allows both in CI. + +## Decision + +**A `probes` job in `dotnet.yml`**, on `ubuntu-latest`, on every push to `main` or a `release/**` branch +and every pull request into one, beside the test matrix (it needs no other job). It builds `tools/Prober` +in Release, runs `prober replay --all`, then `prober universe` and `prober status`, writes the status to +the job summary, and uploads the state directory as the `probe-state` artifact for triage. Linux only: the +probes check documented claims, not the platform, and the test job's three legs already test the prober. + +**`replay --all`, from an empty state directory, and no cache.** The footprint replay was the other +candidate. On an empty state it selects every probe anyway (`replay --dry-run` and `replay --all +--dry-run` both listed 7 of 7), so the real alternative was the footprint replay over a state carried from +run to run in the Actions cache. That was refused: + +- A carried state makes CI's answer depend on what an earlier run left behind. A cache entry is chosen by + key and branch scope, not by the commit the state describes, and a probe the footprint does not select + is not run. The footprint is the coverage of `src/` plus a fixed list of compile-time paths, so a + dependency it does not see skips a probe until something it does see changes. A regression that a fix + brings in elsewhere is exactly that case. The local hook keeps that trade-off, where speed between + commits matters; the check of record reads the tracked corpus and the commit alone, as decision 92 asks + of every CI check. +- It saves no wall time. The job runs beside the test matrix, and the test, pack and consumer jobs take + about 13 minutes from end to end; the cold replay takes under 5. +- On an empty state, every failing probe fails the job. That includes a probe that passed at its + identity and fails now, which is what the replay must catch, and also a probe that never passed. A + probe in the corpus is expected to pass; one that fails for a known defect is fixed, retired or + removed, not left failing. `--all` includes retired probes, as the release gate does. + +Revisit this when `replay --all` on a runner takes longer than the test job's path to green. + +**The release replays and gates before it packs.** In `release.yml`'s `verify` job, after the test suite +and before `dotnet pack`, `prober replay --all` runs, and a failing probe fails the release, as a failing +test does and as the replay already failed the release pull request. Then `prober gate --since ` +runs against the previous release's tag, `git describe --tags --abbrev=0 --exclude v`. Without +`--since`, the gate reads `git describe` itself, which on a tag push names the tag being released: the +gate compared the release with itself and found nothing touched. **The gate stays advisory** (AGENTS.md, +"Releasing"): exit 1 is a refusal, reported with its reasons in the job summary and a warning, and the +release goes on. Any other exit, a usage error or a stale dll, means the gate did not run, and fails the +job. Once a binding release is named, the refusal fails the job instead, in a new record. + +**The state stays in the job.** `PROBE_STATE_DIR` and `PROBE_CACHE_DIR` point under `$RUNNER_TEMP`, +written to `$GITHUB_ENV` by a step. A job's `env:` cannot read the `runner` context: GitHub refused the +workflow ("Unrecognized named-value: 'runner'"). + +**Held by a test.** `ReleaseWiringTests` in the conventions tests holds both: + +- `dotnet.yml` has exactly one job that runs `prober replay`. It sets `PROBE_STATE_DIR`, builds the + prober in Release first, runs `replay --all` as a bare run line, with no `|| true` and no + `continue-on-error`, and runs `status` after it. +- `release.yml`'s `verify` job sets `PROBE_STATE_DIR`, builds first, runs `replay --all` the same way, + then `gate --since`, and only then `dotnet pack`. + +## Consequences + +- A regression that the corpus catches fails the topic's pull request on its way into the release branch, + whether or not its author installed the hook. +- The job summary of every run shows the pair coverage of the documented claims, and a failing run leaves + each probe's evidence in the `probe-state` artifact. +- Each probe added to the corpus adds its replay time to the job, about 7 to 25 s per process variant on a + runner. +- The first replay on a runner found a defect in `tools/BuildVerdict`. Under GitHub Actions the test + platform colours its output although it is redirected, and an escape sequence opened the summary's lines + (`ESC[m total: 177`). The verdict found no total and reported every passing probe as + `TESTS DID NOT REPORT`. It now strips terminal control sequences before it parses (a366509). + +## Evidence + +Measured 2026-10-15 on `ubuntu-latest` with the pinned SDK, at 8dd02d2 (`release/2.2.0` at dac10f0 with +this change), 7 probes and 15 process variants, all passing: + +| Replay | Probes | Time | +|---|---|---| +| `replay --all`, empty state, cold build | 7 of 7 | 287 s (the build 73 s) | +| footprint replay right after it, nothing changed | 0 of 7 | 1 s | +| `replay --all` again, warm build | 7 of 7 | 209 s (the build 4 s) | +| footprint replay after a one-line change to `src/CodoMetis.TypeKit/Option.cs`, warm build | 3 of 7 | 113 s (the build 46 s) | + +The state directory was 4.2 MB and the replay worktree 128 MB. The whole `probes` job, without the +measurements, takes about 5 minutes. A run of `dotnet.yml` on `release/2.2.0` earlier the same day took +3 to 6.6 minutes for the test legs and 13 minutes to the last job. + +On macOS (arm64), the same `replay --all` from an empty state took 229 s, and the footprint replay after +it 5 s. + +The convention test was shown to catch each of these, seeded one at a time: the replay step dropped, its +exit swallowed by `|| true`, `continue-on-error` on it, `replay` without `--all`, `status` dropped, the +prober's build dropped, `PROBE_STATE_DIR` unset in either workflow, the release's replay dropped, its gate +dropped, the gate without `--since`, and `dotnet pack` moved before the replay. diff --git a/design/adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md b/design/adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md new file mode 100644 index 00000000..ec0d50de --- /dev/null +++ b/design/adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md @@ -0,0 +1,40 @@ +# 0097. 2.2.0 is a consolidation release, and the probe gate binds from it + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, an independent review of the repository at `1863f32`, the head of `release/2.2.0`; +amends where [decision 88](0088-2-1-0-ships-without-its-open-roadmap-items.md) placed the open roadmap +items, the "Releasing" section of `AGENTS.md`, which left the gate advisory until a release was named, and +[decision 96](0096-ci-replays-every-probe-and-the-release-runs-the-gate.md), whose release step reported a +refusal and went on. + +The review's numbers, measured again here: 399 commits since 2026-09-27, 135 of them `fix:`; five tags in +18 days, a major two weeks after 1.0.0. By the fix history's count, 69 fixes came from review sessions and 5 +from users. The machinery meant to replace that exposure has not run yet: the corpus holds 42 claims and 11 +registered probes, `prober status` on `release/2.2.0` found no universe, the gate is advisory, and no `fix:` +commit since v2.1.0 carries the trailers of [decision 94](0094-topic-branches-into-a-release-branch-and-fix-trailers.md). +Also, `AGENTS.md` said CI runs the gate, but no workflow did, until decision 96 added the step as advisory. + +**Decision.** + +- **2.2.0 adds no public API.** It adds no type, member, overload or generated member, and no new + analyzer rule. It takes fixes to behaviour that is already documented, each with its trailers. It also + takes performance work measured before and after, guards, docs and repository tooling. A Warning may + still learn what it misses, as [decision 34](0034-errors-fixed-within-major-version-warnings.md) allows. +- **The additive items move to 2.3.0 unchanged:** the `Cause` on a fault + ([decision 52](0052-cause-on-fault-joins-2-1.md)), the opt-in that withholds the `fault` name + ([decision 64](0064-fault-name-reaches-client-causes-not-told-apart-one-fault.md)) and the default `ulong` + key ([decision 63](0063-char-keys-application-assigned-ulong-documented.md)) stay decided, with 2.3.0 as + their release. So do the gaps that add surface: async test assertions, `CatchAsync` over a `ValueTask`, + and `TraverseAsync` on `Option` and `Patch`. +- **2.2.0's own work** is the probe corpus, grown until the gate passes on probes and reviewed waivers; + the gaps that are defects; and the generated-surface snapshot. +- **The gate binds from 2.2.0.** No tag of 2.2.0 or later goes over a refusal without the user's word. A + refusal is answered by a probe in the corpus, a fix, or a waiver in `tools/probe/corpus/waivers.json` + that names its reviewer; the reviewer is the user's word. `release.yml`'s gate step ends with the gate's + exit code, so a refusal fails the release before it packs, and `ReleaseWiringTests` fails for a step that + catches the code without ending on it or that carries `continue-on-error`. + +Not decided here: a cadence for later releases, which the 2.2.0 cycle should inform, not a rule written +before it. diff --git a/design/adr/0098-a-null-passes-no-comparison-in-a-query.md b/design/adr/0098-a-null-passes-no-comparison-in-a-query.md new file mode 100644 index 00000000..2aac2659 --- /dev/null +++ b/design/adr/0098-a-null-passes-no-comparison-in-a-query.md @@ -0,0 +1,39 @@ +# 0098. A nullable record-class value object compared in a query answers as SQL does, and the docs say so + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, the EF variant catalogue of [decision 95](0095-a-fix-is-tested-across-its-input-class.md) +(`QueryTranslationVariantTests`), which left one case open; [EF Core](../ef-core.md), +[decision 57](0057-hand-written-ordering-refused-in-query.md). + +The comparable aspect's operators on a record class take a nullable operand and sort null first +([aspects](../aspects.md), "A record class sorts null first"), so in memory `r.Rank < rank` is true for a null +`Rank`, and so is `<=`. EF drops the operator and translates `"r"."Rank" < @rank`, which is unknown for NULL, +and the row is left out without a word. Measured with EF Core 10.0.12 on SQLite, for a record class over +`string` and one over `int`, through `UseTypeKit()` and the named converter: the query leaves out exactly the +null rows memory keeps. A record struct is unaffected: its lifted operators give +false for null, in memory and in SQL alike. + +**Decision.** The query keeps SQL's answer, and the EF README's "Good to know" and `docs/ef-core.md` say that a +null passes no `<`, `<=`, `>` or `>=` in the database, and how to keep those rows +(`r.Rank == null || r.Rank < rank`). Not refused, because: + +- **SQL's answer is the underlying type's.** The same query over an `int?` column, whose lifted `<` is false + for null, answers the same rows (the catalogue's differential). The departure is from the + generated operators alone, where decision 57 refused an ordering the database cannot run at all, whose every + row could be the opposite. +- **A guard cannot see it.** A nullable record-class property and a required one have the same type in the + expression tree, so refusing by type would reject `r.Rank < rank` on every required column too. Reading + nullability from the model still misses a parameter that is null at run time, since the interceptor sees one + tree per query shape. +- **A refusal would leave no plain way to write the query.** `r.Rank.Value < rank.Value` throws in memory for + a null `Rank`, and 2.2.0 takes guards, not breaks of queries 2.1.0 answers + ([decision 97](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md)). +- **Rewriting to memory's answer** (`IS NULL OR <`) would change rows silently, and depart from the SQL of the + underlying type's column, which the docs promise ("re-typed, not cast"). + +Held by `QueryTranslationVariantTests` (`NullsLeftOut`): the query answers as the same query over the +underlying type, and memory's rows are the query's and the null rows. Seeded both ways on 2026-10-15: the +generated operators sorting null last, and an interceptor rewriting `<` to keep null rows, each fail all six +cases. The catalogue gained `<=` and a record class over `int` for it. diff --git a/design/adr/0099-a-probe-row-says-how-it-ended.md b/design/adr/0099-a-probe-row-says-how-it-ended.md new file mode 100644 index 00000000..186a9609 --- /dev/null +++ b/design/adr/0099-a-probe-row-says-how-it-ended.md @@ -0,0 +1,73 @@ +# 0099. A probe row says how it ended: notes are a closed catalogue, a row that earned nothing is a skipped test, and a skip for a finding binds the gate + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, an outside review of the probing infrastructure, pasted into a session and checked +against the code and the last replay's evidence on `docs/consolidation-release`. The review's main concern +was the soft-pass paths: a check that notes something and returns no failure. Measured: + +- **Most rows of the HTTP probes earned nothing, and every test runner showed them green.** BaseProbe passed + 46 of 346 rows, C03 94 of 345, C05 81 of 347. The rest ended as `expected-refusal` or `inapplicable`, + which the ledger rightly does not credit, but xUnit reported a pass, and `prober status` showed only pairs. +- **A note's outcome was read from its text.** `NoteCategories` matched substrings (`"-NA"`, `"base64"`, + `"cannot-bind"`), and a note that matched none left the row a pass: an unrecognised note failed open. +- **A skip outlived its finding.** C03 skipped a document failure in `ClosedSetSchemaTransformer.Describe` + for an own record class, "open finding F2". F2 was fixed in `461c920`; the skip stayed, so F2 coming back + would have been filed as `inapplicable`, never as a finding. +- **The document's baseline kept TypeKit on both sides.** F2 had first been hidden by `HttpBaseline`, which + isolated a failing document by comparing the value object's with the wrapped type's, both with + `AddTypeKit()`, and filed it `doc-refused-for-wrapped-too`. +- **Two settings made pairs uncoverable.** Under `json.ReferenceHandler=Preserve` or `json.MaxDepth=3`, + System.Text.Json refuses the OpenAPI document of a problem response for plain ASP.NET too. C03 and C05 + noted it by substring and ended the whole row `expected-refusal`, its passed wire checks included, so no + pair with either value could ever be covered: 2049 of C03's pairs and 2518 of C05's stayed uncovered. +- **A harness error raised without a failure text passed the test.** `Rec.Run` asserted only on the + returned failure; `Outcome("harness-error", …)` followed by `return null` was green in xUnit. + +## Decision + +**Notes are a closed catalogue.** `NoteKeys` in `harness/Row.cs` maps every note key to its outcome +(`pass`, `inapplicable`, `expected-refusal`, `inconclusive`). A check calls `r.Note(key, detail)`; the key +alone decides, the detail only explains. A key not in the catalogue makes the row a harness error. No +failure text is matched to excuse a row. + +**A row that earned nothing is a skipped test.** `Rec.Run` fails the test for a `violation` or a +`harness-error`, however it was raised, and ends every other outcome but a pass with `Assert.Skip` and its +reasons. Every runner then counts what the ledger counts ("47 passed, 130 skipped"). Each row is labelled +with its index and the settings it sets that are not defaults (`Row [#6 input.json=valid json.MaxDepth=3 …]`), +through xunit v3's `TheoryDataRow.Label`, so a test list and a failure name the row without the ledger. +`build-verdict` keeps such a label whole in its headings. + +**A skip for a finding names it.** A row that would fail for an open finding calls `r.Skip("F7", why)`: it +ends as `known-finding`, and the replay records the finding with the row. The replay fails a probe that +skips for a finding the ledger has fixed (in HEAD's history) or the corpus has dismissed. The gate refuses +while any row skips for any finding: a skip is an open finding that the tracked corpus records, so the gate +sees it from an empty state, as in CI and the release, where the ledger's own findings are absent. + +**A document failure is judged against ASP.NET alone.** When the document fails with `AddTypeKit()`, the +same host is started without it (`Hx.DocRefusedByAspNetAlone`). The same exception there means the +document part has nothing to check, and the row is judged by its other checks +(`doc:refused-by-aspnet-alone`, a pass). ASP.NET alone writing the document, or failing otherwise, is a +violation. This replaces the substring rules for `Preserve`, `MaxDepth=3`, `char` with `WriteAsString` +and F2 in C03 and C05, and the wrapped-against-value-object isolation in `HttpBaseline`. A controller +without `[ApiController]` is checked absent from the document before its document part counts as nothing +to check (docs/aspnet-core.md, "Bind value objects in MVC"). + +**`prober status` shows how each claim's rows ended** at their last replay, beside its pairs, and the +findings rows skip for. + +## Consequences + +- Replayed on the same commit with an empty state, every probe passes. C03 passes 345 of 345 rows and C05 + 347 of 347; BaseProbe 107 of 346. Uncovered pairs fell from 2049 to 59 for C03, from 2518 to 79 for C05, + and from 4093 to 1737 for C07. The other probes' outcome counts are unchanged, row for row, which checks + that the catalogue kept each note's outcome. +- Seeded and caught: a document transformer that throws only with `AddTypeKit()` fails 16 of the first 32 + rows of BaseProbe and C03 with "ASP.NET alone writes it"; under the previous harness the same seed left + every BaseProbe row green. A key removed from the catalogue fails its rows as harness errors. A skip for + a finding marked fixed fails the replay. The prober's own tests hold the gate's refusal, the stale skip + and the status counts. +- A note's outcome is a reviewed line of code, so a new excuse is visible in a diff rather than in a + string. Rows that skip for any reason are still credited nothing; only how they are reported changed. +- `.claude/probe-axes.md` tells a probe writer the same rules. diff --git a/design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md b/design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md new file mode 100644 index 00000000..32f62673 --- /dev/null +++ b/design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md @@ -0,0 +1,96 @@ +# 0100. Every probe is shown to fail against a seeded defect + +Date: 2026-10-15 +Status: accepted + +Context: 2026-10-15, an outside review of the prober. Every fix and every new test here is proven by +reverting it or by seeding the defect it claims to catch ([verify-the-guard](../../.claude/skills/verify-the-guard/SKILL.md), +AGENTS.md "Workflow" step 3). The probe corpus had no such step: no probe had ever been shown to fail +against a defect in the product. A probe whose checks all end as skips, refusals or a comparison of the +product with itself passes on every commit and earns its claim's pairs, and nothing would notice. The +review's first items made a row's outcome exact ([decision 99](0099-a-probe-row-says-how-it-ended.md): a +closed catalogue of notes, a row that earns nothing ends as a skip); this record is the step after it: each +probe proves that it can fail. + +## Decision + +**A seed per registered probe.** A probe's entry in `tools/probe/corpus/registry.json` names one seeded +product defect: + +```json +"seed": { "patch": "seeds/L01-json-bytes.patch", "defect": "A dictionary key is written with ToString(), not the wrapped type's key converter and DictionaryKeyPolicy." } +``` + +`patch` is a unified diff, relative to the corpus directory, against the product files only (the replay's +`include`, `src/` here). `env` is an environment toggle set for the seeded build and every run; the product +reads none today, and none is added to shipping code for this. `defect` says, in one sentence, what the +seed breaks. A seed is the defect the claim is about, one a consumer would meet: a facet dropped, a refusal +that quotes its input, a rule skipped. It is not part of the probe's identity, so writing or changing one +stales no result. The schema is `tools/Prober/Schemas/registry.schema.json`. + +**`prober seeds [--probe P]`.** For each probe that is not retired, it applies the seed in a scratch +worktree of its own (`seeds`, beside the replay's `wt`, under `PROBE_CACHE_DIR`), builds, runs the probe +in every one of its process variants, reads how each row ended, and undoes the seed. The seed is **caught** +only by a row that ends as a `violation` and does not in the unseeded probe. The unseeded rows come from +the ledger where the probe's evidence is current, else from an unseeded build and run first. A harness +error, an inconclusive row or a skip is not a catch: it says the harness broke, not that the claim's check +fired. Every other end fails the command (exit 1): a seed **missed**, a probe with **no seed**, a seed that +**does not apply** (gone, outside the product files, or its lines moved), and a seeded **build failed**, +which proves nothing about the probe. Nothing a seeded run produces is recorded as evidence or as a +finding; the command writes `seeds-last.json` (`Schemas/seeds-run.schema.json`) and takes no lock. It runs +in neither the checkout nor the replay's worktree. + +**Local, not CI.** It starts no model, so [decision 92](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) +would allow it in CI, but it builds once per seed. It runs after a probe is written or its harness changes, +and before a release. CI holds the part that needs no build: `SeedsTests` in the prober tests fails while a +registered probe has no seed, or a seed's patch touches anything outside `src/` or no longer applies. + +**Pairwise coverage is a floor, not a guarantee.** The gate's measure, every pair of a claim's axes covered +by a row that passed, says that each combination of two settings ran once. A defect that needs three +settings at once (a value object over a decimal, as a dictionary key, under a naming policy) is in the +covering array only by chance, and a pass says nothing about the rows the array never generated. A seed +shows that a probe can fail; it does not show that the probe covers its claim. Where a defect is known to +live in a class of input, the class is tested over its variant catalogue ([decision 95](0095-a-fix-is-tested-across-its-input-class.md)), +every entry rather than every pair, and that is where combinations of three or more settings are +covered. + +## Consequences + +- Registering a probe takes a seed: `prober register ... --seed seeds/.patch --defect "..."`, and + `.claude/probe-axes.md` tells the writer how to make one. +- A product change that moves a patched line fails `SeedsTests` until the seed is made again against the + new code. That is the cost of a seed that keeps proving something. +- A seed is itself a claim about the product, and is measured like one: the first seed written for + E01 (a nullable value object left unmapped when the plugin does not unwrap `Nullable`) was missed, + because EF unwraps `Nullable` before it asks a type mapping plugin. The defect was not real, and the + command said so. + +## Evidence + +Measured 2026-10-15 on macOS (arm64) with the pinned SDK, at 168ac2a (`fix/probe-soft-outcomes` at 8ee899d +with this change), from an empty state: one unseeded build (21 s) and run of all 11 probes, no violation in +any of them, then one seeded build and run per probe, 553 s in all. + +| Probe | Claim | Seed (file under `src/`) | Rows violating / run | +|---|---|---|---| +| `BaseProbe` | C07 | the value object's schema drops the wrapped type's `format` (`ValueObjectSchemaTransformer.cs`) | 52 / 346 | +| `C03ComposedProbe` | C03 | a composed fault's statuses listed descending (`FaultResponsesOperationTransformer.cs`) | 94 / 345 | +| `C05RuleProbe` | C05 | an enum rule named without its nesting chain (`Fault.cs`) | 63 / 347 | +| `E01ColumnParityProbe` | E01 | an enum value object mapped as a text column (`ValueObjectTypeMappingSourcePlugin.cs`) | 6 / 90 | +| `E02ValueTranslatesProbe` | E02 | `.Value` on a column translated as a cast (`ValueObjectTranslators.cs`) | 81 / 108 | +| `E04IntegerKeyProbe` | E04 | an integer key declared never generated (`ValueObjectKeyValueGenerationConvention.cs`) | 5 / 28 | +| `E05OwnCompareToProbe` | E05 | a hand-written ordering not refused in a query (`HandWrittenOrderingInterceptor.cs`) | 66 / 90 | +| `L01JsonBytesProbe` | L01 | a dictionary key written with `ToString()` (`GeneratedJsonPlan.cs`) | 6 / 289 | +| `L02NoQuoteProbe` | L02 | parsing through the wrapped type's `Parse`, which quotes the input (`GeneratedParsing.cs`) | 92 / 296 | +| `L03OneRuleProbe` | L03 | the text entries build a validated value object without `Create` (`ValueObjectAspect.Templates.cs`) | 56 / 296 | +| `L04RoundTripProbe` | L04 | formatting in the current culture, not the provider (`GeneratedFormatting.cs`) | 15 / 296 | + +The first run, on `release/2.2.0` at 6e630d6, caught 9 and failed on two misses, which is how both seeds +were found wrong: E01's (above) and L02's, which changed only the string `TryParse` while the generated +`Parse` of most wrapped types goes through the span overload. Seeding that overload too caught 92 rows. + +The command's tests (`SeedsTests`) were shown to catch each of these, seeded one at a time in +`Seeds.cs` and `Ledger.cs`: the baseline ignored (a violation the probe already had counted as the +seed's), the seed left in the worktree, a patch outside `src/` accepted, a missed seed exiting 0, the seed +made part of the identity, a probe without a seed counted caught, and a seeded build failure ignored. The +repository check fails when one registered probe's seed is removed. diff --git a/design/adr/0101-ci-replays-by-family-and-footprint.md b/design/adr/0101-ci-replays-by-family-and-footprint.md new file mode 100644 index 00000000..c263b7e4 --- /dev/null +++ b/design/adr/0101-ci-replays-by-family-and-footprint.md @@ -0,0 +1,121 @@ +# 0101. The build workflow replays the probes in legs by family, in parallel and under a time limit: every probe on a push, the stale ones on a pull request against the base branch's saved state + +Date: 2026-10-16 +Status: accepted + +Context: 2026-10-16, the probe gate round for 2.2.0 (pull request #20) took the corpus from 11 registered +probes to 43, 68 processes with their variants. [Decision 96](0096-ci-replays-every-probe-and-the-release-runs-the-gate.md) +had chosen `replay --all` from an empty state on every push and pull request, one probe after another, each +with the coverage extension on and a report written, measured at 287 s for 7 probes, and said to "revisit +this when `replay --all` on a runner takes longer than the test job's path to green". On pull request #20 +the probe replay took 26 min 50 s on `ubuntu-latest`, against 3.5 to 7.7 min for the test legs and 13 min for +the rest of the run; locally 1,039 s. No `timeout-minutes` was set, so a hung replay would have run to the +six-hour default. The concept of the same day on agentic issue finding (H14) named the four costs: serial, +coverage-instrumented, stateless, unbounded. The coverage is needed only for the footprint, which a run from +an empty state records and nothing reads. + +## Decision + +**The replay runs its probes in parallel where it records no footprint.** `prober replay` starts every +variant of every selected probe at once and runs them `--parallel N` at a time, one per core by default, and +records each probe in registry order once its variants have ended, so the transcript and the state files read +as before. A replay that records a footprint runs one host at a time whatever `--parallel` says: the coverage +collector instruments the assemblies on disk, and hosts started together from one output directory corrupt +each other ([KB-025](../knowledge/KB-025-the-coverage-collector-rewrites-assemblies-on-disk.md)); its +in-memory mode avoids that and records nothing the footprint can read. The verdict tool's test mode takes +`--no-build` and the replay's test template uses it: the replay builds once, and two builds of one project at +once would race in its `obj` directory. Each verdict process now writes and reads a log of its own and +publishes the shared one when it ends (six at once had all reported the last one's duration), and a failed +run's full log is kept under the state's `logs/`, so CI's `probe-state` artifact holds it. + +**The build workflow splits the corpus into legs by family.** The replay configuration of +`.claude/probe-axes.md` gains `families`, a name to the surfaces it holds (`http`, `ef`, `json`, `text` with +`logging`, `build` with `generation`, `analyzers` and `option-result`). A probe's family is its claim's first +surface's. `replay --family F` runs one family and refuses while any probe of the registry has a surface in +no family, so a matrix cannot leave a probe out quietly. The `probes` job of `dotnet.yml` is a matrix over +the families, `fail-fast: false`, and `ReleaseWiringTests` holds the matrix to exactly the families the axes +file names. A `probe-status` job follows the legs always, joins their states with `prober merge --from`, +which copies each leg's evidence, adds its run, footprint and finding lines where the state lacks them (a +line a leg carried over from a restored state is added once), refuses while a registered probe was neither +replayed nor skipped by any leg, and fails when a leg failed; then it reports `prober status` in the job +summary. Both jobs carry `timeout-minutes` (60 a leg, 15 the merge). + +**A push replays every probe and saves the state.** On a push to `main` or a `release/**` branch each leg runs +`replay --all` of its family from an empty state, with coverage, so every footprint is fresh; the merged state +goes into the Actions cache under `probe-state--`. A red push saves nothing: the save step runs +only after a merge that passed. + +**A pull request replays what it stales, against the base branch's saved state, without a footprint.** Each +leg restores the newest `probe-state--` entry, takes the base branch as its ledger branch +(`PROBE_BRANCH`), and runs `replay` of its family without `--all`: the probes whose identity (the corpus +sources, the rows, the axes file, the build environment, the tool) or product footprint the branch changed, +and `--no-footprint`, which leaves the coverage extension and the report out and records no footprint, since +nothing of that state is kept. With no saved state (the first run, an evicted entry, a base that has not been +green since the corpus changed) every probe runs, as from an empty state. The legs' states are uploaded and +merged as on a push; nothing is saved. + +**The release is unchanged.** `release.yml` keeps `replay --all` from an empty state and the gate +([decisions 96](0096-ci-replays-every-probe-and-the-release-runs-the-gate.md) and +[97](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md)); the replay now runs its probes in parallel +there too. + +**Why a carried state is acceptable now.** Decision 96 refused it because the footprint sees only what ran +under coverage in `src/` and a fixed list of compile-time paths, so a dependency it does not see skips a probe, +and because "the check of record reads the tracked corpus and the commit alone". Both still hold for the +check of record: the push after a merge and the release replay everything from an empty state. The pull +request's replay is the fast check on what the branch touched; what it cannot see, the push catches on the +merge commit and the gate before a release. The roadmap's harness work adds a declared `watched` list per +probe, which widens what stales a probe. + +**Held by tests.** `ReplayTests` in the prober tests run the replay against a fake adapter: a family replays +the probes whose claim's surface it holds; a probe whose surface is in no family refuses the replay; an +unknown family or a bad `--parallel` is a usage error; a replay without a footprint records evidence and no +footprint and leaves its probes stale; probes run as many at a time as asked and are recorded in registry +order; `merge` joins the legs, adds a carried line once, refuses a probe no leg saw and fails when a leg did. +`ReleaseWiringTests` holds the workflow: the matrix lists every family of the axes file, the legs and the +merge job have a time limit, the push replay is `--all` of the family leg, the pull request replay is the +family leg without `--all` and with `--no-footprint`, the base state is restored on pull requests only and by +the base branch's prefix, the replay and the merge end their steps with their own exit code, the merge job +follows the legs always, downloads before merging, reports after, and saves the state on a push only. + +## Consequences + +- A pull request's probe job takes minutes where it took the better part of an hour, and fails on what the + branch touched. A pull request that does not touch a failing probe's footprint is green while the base + branch is red; the push after the merge shows the red, as the base already does. +- Every probe's identity changed with this decision (the axes block holds the replay configuration), so the + first push replays everything, as it would anyway. +- A push's legs run their probes one at a time, since they record footprints; the five legs run at once, so the + push replay is bounded by its largest family (`http`, 13 probes, 26 processes). A pull request's legs run + their probes as many at a time as the runner has cores. +- The parallelism of a push's legs is the next thing to buy if the push replay grows long: a copy of the + output directory per run slot would let the collector instrument each copy alone, at the cost of the copies. + +## Evidence + +Measured 2026-10-16 on macOS (arm64, 16 cores) at 4b7f3f9 (43 probes, 68 processes), in the warm replay +worktree, while other builds ran on the machine: + +| Replay | Build | Probes | Result | +|---|---|---|---| +| serial, with coverage, at 00d3b8f (the gate round's measurement) | 35 s | 1,039 s | 40 passed, 3 failed: BaseProbe, C12, C14 | +| `--parallel 4 --no-footprint` | 3 s | 458 s | the same 40 and 3 | +| `--parallel 4`, with coverage (before the one-at-a-time rule) | 3 s | 428 s | 29 passed, 14 hosts exited 7; a second run 56 | +| `--parallel 16`, with coverage (before the rule) | 53 s | 122 s | 1 passed, 42 hosts exited 7 having run zero tests | + +The crashed hosts' logs, kept by this change, show the collector's instrumentation racing between hosts +(KB-025). With the in-memory mode of the collector eight hosts of three probes ran at once without a crash, +and every coverage report was empty of `src/`. + +On `ubuntu-latest` (4 cores): pull request #20's serial job, 26 min 50 s. This decision's own pull request, +where the base branch had no saved state yet, so every probe ran in its family's leg without a footprint, and +the first push after it, every probe with coverage one at a time per leg: see the pull request's job times, +recorded in the changelog of the next release. + +The seven prober tests and the convention test were each shown to fail against a seeded defect: the family +filter ignored (3 tests fail), a probe in no family not refused, `--no-footprint` still asking for coverage, +`--parallel` ignored, the merge accepting a probe no leg saw, the merge passing with a failed leg, the merge +adding a carried line twice; and in the workflow, the time limit dropped from the legs or moved onto a step, +a family dropped from the matrix, the push replay without `--all`, the pull request replay with coverage, a +replay or a merge that cannot fail its job, the state saved from a pull request, the merge job skipped when a +leg fails, the base state restored on pushes, and the pull request not taking the base branch's state. diff --git a/design/adr/0102-an-exclusion-carries-its-reason.md b/design/adr/0102-an-exclusion-carries-its-reason.md new file mode 100644 index 00000000..86a143f4 --- /dev/null +++ b/design/adr/0102-an-exclusion-carries-its-reason.md @@ -0,0 +1,92 @@ +# 0102. An exclusion carries its reason, claims.json is held to its schema, and the prober writes JSON with only JSON's own escapes + +Date: 2026-10-16 +Status: accepted + +Context: 2026-10-16, after the probe gate round for 2.2.0 (pull request #20), whose four agents wrote 18 +probes. A claim in `tools/probe/corpus/claims.json` drops an axis by quoting the sentence that limits it +(`narrow`, checked against the document), and drops pairs no row can hold by listing them (`exclude`, checked +by nothing). The schema, `tools/Prober/Schemas/claims.schema.json`, had no field for why a pair is excluded, +and the prober read claims.json without checking it against the schema at all. Three agents each invented a +field of their own for the reason: `excludeReasons` as a list of `{match, reason}`, `excludeReasons` as an +object keyed by its match, and `excludeWhy` as a list of `{pairs, reason}`, each naming its pairs in prose. +Nothing read any of them, so a reviewer reading `prober universe` saw how many pairs a claim had, never what +it left out or why. And `prober publish` rewrote claims.json through `Files.WriteJson`, whose default encoder +writes `+`, a backtick, `<`, `>` and `'` as six-character escapes: the 1,574 pairs and the reasons are full of +them (`Option`, `1E+2`, code in backticks), so a publish turned every such character into an escape and +every agent edited the file by hand instead. + +## Decision + +**An exclusion is a reason and its pairs.** Each entry of a claim's `exclude` is an object: + +```json +{ "reason": "A closed set wraps nothing (Generators README#closed-sets), so it has no shape.wrapped.", + "pairs": [["shape.kind=closed-set", "shape.wrapped=Guid"], ["shape.kind=closed-set", "shape.wrapped=int"]] } +``` + +`reason` is one sentence saying why the pairs cannot exist or are not promised, citing the docs sentence, the +record or the KB fact where one applies; it is required and may not be blank. `pairs` lists the pairs, at least +one, each two `axis=value` settings, as before. `narrow` is unchanged. A pair's effect is what it was: the +covering array never asks for it and the gate never counts it. + +**The prober holds claims.json to its schema.** The schema says `additionalProperties: false` for a claim and +for an exclusion, so `excludeReasons`, `excludeWhy` and any other field nobody reads are refused. The prober +checks every claims file where it reads one (the corpus's and the branch's staging, for `universe`, the gate, +and both sides of `publish`) and refuses with each problem named by its claim and field +(`claim C20-testing-shouldhold: exclude[0].reason is required`). The check (`SchemaCheck`) reads the schema +embedded in the dll and knows the keywords the schema uses; a keyword it does not know is an error, never a +pass. + +**`prober universe` shows what nothing checks mechanically.** Under each claim's line it prints the axes the +claim narrows, with their quotes, and each exclusion with its pair count, its first pair and its reason. +`universe.json` and `status --json` do not change: they carry the pairs, not the reasons. + +**The prober writes JSON with only JSON's own escapes.** `Files.WriteJson` uses +`JavaScriptEncoder.UnsafeRelaxedJsonEscaping` and a `\n` line end on every OS. `+`, backticks, `<`, `>` and `'` +stay literal, and a tracked file in the form `publish` writes is unchanged by a publish that changes nothing in +it. The relaxed encoder is unsafe only for JSON embedded in HTML or a script, which none of these files is; no +reader parses them as anything but JSON, and a probe's identity hashes the canonical form, not the file's text, +so no identity changes. + +## Consequences + +- An agent writes an exclusion with its reason, in the one field, and `prober universe` is where a reviewer + reads it. A reason the round invents somewhere else fails the next `universe`, `gate` or `publish`. +- The migration is done: a one-off file-based app, run once and deleted, put each of the 51 invented reasons + over the pairs its prose named and wrote the two reasons C20 lacked. claims.json is now in the form `publish` + writes, 7,612 lines (3,771 before: most pairs had been folded onto one line by hand), and a test in the + prober tests holds both the schema and the form for the tracked file. +- A schema the prober reads at load can no longer say more than is checked: a new keyword in it needs + `SchemaCheck` to learn it first. Only claims.json is checked this way today; the other schemas stay + documentation until a file of theirs is found carrying a field nobody reads. + +## Evidence + +Measured 2026-10-16 on macOS (arm64) with the pinned SDK, on `claude/loving-napier-535af5` (pull request #22's +branch with release/2.2.0 merged in, d8b7cd7): + +| | Count | +|---|---| +| Claims | 42 | +| Claims with `narrow` | 22 | +| Claims with `exclude` | 19 | +| Excluded pairs | 1,574 | +| `excludeReasons` as a list of `{match, reason}` | 4 claims, 18 reasons | +| `excludeReasons` as an object keyed by its match | 9 claims, 15 reasons | +| `excludeWhy` as a list of `{pairs, reason}` | 5 claims, 18 reasons | +| Claims with `exclude` and no reason anywhere | 1 (C20, 3 pairs) | +| Exclusions after the migration | 53 (51 migrated, 2 written for C20) | +| Escapes in claims.json before (`'` 25, `+` 3, `<` 14, `>` 21, backtick 92) | 155, on 38 lines; 0 after | + +- Every excluded pair of every claim is in exactly one exclusion after the migration, and the set of pairs per + claim is the one before (compared per claim, sorted). Everything else in every claim is unchanged. +- `prober universe --base origin/main` printed the same line per claim, `universe: 42 claims, 227285 pairs`, + from the tool before this change over the old claims.json and from the tool after it over the new one, and + the two `universe.json` files hold the same axes and the same excluded pairs per claim. +- A `publish` of a staging holding the migrated claims.json unchanged left the corpus's claims.json byte for + byte as it was. +- The new tests in `ClaimsFileTests` were each shown to fail with their defect seeded: `reason` not required, + a blank reason allowed, a claim's or an exclusion's fields left open, the check's result ignored, the + default encoder, the exclusion or the narrowing line left out of `universe`, and an unknown schema keyword + passed over. diff --git a/design/adr/0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md b/design/adr/0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md new file mode 100644 index 00000000..6c17fe0a --- /dev/null +++ b/design/adr/0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md @@ -0,0 +1,98 @@ +# 0103. A harness error is never a catch, and note keys are constants the compiler checks + +Date: 2026-10-16 +Status: accepted + +Context: 2026-10-16, the round that gave the last 14 probes their seeds (`87f797f`). E11's seed, `UseTypeKit()` +ordered after the provider, was reported caught on 14 of 14 rows, but every one of those rows had ended by +an exception the check did not expect, not by the order differential firing. The probe was fixed so that +each order's run returns its refusal as a result, and the seed was then caught by the check itself. The +miscount was not E11's. [Decision 100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md) says a +harness error is no catch, and two places counted one anyway: + +- **`Rec.Run` made a thrown exception a violation.** Its catch branch raised `harness-error` and kept the + exception's text as the failure, and a non-null failure then raised `violation` too. `violation` ranks above + `harness-error` in `Evidence`, so the row ended as a violation. +- **`prober seeds` read the category alone.** It counted every row that ended as a `violation` and not in the + unseeded run, and did not look at the reasons, so a row that also raised a harness error counted. + +The same review looked at the note keys of [decision 99](0099-a-probe-row-says-how-it-ended.md). `NoteKeys` +was a dictionary of string keys, and each check called `r.Note("some-key", detail)`. A misspelled key compiled, +and came up only as a harness error after a build and a replay. Five probes had built keys from text, and +three of those keys had fired. + +## Decision + +**An exception is a harness error and nothing else.** `Rec.Run` (`harness/Row.cs`) records the exception as +`harness-error` and does not raise `violation`. The row's category stays `harness-error`, and the test still +fails with the exception named (`UNEXPECTED InvalidOperationException: ...`). A check that returns a failure +still raises `violation`. + +**`seeds` credits only a clean violation.** A seed is caught only by a row whose category is `violation`, whose +reasons carry no `harness-error`, and which does not violate unseeded. A missed seed's detail counts the rows +that ended in a harness error, "which is no catch". + +**Note keys are a typed catalogue.** `NoteKeys` is a sealed class with a private constructor. Each of its +`public static readonly` members is one key, with its spelling and its outcome on the same line, and that line +is the only place the key is declared. `Row.Note` takes a `NoteKeys`, and the overload that took a string is +gone. A check writes `r.Note(NoteKeys.Compared, detail)`. A misspelled member, a string literal and a key +built from text all fail to compile, for example `CS0117: 'NoteKeys' does not contain a definition for +'Comparde'`. + +A static class of `const string` members was weighed and refused. With it, `Note` would still take a string, +so `r.Note("msg:" + x)` would compile, and only a lint could catch it. An enum would need an attribute for +the outcome and another for spellings such as `schema:compared-with-nullable-wrapped`, and reflection to read +both. A class whose only instances are its members carries both values and lets the compiler hold the +whole rule. The spellings did not change, so notes and reasons read as before. + +**`Skip` stays a string, and is read before a build.** `r.Skip("F7", why)` names a finding id, which no +compile-time catalogue holds. `prober register` and `prober publish` scan the staged harness and probe +sources before they do anything else, blanking out comments and string literals first. They refuse, +naming the file and the line, where: + +- a `Note` call's first argument is not `NoteKeys.Name` for a member the corpus or the staging declares; +- a two-argument `Skip` call (the harness's, not LINQ's) has a first argument that is not a plain string + literal; +- that literal names a finding that this ledger does not hold open: one it never saw, one fixed in HEAD's + history, one withdrawn, or one the corpus dismissed. + +The prober references the base class library only ([decision 92](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md)), +so this scan reads text, not syntax. `ProbeNoteKeyTests` in the conventions tests holds the same shape over the +tracked corpus with Roslyn's syntax tree: a `Note` call's first argument is a member of `NoteKeys`, and a +two-argument `Skip` call's first argument is a string literal. A positive control and a floor of 130 note +calls keep the test from passing on a walker that finds nothing. + +## Consequences + +- Every harness and probe source changed, so every probe's identity changed, and the next replay runs them all. +- A seed that only makes a check throw is now reported missed. That is the outcome decision 100 asked for: the + probe is fixed until its own check fires, as E11's was. +- A new key goes into `NoteKeys` with its outcome, on one line. A probe in another namespace imports the + catalogue with `using NoteKeys = Probes.Shapes.NoteKeys;` (C09, C18 and C19 do), because the rule reads the + name `NoteKeys` and nothing more. +- `HarnessErrorTests` in the prober tests compile `harness/Row.cs` in. They run the real `Rec.Run` on a + throwing check, read the line it writes, and feed lines written that way to `seeds` through an adapter + that runs the harness in-process. + +## Evidence + +Measured 2026-10-16 on macOS (arm64) with the pinned SDK: + +- **The catalogue.** 63 keys: 25 pass, 20 inapplicable, 17 expected refusal and 1 inconclusive. All of them are + used. There are 136 `Note` calls in 30 files, 14 in the harness (`Row.cs` among them) and 16 in the probes. 127 calls passed a + string literal. Six passed a local constant, `Alone` in `TextRow.cs`, and one a conditional between two + literals, now an `if`. The last one is `Row.Substituted` itself. No `Skip` call names a finding in the + corpus today. +- **Reverting the `Rec.Run` change alone** fails two of the four `HarnessErrorTests`. One fails with "should + be "harness-error" but was "violation"". The other expects the seed to be missed with "harness-error 1, + pass 1; 1 ended in a harness error, which is no catch", and fails because the row it reads is now a + violation. +- **Reverting the `seeds` filter alone** fails `A_violation_whose_reasons_carry_a_harness_error_is_no_catch`. + Its seed is reported "caught, 1 of 2 rows violate; e.g. #1: violation:and failed anyway". +- **A probe with `r.Note("msg:" + x, ...)`.** Put in the corpus, it fails `ProbeNoteKeyTests` with + `tools/probe/corpus/probes/ZzSeedProbe.cs:7: Note("msg:" + x, ...) does not name a key of NoteKeys`. Staged, + it is refused by `prober register` and by `prober publish` with the same line. +- **The rule switched off.** With the scan's structural rule off, `SourceLintTests` fails. With the + conventions rule accepting any expression, its positive control fails. +- **A misspelled member** in a probe, `NoteKeys.Comparde`, fails the Samples build with CS0117. The corpus as + converted compiles in the Samples. diff --git a/design/adr/0104-a-claim-states-its-obligations-and-each-has-a-seed.md b/design/adr/0104-a-claim-states-its-obligations-and-each-has-a-seed.md new file mode 100644 index 00000000..ea2d85de --- /dev/null +++ b/design/adr/0104-a-claim-states-its-obligations-and-each-has-a-seed.md @@ -0,0 +1,99 @@ +# 0104. A claim states its obligations, and each has a seed + +Date: 2026-10-16 +Status: accepted + +Context: 2026-10-16, the first workstream of the concept for finding issues with agents, which read the probe +corpus claim by claim against what each probe checks. C07 (`C07-fault-responses-blanket`, the AspNetCore +README's "Every fault set at once") had one registered probe when +[decision 100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md) gave every probe a seed, and that +probe, `BaseProbe`, never checked the claim's central promise: that one call maps every fault set. Its seed +dropped the wrapped type's `format` from a value object's OpenAPI schema, which `BaseProbe` caught, so the probe +passed, it could be shown to fail, and the corpus's count of passing probes had an unchecked claim inside it. +A seed that breaks any assertion of a registered probe shows that the probe can fail. It does not show that the +claim is checked. Decision 100 asked for a seed per probe; this record asks for one per obligation, a thing the +claim promises that a probe can check on its own, and for the claim to say in the corpus what those are and +what it does not promise. + +## Decision + +**A claim lists its obligations.** An entry of `tools/probe/corpus/claims.json` takes `obligations`, each an +`id` (`^[a-z0-9][a-z0-9-]*$`), a `sentence` quoted verbatim from the claim's source document, and, where the +obligation is not the whole claim, the `axes` values it concerns. A claim without the field has one obligation, +the claim itself, under the id `claim` (`Claim.ImplicitObligation`). + +**A claim lists what it does not promise.** `not-promised` takes one entry per intended difference: an `id`, the +`source` (`file#heading`) of the document or record that makes it intended, its `sentence` quoted verbatim, and +the `axes` values it concerns. A note of outcome expected-refusal, a narrowing or an applicability condition +cites an entry by its id. + +**Every quote is checked as a narrowing's is.** `Axes.Build`, which `universe`, `gaps` and every command that +builds the universe call, collapses whitespace and requires each obligation's sentence in the claim's source and +each not-promised sentence in the entry's own source. A sentence the document no longer holds is a refusal that +names the claim and the id (`claim C1: obligation keys quotes a sentence not found in README.md`), never a stale +promise, and so is an id listed twice. The schema (`Schemas/claims.schema.json`) holds the form, as +[decision 102](0102-an-exclusion-carries-its-reason.md) made it hold an exclusion's. + +**A seed per obligation.** A registry entry takes `seeds`, one per obligation its probe checks, each a seed as +decision 100 defines it (`patch`, `env` or both, and `defect`) with `obligation` naming the id it breaks. `seed` +may name an obligation too. A seed that names none proves the probe, and counts for the implicit obligation +`claim` only, so for a claim without `obligations`. `prober seeds` runs each seed of a probe on its own, judges +it as decision 100 and [decision 103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md) say, +and reports it as `P/` in its output and in `seeds-last.json`. Its check without a build +(`Seeds.Problems`, which `SeedsTests` runs over the repository) refuses a probe without a seed, a seed that does +not apply, a seed naming an obligation its claim does not list, and a listed obligation of a claim with probes +that no seed of them names. + +**`status` shows the obligations.** `prober status --json` gives each claim its `probes` and its `obligations`, +each with `seeded_by`, the probes whose seeds name it. The text form ends each claim's line with +`obligations N, each seeded` or `obligations N (K without a seed: ids)`. + +**`gaps` reports what the corpus leaves out.** `prober gaps [--base REF]` prints, each under its heading with its +count and its recorded reason: the generated settings no surface reaches and nothing ignores, the settings +without values, the declarations the generators refuse (`shape.refused-kind`), the values the frameworks refuse +on their own (`refused`), every not-promised entry and narrowing by what it concerns, and the surfaces the axes +file's new `out-of-reach` object declares, each with why no probe reaches it. A heading with nothing says +`none`. It reads the tracked corpus and the generated axes only, runs no model, and decides nothing: it is the +list a release checklist and a feature-research round read, and no command fails on it. + +## Consequences + +- The work that follows is the walk over every claim, family by family: list its obligations from its source, + move each intended difference into `not-promised`, and give each obligation a seed in the probe that checks it, + or a probe where none does. Until a claim is walked it keeps the implicit obligation, which every probe with a + seed already meets, so this record fails no current probe. +- A claim's obligations are the gate's next unit after pairs. Today the gate counts pairwise coverage per claim; + an obligation without a seed is visible in `status` and refused by the check without a build only for a claim + that lists it. +- `out-of-reach` starts with PostgreSQL (the EF probes run SQLite in memory) and Native AOT (measured by the + consumer smoke test only). + +## Evidence + +Measured 2026-10-16 on macOS (arm64) with the pinned SDK, on `build/obligations-per-claim`: + +- **The tests.** `ObligationsTests` and `GapsTests` in `test/CodoMetis.TypeKit.Prober.Tests`, 15 cases, run the + commands in-process on a scratch repository, `prober seeds` with a fake adapter whose row N violates while the + seed's `SEED_ROW` is N, and compare output with line endings normalised, so Windows runs them too. +- **Each test fails against its seeded defect,** one line of the prober's source changed at a time: + + | Seed | Fails | How | + |---|---|---| + | `Axes.Build` drops the refusal of an obligation whose sentence its source does not hold | `An_obligation_quoting_a_sentence_its_source_does_not_hold_is_refused` | `universe` exits 0 | + | the same for a not-promised entry against its own source | `A_not_promised_entry_is_checked_against_its_own_source` | `universe` exits 0 | + | `Axes.Build` drops the refusal of an obligation id listed twice | `A_duplicate_obligation_id_is_refused` | `universe` exits 0 | + | `Claim.ObligationIds` returns the listed ids alone, without the implicit `claim` | `A_claim_without_obligations_has_one_the_claim_seeded_by_a_seed_naming_none` | `status --json` gives C1 no obligation | + | `status` drops the obligations from a claim's line | `Status_shows_the_obligations_no_seed_breaks`, both line ends | the line ends at `C1: 0/4` | + | `Seeds.Problems` adds nothing for a listed obligation no seed names | `The_check_without_a_build_names_an_obligation_no_seed_breaks` | no problem where `nulls` was expected | + | `Seeds.Problems` drops the check that a named obligation is listed | `The_check_without_a_build_names_a_seed_naming_an_obligation_the_claim_does_not_list`, both cases | no problem for `kyes` | + | `seeds` runs only a probe's first seed (`seeds.Take(1)`) | `Seeds_runs_each_seed_under_its_obligation` | exit 0: `P/nulls`, the missed seed, never ran | + | `gaps` drops its "out of reach" section | both `GapsTests`, all four cases | the heading and its entry are missing | + | `gaps` prints a count of 0 where it said `none` | `A_corpus_without_gaps_says_none_under_each_heading`, both line ends | `out of reach: 0` and the like | + +- **`prober gaps` on this branch** prints, at `776c9f6`, for the tracked corpus (the axes enumerated at + `JsonSerializerOptions` 10.0.0.0, `OpenApiOptions` 10.0.12.0 and `MvcOptions` 10.0.0.0): no setting that no surface reaches, no setting without values, 4 declarations the + generators refuse, 3 values the frameworks refuse, 64 narrowings over 22 claims and no not-promised entry yet, + and 2 surfaces out of reach. `prober status --json` gives each of the 42 claims, all with probes, its one + implicit obligation `claim`, and every one is seeded, so `Seeds.Problems` over the repository stays empty. +- **A slip found on the way.** `seeds-last.json` gained `obligation`, which `Schemas/seeds-run.schema.json` did + not list under its `additionalProperties: false`; the schema now has it, a string or null. diff --git a/design/adr/0105-every-tool-is-built-in-the-session-s-checkout.md b/design/adr/0105-every-tool-is-built-in-the-session-s-checkout.md new file mode 100644 index 00000000..cef4868d --- /dev/null +++ b/design/adr/0105-every-tool-is-built-in-the-session-s-checkout.md @@ -0,0 +1,92 @@ +# 0105. Every tool is built in the session's checkout, with a dotnet that has the pinned SDK + +Date: 2026-10-17 +Status: accepted + +Context: 2026-10-17, an analysis of the agent sessions of 2026-10-15 and 2026-10-16, the first two days of the +tooling [decision 92](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) ported to C#. + +The port worked: raw `dotnet build` and `dotnet test` calls fell from about 1,000 a day to none within an hour +of the guard, and the verdict replaced the hand-written greps of German MSBuild output that 83% of build and test +calls had carried. Two things did not: + +- **Tools missing in worktree sessions.** 32 calls in 30 sessions failed because `build-verdict.dll`, + `prober.dll` or `docs-query.dll` was not there, every one in a session the desktop app had started in a + worktree, and agents then built the tools by hand about 113 times. The SessionStart hook built only the guard + and the verdict tool, and built them in `$CLAUDE_PROJECT_DIR`, which in a desktop worktree session is the main + checkout, not the worktree the session works in (Claude Code's worktree documentation: "Hook paths don't + follow the worktree"). +- **Session-start builds that failed unseen.** 70 of about 100 session starts reported "A compatible .NET SDK + was not found" for both builds. On the maintainer's Mac a hook's PATH, unlike a login shell's, puts Homebrew's + `dotnet` (SDK 10.0.401) before the installer's (`/usr/local/share/dotnet`, 10.0.402), and global.json pins + 10.0.402 with `latestPatch`. Homebrew's formula stays because PowerShell depends on it. A SessionStart hook + that exits non-zero shows the user a notice and the agent nothing, so a session went on without its tools, + and in a worktree without its guard: a PreToolUse hook whose dll is missing fails, and a failing hook allows + the call. The verdict tool already fell back to the installer's `dotnet`; the hook, the prober and the + smoke test did not, so agents prefixed `export PATH=/usr/local/share/dotnet:$PATH` to every call. + +The guard also refused `dotnet msbuild -getProperty:X -getItem:Y`, a question about a project that +the verdict tool has no form for; the agent that met it dropped the approach. + +## Decision + +**SessionStart builds every tool, each where it is run from.** Two commands: + +- the guard, in `$CLAUDE_PROJECT_DIR`, because the PreToolUse hook runs it from there; +- every other tool, through `tools/AgentTools.slnf`, by a path relative to the session's directory, so a + worktree session builds its own. The filter lists every tool project but the guard; `SessionStartHookTests` + discovers the projects under `tools/` and refuses one the filter leaves out. + +**A dotnet without the pinned SDK falls back to the installer's, everywhere a tool starts one.** Each hook +command builds with the `dotnet` on PATH, then once more with `/usr/local/share/dotnet` first on PATH, the SDK +installer's default location and the place the verdict tool already falls back to. `tools/Shared/Dotnet.cs` holds +the same rule for the tools: `dotnet --version` in the directory decides whether a dotnet has the pinned SDK, +the installer's (`%ProgramFiles%\dotnet` on Windows, or `TOOLS_DOTNET_FALLBACK_DIR`) is used when only it has, +and its directory goes first on the child's PATH and into `DOTNET_ROOT`. The fallback is started by its full path: +.NET looks for a bare `dotnet` beside the host that runs the tool before it reads PATH, so a tool Homebrew's +`dotnet` runs would start Homebrew's again. The prober starts every `dotnet` that way, the smoke test decides once +at its start, and the verdict tool takes its fallback location from it. When +neither dotnet has the SDK, the one on PATH stays, so its own error reaches whoever reads the output, and the +prober's toolchain refusal still says so. + +**A session start that builds nothing says so to the agent.** Each hook command ends in an `echo` naming what did +not build and the command that shows why, and exits 0: Claude Code adds a SessionStart hook's stdout to the +agent's context only on exit 0. This is the one place a hook holds more than a call to `dotnet`: three steps joined +by `||`, which `sh` on macOS and Linux and Git Bash on Windows, where Claude Code runs hooks, all read. + +**The guard lets a project query through.** `dotnet msbuild` with `-getProperty` or `-getItem` (as `-`, `--` or +`/`, any case) and no `-t`/`-target`, `-getTargetResult` or `-r`/`-restore` passes. Without a target MSBuild stops +after evaluation and writes nothing; with one it builds, and the guard refuses it as before. + +## Evidence + +Measured 2026-10-17 on macOS (arm64), Homebrew's `dotnet` first on PATH as a hook sees it: + +| Hook, in a new worktree | Result | +|---|---| +| guard, nothing built yet | Homebrew's fails (exit 155), the fallback builds it in 1.9 s, exit 0 | +| tools, nothing built yet | the same, all four built in 1.7 s, exit 0 | +| guard and tools, built | 1.7 s and 1.6 s, exit 0 | +| global.json pinning an SDK neither has | both commands print their message on stdout, exit 0 | + +`dotnet msbuild src/CodoMetis.TypeKit/CodoMetis.TypeKit.csproj -getProperty:TargetFramework +-getProperty:IsRoslynComponent` and `-getItem:ProjectReference` answered in 0.8 s each and wrote no file under +`obj/` or `bin/`. MSBuild's `XMake.cs` stops evaluation after the properties pass, or the items pass, when +`-getProperty` or `-getItem` comes without a target. + +Each guard was shown to fail against a seeded defect: the prober starting `dotnet` without the resolver +(`ToolchainTests.A_dotnet_without_the_pinned_sdk_falls_back_to_the_installers`, which runs the built prober +under a pin its own dotnet lacks, with a stub in the installer's place), the resolver never falling back, a child +left without `DOTNET_ROOT`, the guard refusing a query, the guard reading a query that runs a target as one, +the guard missing a `/getProperty` switch, the filter missing a tool, the tools built in `$CLAUDE_PROJECT_DIR`, +and a hook without the fallback. + +## Consequences + +- An agent no longer needs to prefix `PATH` for the tools, the prober's replay and the smoke test. A raw `dotnet` + in its own Bash command still resolves Homebrew's; that is the machine's PATH, which the repository does not + set. +- The installer's location is fixed in the hook text. On a machine whose installer put `dotnet` elsewhere the + fallback finds nothing, and the message says so. +- A worktree a session enters after its start (EnterWorktree) gets no session-start build; the tool's missing dll + names itself, and `dotnet build tools/AgentTools.slnf -c Release` builds them all. diff --git a/design/adr/0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md b/design/adr/0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md new file mode 100644 index 00000000..ab9f1c5e --- /dev/null +++ b/design/adr/0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md @@ -0,0 +1,70 @@ +# 0106. A parameter of a value object over a described type is written inline, never as its component + +Date: 2026-10-17 +Status: accepted + +Context: 2026-10-17, finding A3 of the code-analyst run of 2026-10-16 (AspNetCore): a value object over a type the +JSON options describe, such as `Money : IValue` under `DecimalStringJsonConverter`, was described as its JSON +spelling (`"type":"string"`, whose pattern refuses `1.50`, which `TryParse` binds) as a minimal-API query, header and +form parameter, and as the items of a query or header array in minimal APIs and MVC. `ValueObjectSchemaTransformer` +asked ASP.NET for the wrapped type without the parameter's description, so `DescribedSchemaTransformer` could not tell +it was a parameter's. The fix asks for it as the parameter's. That settles the schema where value objects are inlined; +where they are components, it does not. + +Where value objects are components, ASP.NET writes such a parameter as a reference to the value object's component. +That component is shared with every JSON position (a property, a body, a response, an element), and it is the schema of +the first use ASP.NET meets ([KB-027](../knowledge/KB-027-an-enum-component-is-its-first-use.md)). A parameter of +`Money` and a property of `Money` cannot both be right through one component: the parameter is the `decimal` +`TryParse` reads, the property the string the converter writes. Measured with the fix in place and the reference kept, +in one document: `ParameterMoney`, met first as a parameter, got the parameter's number as its component, and its JSON +property referred to it (6 rows of `A_value_object_s_JSON_positions_keep_the_described_spelling_beside_parameters_of_it`, +every components row); `ProbeAmount`, met first in JSON through the probe endpoints mapped before, got the string, and +its parameters referred to that (48 parameter rows, every components row of a value object outside the body). + +Three ways were weighed: + +- **Keep the reference.** Whichever position comes first is wrong for the other, by the order of the endpoints. +- **Write every value-object parameter inline.** Sound, but it changes the document of every application with a + value-object parameter in component mode, where the parameter's schema and the component are the same. +- **Write inline only where they differ:** a parameter, or an element of one, of a value object that wraps a type the + options describe, directly or through the value objects it wraps. + +## Decision + +**A value object over a described type is written inline outside the body.** As a route, query, header or form +parameter and as an element of a query or header array, where the host makes value objects components, its schema is +written in place, as ASP.NET writes a parameter of the underlying type, which has no component. Its JSON positions keep +referring to the component, which is what the converter writes. `ValueObjectSchemaTransformer` takes away the name +ASP.NET reads the component from (the `x-schema-id` metadata, ASP.NET's own key), and puts back on the schema the +default ASP.NET set aside for the reference (`x-ref-default`). + +**Every other value object keeps its reference.** A value object over a type nothing describes is the same schema as +a parameter and in JSON, and stays a reference to its component, as before. + +**The schema below a parameter is asked for without the parameter's own description.** The wrapped type and an element +are asked for with the parameter's source and type only (`Detached`): ASP.NET applies a parameter's default and its +validation attributes to the schema it is asked for, and they are already on the parameter's. Asked again, a value +object's default is written as the wrapped type, and ASP.NET throws (`InvalidCastException`, measured); an array's +attributes would be each element's. + +## Consequences + +- In component mode a parameter of `Money` reads `{"type":["number","string"],"format":"double",...}` in place, and a + property of `Money` reads `{"$ref":"#/components/schemas/Money"}`, whose component is the string. Nothing about a + parameter decides the component any more, whatever the order of the endpoints. +- The test is `DescribedParameterTests`: each parameter against the same parameter of `decimal` or `decimal?` in a host + without the converter and without `AddTypeKit()`, in every references mode, and the JSON positions beside it. +- A value object whose own converter is replaced by a described one is unchanged: as a parameter it is what it was + before, which this record does not decide. + +## Evidence + +- `DescribedParameterTests` at the fix: 270 rows green. With the source change reverted alone, 144 fail: every value + object row of a minimal-API query, header, form and defaulted query, and of a query or header array in both hosts. +- With the fix and the reference kept (no inlining), 54 fail, all in component mode: the parameter rows that refer to + the JSON component, and the JSON positions whose component became a parameter's. +- With the default not put back, 6 fail: the defaulted query parameter of a value object, components, minimal APIs. +- ASP.NET's keys, read from Microsoft.AspNetCore.OpenApi 10.0's source (`OpenApiConstants`, internal): + `x-schema-id`, read by `ResolveReferenceForSchema` to make the component; `x-ref-default`, written by + `ApplyDefaultValue` for a schema that will be a component and moved onto the reference by + `AddOpenApiSchemaByReference`. diff --git a/design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md b/design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md new file mode 100644 index 00000000..f11db031 --- /dev/null +++ b/design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md @@ -0,0 +1,69 @@ +# 0107. A fact that prunes or excuses is executable, its answer is a fixture beside its test, and seeds run by footprint + +Date: 2026-10-17 +Status: accepted + +Context: 2026-10-17, WS2b of the probing concept (facts at a pinned version shorten every loop), after the probe +triage of 2026-10-16 and 2026-10-17 pinned KB-026 to KB-038 and the harness began to cite them +(`Cite.Fact("KB-037")`, PR #53). [Decision 93](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md) +made a fact executable where it can be; nothing held the probe corpus to citing only those. A prose fact cited by a +`refused` entry, a `not-promised` entry or an applicability condition would prune or excuse rows on the strength of a +sentence no test re-reads when a dependency moves, which is the substring excuse in a nicer form. The facts whose +tests compare a document or a status held the measured answer inside the test's code, where the harness, which will +read a fact as the framework's side of a row (`Baseline.Fact`), cannot reach it. And `prober seeds` built once per +seed of every probe on every run, a release step's cost paid after every probe change. + +## Decision + +**A fact that prunes or excuses is executable.** `docs-query check` gains `fact-not-executable-cited`. It fails, at +the citing place and naming the fact, when a fact without a test is cited by a `refused` entry of +`.claude/probe-axes.md` (its reason names `KB-NNN`), a `not-promised` entry of `tools/probe/corpus/claims.json` (its +`source` is the fact's file, or a field says `fact:KB-NNN`), an obligation's `inapplicable` declaration (its `cite` is +`fact:KB-NNN`), or a `Cite.Fact("KB-NNN")` or `Baseline.Fact("KB-NNN")` literal of the harness and probe sources, read +as text with comments blanked out as `SourceLint` reads them. A fact without a test is one whose `test:` is `none, …`, +names no file, or names a file that does not exist or declares no class of the fact's id; a cited id that is no fact +fails too. `Baseline.Fact` takes a fixture as well. The check's summary counts the citations of each kind, and +`RepositoryTests` holds floors on the places read and the citations found, so a scan that finds nothing cannot pass. +A fact without a test keeps its pins and its date and may be named in a reason's prose; it never decides a row. + +**A fact's measured answer is a fixture beside its test.** Where the answer is data (a document, a status, an +exception type at an operation), it is `test/CodoMetis.TypeKit.Facts/KBNNN.fixture.json`, named by the fact's +`fixture:` line: the fact's id and its answers, each with an `id`, a `kind` (`document` with `at`, a JSON pointer; +`status`; `exception`), the settings it was measured under (`when`, axis to value, as a row names them), the `value` +and a `note`. The fact's test measures every answer of the fixture, a theory over its ids, so an answer the test does +not measure fails it. `docs-query check` fails a `fixture:` whose file is missing (`fact-fixture-missing`), one that is +not `KBNNN.fixture.json` beside the test or belongs to a fact without a test (`fact-fixture`), and a fixture no fact +names (`fact-orphan-fixture`). The reader, `FactFixture`, is BCL only, so the harness can compile it as it is, and +states the rule the harness will follow: only a fixture a fact names, only while the check is clean; the one answer +whose every `when` setting is the row's, none meaning the harness measures the framework itself and several a harness +error; compared by kind; never written by the harness. KB-001, KB-026, KB-027 and KB-037 are in this form. + +**Seeds run by footprint, the release runs them all.** Without `--all`, `prober seeds` runs only the probes whose seeds +identity (the probe's identity, its seeds and their patches' content) changed, or whose product footprint or seeded +file changed, since the run that caught every one of its seeds, and prints each probe it skips and why. +`seeds-last.json` records, per probe, the identity and the commit of that run; a probe with a seed missed loses its +record, so it runs next time whatever changed, and a probe without a recorded footprint runs. `--all` runs every probe +whatever the record says, and `--probe P` runs P. A release runs `seeds --all` locally before the tag, beside +`replay --all` and the gate; CI still never builds a seed ([decision 100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)). + +## Evidence + +On 2026-10-17 the citation rule read 3 refused entries, 41 not-promised entries, 11 obligation declarations and 87 +harness and probe sources, and found 4 citations: KB-001 and KB-027 by not-promised entries, KB-037 twice by the +harness; every one is executable. Each kind was seeded in a scratch copy of the repository and failed the check: a +cited fact's `test:` line removed, one pointed at a missing file, and a prose fact (KB-005) cited from a refused entry, +an obligation declaration and the harness. A wrong answer seeded into KB001's and KB037's fixtures failed +`KB001` and `KB037`. Each rule of the seeds selection was seeded in the prober (identity, footprint, seeded file, +`--all`, a missed seed's record, a missing footprint, never skipping) and failed its `SeedsTests`. + +Rejected: a fixture kept in `design/knowledge/` beside the entry, which no build copies and the facts project would +reach by path. + +## Consequences + +- A triage that settles a row on a framework fact writes the test first, then cites it; a prose fact that would + explain a row is research to do, not a citation. +- `prober seeds` after a probe or product change runs seconds to minutes instead of one build per seed of every probe. +- Wiring `Baseline.Fact` to the fixtures is the harness's next step; this record only fixes the rule it follows. A + probe's identity does not yet include the facts it cites, so a changed fixture does not stale the probes reading it + until that step adds it. diff --git a/design/adr/0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md b/design/adr/0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md new file mode 100644 index 00000000..4f8495b4 --- /dev/null +++ b/design/adr/0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md @@ -0,0 +1,516 @@ +# 0108. A probe check earns credit against a baseline, applicability is declared and the harness compares three sides; WS2 closes, and WS3 to WS6 are dropped + +Date: 2026-10-17 +Status: accepted + +The gate passed on release/2.2.0 on 2026-10-18 (#62: declarations citing this record and KB-042, exclusions with reasons and rows for the 17 refusals; #63: the fix for F4, a nullable value object as a component losing its null, which C21's regenerated rows raised and the code-analyst confirmed). + +Context: 2026-10-16 and 2026-10-17, the second workstream (WS2, the harness) of the concept for finding issues with +agents, which set out to make the probe corpus (`tools/probe/corpus`) and the prober (`tools/Prober`) find defects +mechanically. The gate round of 2026-10-16 that preceded it had written 18 probes and found two product defects and about +ten harness traps, five traps for each defect, and left five failing rows of C07 untriaged (`.claude/skills/probe-triage`). +What the traps had in common was that the harness, not the product, decided a row: + +- **A row was credited all or nothing.** A row ended as one category, so one excuse anywhere in it voided the comparisons + that passed beside it, and one passing comparison credited every setting the row held. At 265309f, the commit before + the first per-check change, BaseProbe passed 135 of its 411 rows and C07's two probes 545 of 822 (66.3%); L01 credited + nothing for 160 rows whose write passed, because their read was refused for the wrapped type too. +- **An excuse was a string.** A check noted an outcome key of `NoteKeys` (63 keys at the start of WS2: 25 pass, 20 + inapplicable, 17 expected refusal, 1 inconclusive, [decision 103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md)), + and the harness decided rows by an exception's words wherever a substring could tell (`stj-maxdepth`, + `mvc-400-unwritable-under-source-gen`, `json:read-refused-by-stj-alone`, the OpenAPI 2.0 and enum paths). +- **The baseline was the product beside itself.** The wrapped twin ran with TypeKit, not without it, and at a nullability + of its own (`Hx.PlainNull`), and a source-generated context fell back to reflection where it did not list a shape + (`+reflection-fallback`), so about 270 pairs could never be earned as the row named them. +- **Nothing tied a probe to what it read outside its footprint**, the generated harness files had lost their + generators, and an obligation could not say where it does not apply. + +The maintainer ended the programme on 2026-10-17 at WS2 and the 2.2.0 release. + +## Decision + +**A row runs checks, and the ledger credits checks.** `Row.Check(obligation, baseline, body)` (#41) is one check per +obligation the row's axes select, named by the claim's obligation id ([decision 104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md)). +A returned failure is the check's violation, an exception its harness error, and a pass without a `Baseline` (`Twin`, +`Framework`, `RoundTrip`, `Model`, `Refusal`, `Cost`, `Fact("KB-NNN")`, `Docs("file#anchor")`) a harness error. A row +passes when no check failed and one passed. The ledger credits each passing check with the settings it read and the +row's, never a pair put together from two checks, and `prober seeds` counts a seed naming an obligation caught only by a +clean violation of a check naming it. Evidence is schema version 2. + +**An excuse is a closed citation.** A check that did not apply, refused or could not tell says so through its scope: +`c.Inapplicable(Cite…)`, `c.Refused(Cite…)`, `c.Inconclusive(reason)`. A `Cite` names a not-promised entry of the claim, +a docs section, a verified fact or a record, with a string literal; `register`, `publish` and `ProbeCiteTests` refuse +anything else. A fact that prunes or excuses a row is executable, and its measured answer is a fixture beside its test +([decision 107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)). `NoteKeys` keeps the pass keys, which +say how a check compared, and one outcome key, `framework-refuses`; the 41 outcome keys no source called any more are +gone (27 keys remain). + +**Applicability is declared.** An obligation's `inapplicable` entries in `claims.json` (axes and a cite, #50) take a pair +out of what the obligation wants; the universe, `rows`, the gate and `Row.Check` read the same declaration, and a pair no +obligation wants is counted as the gate counts an exclusion. + +**The harness compares three sides.** For the http, json and text families (#58, `harness/Sides.cs`): (1) the wrapped +twin without TypeKit, (2) the twin with it, (3) the value object. (1) and (3) refusing alike, by operation, stage, +exception type and status and never by message, is the framework's refusal (`framework-refuses`, an expected refusal +against side (1)'s baseline); one refusing where the other answers is a violation unless the check cites the sentence +that makes it intended; (2) differing from (1) is a violation. Side (1) comes from a fact's fixture where one covers the +row, else it is measured, on every row of an obligation that promises non-interference and lazily elsewhere. +`ProbeMessageTextTests` refuses a message read that excuses a row. **Decision 99's document rule joins it**: a document +ASP.NET alone refuses is compared with the one `AddTypeKit()` refused by type and stage (`Hx.DocAlike`), and leaves a +check that compared an answer on the wire first to be judged by that answer (`doc:refused-by-aspnet-alone`); a check +that compared nothing else is the framework's refusal and earns nothing. No message comparison is kept. + +**The twins and the context are generated with the shapes** (#59): the twin is the wrapped type at the value object's +nullability, `Sides.Twins` holds both sides to the same shape, and one `ProbeContext` lists every shape, so +`json.TypeInfoResolver=source-generated-context` is strict and the reflection fallback is the existing +`json.TypeInfoResolverChain=context-then-default`. + +**Rows are per probe, and `watched` stales a footprint** (#39): `rows/.json.gz`, deduplicated, and a registry +entry's `watched` paths stale the probe like its footprint. **The harness generators are C# file-based apps** +(`tools/probe/generators`, #35), held to their output by `GeneratedHarnessTests`. + +**The prober closes the loop it left open:** `prober fix F --commit SHA` records a finding fixed; `rows` never places +an excluded pair or one no obligation wants, and leaves out of a row an axis none of whose values can join it; +`prober gaps` lists, from the last replay's evidence, the wanted pairs no obligation wanting them has a passing check +that read both their axes of. + +**WS3 to WS6 are dropped**, by the maintainer's decision of 2026-10-17: WS3 (beyond pairs: three-way and deeper +combinations), WS4 (the mutation nightly), the second half of WS5 (the finder benchmark at equal budgets) and WS6 +(PostgreSQL in CI). Their work is not scheduled; the roadmap says so. + +## Consequences + +- **A probe writer** writes one `Row.Check` per obligation the row reaches, with its baseline; cites every excuse or + ends the check inconclusive; declares in `claims.json` an inapplicability the axes alone decide; builds two-sided + comparisons with `Sides.Twins` and refuses alike by `Sides.Alike`; never reads a message to excuse a row; names + `watched` paths the footprint cannot see; and gives each obligation a seed that a check of that obligation catches. +- **The gate counts** pairs earned by passing checks of probes current at their identity and footprint, less exclusions + and pairs no obligation wants. It now counts nothing for a document both hosts refuse in a check that compared nothing + else: 2,001 pairs that 401 such rows alone had credited are uncovered (C12 821, C13 48, C16 455, C21 677), nearly all + under `json.ReferenceHandler=Preserve` or `json.MaxDepth=3`, where System.Text.Json refuses the document for plain + ASP.NET too (decision 99). Each needs a declaration of its document obligations' inapplicability there, citing record + 0099 or a fact, and rows that hold the rest of those pairs without those settings. +- **Open, not done here:** + - The EF twins at the value object's nullability (#59): `EfEntry.Plain` is the type `From` takes, and an EF twin is the + entity's own wrapped-type property; giving EF twins the value object's nullability is its own step, with an EF + replay. + - The untriaged candidate rows #59 saw in regenerated arrays it did not commit, open leads for whoever regenerates + those files next: C21 under OpenAPI 2.0 with a class value object over a struct at the root, where ASP.NET writes the + twin's nullable body (`double?`, `int?`) without `pattern` and the value object keeps it (rows 6 and 143 of that + file); a validated struct over `string` as a nullable property, the twin `{"type":"string"}` against the value + object's `{}` (row 115); a validated class over `Uri`, the twin's `x-nullable` (row 181); a sealed record class over + `char` at the root, `format: char` alone against `type: string, minLength/maxLength 1` (row 282); a record class + bound from a form, the twin's `A`/`B` properties (rows 53, 208); `Instant` as a dictionary value (row 134); and C10 + row 266, an array of value objects over `byte` under `default-with-renaming-modifier` with + `input.json=metadata-property` that does not read back its own document, under `ignore-cycles-unaffected`. + - The narrowing of the pairs an obligation wants by its own `axes` (#54's option 2): an obligation that never reads an + axis still wants every pair over it, as C11's parse-shape obligations wanted `shape.wrapped`'s; `prober gaps` now + lists such pairs, and none is open today. + - `polymorphic-base-property` as a value of `shape.position` (#51): the axes file is in every probe's identity, so + adding it stales the corpus and widens every claim over `shape.position`. +- `gaps` reads the last replay's evidence beside the tracked corpus and the generated axes (decision 104 said it read + only those), and still decides nothing. +- A row that `rows` writes may leave an axis out where it cannot apply; a harness that reads it there ends the row as a + harness error, never a pass. + +## Evidence + +Measured 2026-10-17 on Linux x64 (32 threads, otherwise idle) with the pinned SDK. **Before**: release/2.2.0 at +265309f (#39 merged, before #41), replayed from an empty state in a detached worktree with its own built tools. +**Now**: `build/ws2-close-out` at 5c5affe, the same replays. Each replay ran `replay --all --family F` for http (one +probe process at a time, as a replay recording footprints runs), json, text, build and ef, with footprints. + +**Replays.** Before: 43 probes, 43 passed; 677 s wall (http 398 s, json 42 s, text 75 s, build 53 s, ef 102 s, each the +replay's own "in all"). Now: 45 probes, 45 passed; 818 s wall (http 502 s, json 50 s, text 83 s, build 68 s, ef 106 s). +The two new probes (C21, C22), side (1)'s hosts and the larger `ProbeContext` (#59: the Samples' rebuild after a harness +change went from 38 s to 74 s) are the difference. + +**Row for row, per probe.** A row that runs no `Row.Check` counts as one check. "Rows passing" is `Rec.Run`'s rule: no +violation or harness error in the row or a check, and one check that passed. Regained and lost rows are counted only +where the row is the same at the same index before and now; where rows were regenerated (#49, #59) or appended (#54) the +table says so, and those probes' pairs are in the next table. + +| Probe | rows before | checks before | rows now | checks now | rows passing, before → now | regained (category before) | lost (category now) | +|---|---|---|---|---|---|---|---| +| BaseProbe | expected-refusal 39, inapplicable 237, pass 135 | expected-refusal 39, inapplicable 237, pass 135 | expected-refusal 123, inapplicable 109, inconclusive 43, pass 136 | expected-refusal 180, inapplicable 388, inconclusive 63, pass 986 | 135 → 332 | 197 (expected-refusal 30, inapplicable 167) | 0 | +| C01DecimalSpellingProbe | expected-refusal 5, inapplicable 4, pass 108 | expected-refusal 5, inapplicable 4, pass 108 | expected-refusal 5, inconclusive 15, pass 97 | expected-refusal 25, inconclusive 32, pass 528 | 108 → 111 | 3 (inapplicable 3) | 0 | +| C02OptionalTextProbe | pass 46 | pass 46 | pass 46 | pass 52 | 46 → 46 | 0 | 0 | +| C03ComposedProbe | pass 648 | pass 648 | expected-refusal 397, pass 251 | expected-refusal 794, pass 2125 | 648 → 648 | 0 | 0 | +| C04WithoutFaultsProbe | expected-refusal 2, pass 394 | expected-refusal 2, pass 394 | expected-refusal 135, pass 162 | expected-refusal 135, pass 493 | 394 → 297 | 0; 284 of 297 regenerated | 0 | +| C05RuleProbe | pass 645 | pass 645 | pass 645 | pass 1666 | 645 → 645 | 0 | 0 | +| C06FaultKindsProbe | expected-refusal 4, pass 404 | expected-refusal 4, pass 404 | pass 306 | pass 1224 | 404 → 306 | 0; 295 of 306 regenerated | 0 | +| C07BlanketMappingProbe | expected-refusal 1, pass 410 | expected-refusal 1, pass 410 | expected-refusal 44, pass 367 | expected-refusal 44, pass 431 | 410 → 411 | 1 (expected-refusal 1) | 0 | +| C08SameNameWarningProbe | pass 318 | pass 318 | pass 302 | pass 540 | 318 → 302 | 0; 292 of 302 regenerated | 0 | +| C09BaseRecordEqualityProbe | pass 101 | pass 101 | pass 101 | pass 202 | 101 → 101 | 0 | 0 | +| C10PreserveGuardProbe | expected-refusal 14, pass 292 | expected-refusal 14, pass 292 | expected-refusal 100, inapplicable 1, inconclusive 2, pass 203 | expected-refusal 112, inapplicable 1, inconclusive 2, pass 497 | 292 → 294 | 2 (expected-refusal 2) | 0 | +| C11ParseShapesBuildProbe | pass 241 | pass 241 | inapplicable 4, pass 243 | inapplicable 4, pass 243 | 241 → 243 | 0; 6 rows appended | 4 (inapplicable 4) | +| C12DescribedConverterProbe | pass 332 | pass 332 | expected-refusal 156, pass 145 | expected-refusal 338, pass 363 | 332 → 145 | 0; 289 of 301 regenerated | 5 (expected-refusal 5) | +| C13PatchRequiredProbe | expected-refusal 55, pass 301 | expected-refusal 55, pass 301 | expected-refusal 82, inconclusive 51, pass 163 | expected-refusal 105, inconclusive 51, pass 436 | 301 → 273 | 0; 282 of 296 regenerated | 0 | +| C14DeclaredProblemProbe | expected-refusal 3, pass 418 | expected-refusal 3, pass 418 | expected-refusal 273, pass 148 | expected-refusal 633, pass 764 | 418 → 420 | 2 (expected-refusal 2) | 0 | +| C15MvcFaultStatusDocProbe | expected-refusal 7, pass 398 | expected-refusal 7, pass 398 | expected-refusal 104, pass 301 | expected-refusal 104, pass 447 | 398 → 404 | 6 (expected-refusal 6) | 0 | +| C16OptionsAgreementProbe | pass 310 | pass 310 | expected-refusal 93, pass 203 | expected-refusal 127, pass 287 | 310 → 203 | 0; 287 of 296 regenerated | 3 (expected-refusal 3) | +| C17Cmtk0014Probe | pass 310 | pass 310 | pass 283 | pass 512 | 310 → 283 | rows regenerated | rows regenerated | +| C18Cmtk1023Probe | pass 280 | pass 280 | pass 280 | pass 840 | 280 → 280 | 0 | 0 | +| C19Cmtk0010KindsProbe | pass 302 | pass 302 | pass 282 | pass 508 | 302 → 282 | rows regenerated | rows regenerated | +| C20ShouldHoldProbe | pass 261 | pass 261 | pass 261 | pass 727 | 261 → 261 | 0 | 0 | +| C21OpenapiSchemaParityProbe | - | - | expected-refusal 129, inapplicable 4, inconclusive 6, pass 155 | expected-refusal 230, inapplicable 7, inconclusive 12, pass 248 | 0 → 155 | new probe | new probe | +| C22GeneratorRefusalsProbe | - | - | pass 144 | pass 432 | 0 → 144 | new probe | new probe | +| E01ColumnParityProbe | pass 175 | pass 175 | pass 175 | pass 307 | 175 → 175 | 0 | 0 | +| E02ValueTranslatesProbe | expected-refusal 12, pass 203 | expected-refusal 12, pass 203 | inapplicable 12, pass 203 | inapplicable 24, pass 406 | 203 → 203 | 0 | 0 | +| E03ColumnRoundTripProbe | pass 177 | pass 177 | pass 177 | pass 177 | 177 → 177 | 0 | 0 | +| E04IntegerKeyProbe | pass 28 | pass 28 | pass 28 | pass 28 | 28 → 28 | 0 | 0 | +| E05OwnCompareToProbe | pass 90 | pass 90 | pass 90 | pass 90 | 90 → 90 | 0 | 0 | +| E06OwnConverterNoValueProbe | pass 12 | pass 12 | pass 12 | pass 12 | 12 → 12 | 0 | 0 | +| E07ClosedSetNameProbe | pass 9 | pass 9 | pass 9 | pass 11 | 9 → 9 | 0 | 0 | +| E08OptionResultNotColumnsProbe | pass 20 | pass 20 | pass 20 | pass 40 | 20 → 20 | 0 | 0 | +| E09OrNoneInDbProbe | pass 18 | pass 18 | pass 18 | pass 18 | 18 → 18 | 0 | 0 | +| E10ReadBackSkipsValidationProbe | pass 30 | pass 30 | pass 30 | pass 60 | 30 → 30 | 0 | 0 | +| E11UseTypeKitOrderProbe | pass 14 | pass 14 | pass 14 | pass 14 | 14 → 14 | 0 | 0 | +| E12ConventionByTypeProbe | pass 12 | pass 12 | pass 12 | pass 12 | 12 → 12 | 0 | 0 | +| E13PrimitiveCollectionProbe | pass 10 | pass 10 | pass 10 | pass 10 | 10 → 10 | 0 | 0 | +| E14InheritanceKeyProbe | pass 9 | pass 9 | pass 9 | pass 9 | 9 → 9 | 0 | 0 | +| E15CompositeKeyProbe | pass 8 | pass 8 | pass 8 | pass 8 | 8 → 8 | 0 | 0 | +| E16OwnConfigurationWinsProbe | pass 25 | pass 25 | pass 25 | pass 25 | 25 → 25 | 0 | 0 | +| L01JsonBytesProbe | expected-refusal 180, inapplicable 7, inconclusive 1, pass 393 | expected-refusal 180, inapplicable 7, inconclusive 1, pass 393 | expected-refusal 186, inapplicable 112, inconclusive 21, pass 262 | expected-refusal 202, inapplicable 128, inconclusive 22, pass 810 | 393 → 548 | 160 (expected-refusal 160) | 5 (inapplicable 1, inconclusive 4) | +| L02NoQuoteProbe | expected-refusal 29, inapplicable 11, pass 546 | expected-refusal 29, inapplicable 11, pass 546 | inapplicable 218, inconclusive 132, pass 236 | inapplicable 302, inconclusive 159, pass 843 | 546 → 434 | 16 (expected-refusal 15, inapplicable 1) | 128 (inapplicable 96, inconclusive 32) | +| L03OneRuleProbe | expected-refusal 5, inapplicable 13, pass 568 | expected-refusal 5, inapplicable 13, pass 568 | expected-refusal 56, inapplicable 79, inconclusive 21, pass 430 | expected-refusal 75, inapplicable 95, inconclusive 22, pass 1330 | 568 → 561 | 13 (expected-refusal 2, inapplicable 11) | 20 (expected-refusal 14, inconclusive 6) | +| L04RoundTripProbe | expected-refusal 78, inapplicable 127, pass 381 | expected-refusal 78, inapplicable 127, pass 381 | expected-refusal 18, inapplicable 74, inconclusive 59, pass 139 | expected-refusal 21, inapplicable 111, inconclusive 64, pass 994 | 381 → 290 | rows regenerated | rows regenerated | +| L05EqualityProbe | pass 309 | pass 309 | pass 309 | pass 793 | 309 → 309 | 0 | 0 | +| L06OffTheWireProbe | expected-refusal 9, inapplicable 12, pass 304 | expected-refusal 9, inapplicable 12, pass 304 | expected-refusal 21, inapplicable 3, inconclusive 29, pass 272 | expected-refusal 22, inapplicable 15, inconclusive 38, pass 575 | 304 → 303 | 0 | 1 (inconclusive 1) | +| **all** | 10850 rows | 10850 checks (9995 pass) | 10629 rows | 25810 checks (21123 pass) | 9995 → 9871 | 400 | 166 | + +The 400 rows regained all earned nothing before (expected refusal or inapplicable). In 391 one excuse had voided a row +whose other comparisons passed (BaseProbe 197, L01 160, L02 16, L03 13, C01 3, C10 2; #53, #57). C07's 1, C14's 2 and +C15's 6 are rows under `json.MaxDepth=3` that the `stj-maxdepth` message read had ended; both hosts now refuse their +documents alike by type, and their checks are judged by the wire they compared (this record). Every one of the 166 rows lost passed before on no comparison, each for its stated reason: + +| Probe | Rows | Reason, and where it was decided | +|---|---|---| +| C11ParseShapesBuildProbe | 4 | #174, #180, #183, #189: span parsing over `Uri`, an enum, a flags enum and `Instant`, declared inapplicable by `no-span-parsing-without-the-underlying-types` after #52 narrowed the Generators README (#54) | +| C12DescribedConverterProbe | 5 | this record: a document both hosts refuse alike, in checks that compared nothing else | +| C16OptionsAgreementProbe | 3 | the same | +| L01JsonBytesProbe | 5 | #57: four rows under an application's converter that cannot write the value, where the old branch passed with nothing read back, now inconclusive; #363 declared inapplicable (a closed-set key under a key policy, "Your own converter wins") | +| L02NoQuoteProbe | 128 | #57: 99 rows whose only check was `json-never-repeats` under an application's converter, now declared inapplicable (`not-promised:the-applications-own-converter`), and 29 rows that refuse nothing to quote (a string or `Uri` reads every input, or the options leave the member out), now inconclusive | +| L03OneRuleProbe | 20 | #58: `json-half:wrapped-alone-refuses` took any two refusals for the framework's; 14 refuse alike and end `framework-refuses`, 6 refuse differently and end inconclusive | +| L06OffTheWireProbe | 1 | #58: #240's pass compared two refusals' messages; it is `framework-refuses` by type and path | + +**The document rule (this record).** The http family replayed from empty states without footprints at 26e51e6, before +the change, and with it; 13 passed both times: + +| Probe | rows before | checks before | rows after | checks after | rows passing | regained | lost | +|---|---|---|---|---|---|---|---| +| BaseProbe | expected-refusal 123, inapplicable 109, inconclusive 43, pass 136 | expected-refusal 180, inapplicable 388, inconclusive 63, pass 986 | expected-refusal 123, inapplicable 109, inconclusive 43, pass 136 | expected-refusal 180, inapplicable 388, inconclusive 63, pass 986 | 332 → 332 | 0 | 0 | +| C03ComposedProbe | pass 648 | pass 2919 | expected-refusal 397, pass 251 | expected-refusal 794, pass 2125 | 648 → 648 | 0 | 0 | +| C04WithoutFaultsProbe | expected-refusal 4, pass 293 | expected-refusal 5, pass 623 | expected-refusal 135, pass 162 | expected-refusal 135, pass 493 | 297 → 297 | 0 | 0 | +| C05RuleProbe | pass 645 | pass 1666 | pass 645 | pass 1666 | 645 → 645 | 0 | 0 | +| C06FaultKindsProbe | expected-refusal 6, pass 300 | expected-refusal 6, pass 1218 | pass 306 | pass 1224 | 306 → 306 | 0 | 0 | +| C07BlanketMappingProbe | expected-refusal 1, pass 410 | expected-refusal 1, pass 474 | expected-refusal 44, pass 367 | expected-refusal 44, pass 431 | 410 → 411 | 1 (expected-refusal 1) | 0 | +| C08SameNameWarningProbe | pass 302 | pass 540 | pass 302 | pass 540 | 302 → 302 | 0 | 0 | +| C12DescribedConverterProbe | pass 301 | pass 701 | expected-refusal 156, pass 145 | expected-refusal 338, pass 363 | 301 → 145 | 0 | 156 (expected-refusal 156) | +| C13PatchRequiredProbe | inconclusive 51, pass 245 | inconclusive 51, pass 541 | expected-refusal 82, inconclusive 51, pass 163 | expected-refusal 105, inconclusive 51, pass 436 | 296 → 273 | 0 | 23 (inconclusive 23) | +| C14DeclaredProblemProbe | expected-refusal 4, pass 417 | expected-refusal 15, pass 1382 | expected-refusal 273, pass 148 | expected-refusal 633, pass 764 | 417 → 420 | 3 (expected-refusal 3) | 0 | +| C15MvcFaultStatusDocProbe | expected-refusal 9, pass 396 | expected-refusal 11, pass 540 | expected-refusal 104, pass 301 | expected-refusal 104, pass 447 | 396 → 404 | 8 (expected-refusal 8) | 0 | +| C16OptionsAgreementProbe | pass 296 | pass 414 | expected-refusal 93, pass 203 | expected-refusal 127, pass 287 | 296 → 203 | 0 | 93 (expected-refusal 93) | +| C21OpenapiSchemaParityProbe | inapplicable 4, inconclusive 6, pass 284 | inapplicable 7, inconclusive 12, pass 478 | expected-refusal 129, inapplicable 4, inconclusive 6, pass 155 | expected-refusal 230, inapplicable 7, inconclusive 12, pass 248 | 284 → 155 | 0 | 129 (expected-refusal 129) | +| **all** | 5033 rows | 13221 checks (12482 pass) | 5033 rows | 13221 checks (10010 pass) | 4930 → 4541 | 12 | 401 | + +In each of the 401 rows lost every check that had passed noted a document both hosts refused and compared nothing else +(checked over the evidence: 0 exceptions); the 12 regained are the `json.MaxDepth=3` rows above. Seeded: the +value-object schema transformer throwing an `InvalidOperationException` fails, in 8 sampled rows of C21, the 4 whose +document ASP.NET alone writes ("ASP.NET alone writes it") both before and after; of the 4 whose document ASP.NET alone +refuses, before the change 3 passed (the refusal came first, with the same words) and #156 failed on a message that +differed, and after it all 4 end `framework-refuses`. #156 is what comparing no message gives up. + +**`rows` on the whole corpus**, `rows --claim C --full` for each of the 44 claims with the tool before (9e23403, the same +`Covering` as ac4c561) and now: 7,746 rows of which 740 held an excluded pair, against 7,842 rows of which none holds an +excluded pair or one no obligation wants, with no pair left uncovered by either. A first version that gave up a pair no +full row could hold left 3,288 pairs uncovered (C09, C11 and C18 got no row at all) and was replaced by leaving the axis +out of the row. + +**Pairs per claim.** `prober status --json`; "wanted" is the claim's pairs less those no obligation wants: + +| Claim | 265309f: current / pairs | now: current / wanted | declared inapplicable | excluded or declared since | +|---|---|---|---|---| +| C01-decimal-spelling | 4344 / 4344 | 4344 / 4344 | - | | +| C02-optional-text | 119 / 126 | 119 / 126 | - | | +| C03-composed-faults | 15583 / 15607 | 15605 / 15607 | - | | +| C04-without-faults | 15409 / 15426 | 15427 / 15427 | - | +1 pairs in the universe | +| C05-with-rule | 15750 / 15783 | 15781 / 15783 | - | | +| C06-fault-kinds | 15231 / 15248 | 15249 / 15249 | - | +1 pairs in the universe | +| C07-fault-responses-blanket | 15945 / 15956 | 15957 / 15957 | - | +1 pairs in the universe | +| C08-same-name-warning | 15609 / 15609 | 15609 / 15609 | - | | +| C09-base-record-equality | 487 / 487 | 487 / 487 | - | | +| C10-preserve-guard | 6797 / 6797 | 6797 / 6797 | - | | +| C11-parse-shapes-build | 1048 / 1048 | 1044 / 1044 | - | -4 pairs in the universe | +| C12-described-converter | 14904 / 14904 | 14083 / 14904 | - | | +| C13-patch-required | 14731 / 14731 | 14683 / 14731 | - | | +| C14-declared-problem | 14886 / 14903 | 14904 / 14904 | - | +1 pairs in the universe | +| C15-mvc-fault-status-doc | 15238 / 15255 | 15256 / 15256 | - | +1 pairs in the universe | +| C16-options-agreement | 14904 / 14904 | 14449 / 14904 | - | | +| C17-cmtk0014 | 1652 / 1652 | 1791 / 1832 | - | +180 pairs in the universe | +| C18-cmtk1023 | 1067 / 1067 | 1067 / 1067 | - | | +| C19-cmtk0010-kinds | 2091 / 2091 | 2242 / 2292 | - | +201 pairs in the universe | +| C20-testing-shouldhold | 507 / 507 | 507 / 507 | - | | +| C21-openapi-schema-parity | (no claim) | 13185 / 13883 | 336 | | +| C22-generator-refusals | (no claim) | 0 / 621 | - | | +| E01-column-parity | 372 / 414 | 414 / 414 | - | | +| E02-value-translates-to-column | 400 / 412 | 400 / 406 | 6 | | +| E03-column-round-trip | 290 / 290 | 290 / 290 | - | | +| E04-integer-key-generated | 61 / 61 | 61 / 61 | - | | +| E05-own-compareto-rejected | 90 / 90 | 90 / 90 | - | | +| E06-own-converter-no-value | 26 / 26 | 26 / 26 | - | | +| E07-closed-set-stored-as-name | 9 / 9 | 9 / 9 | - | | +| E08-option-result-not-columns | 47 / 47 | 47 / 47 | - | | +| E09-or-none-in-database | 69 / 69 | 69 / 69 | - | | +| E10-read-back-skips-validation | 52 / 52 | 52 / 52 | - | | +| E11-usetypekit-order | 14 / 14 | 14 / 14 | - | | +| E12-convention-by-vo-type | 26 / 26 | 26 / 26 | - | | +| E13-primitive-collections | 10 / 10 | 10 / 10 | - | | +| E14-inheritance-key-generation | 9 / 9 | 9 / 9 | - | | +| E15-composite-key | 8 / 8 | 8 / 8 | - | | +| E16-own-configuration-wins | 25 / 25 | 25 / 25 | - | | +| L01-json-bytes | 6367 / 6478 | 6473 / 6478 | - | | +| L02-no-quote | 8280 / 8314 | 8240 / 8314 | - | | +| L03-one-rule | 7055 / 7084 | 7079 / 7084 | - | | +| L04-round-trip | 8025 / 8314 | 8312 / 8314 | - | | +| L05-equality | 1076 / 1076 | 1076 / 1076 | - | | +| L06-off-the-wire | 8022 / 8022 | 8021 / 8022 | - | | +| **all** | 226635 / 227295 | 239337 / 242175 | 342 | | + +**C07's pass share by check.** Before, each row was one check `claim`: BaseProbe 135 of 411 (32.8%), +C07BlanketMappingProbe 410 of 411; 545 of 822 (66.3%). Now: BaseProbe's checks 986 of 1,617 (61.0%; 332 of its 411 +rows pass); C07BlanketMappingProbe by obligation `by-kind` 57 of 76, `enum-needs-own-mapping` 66 of 66, +`mapping-refusal-stops-start` 138 of 138, `member-without-kind-stops-start` 68 of 68, `own-mapping` 38 of 63, +`own-set-keeps-mapping` 64 of 64; 1,417 of 2,092 checks in all (67.7%). + +**`NoteKeys`.** 63 keys at the start of WS2; 68 at ac4c561; +27 now: 26 pass keys and `framework-refuses`. The corpus makes 119 `Note` calls (`ProbeNoteKeyTests`' floor stays 100). + +**Product defects the conversions and the analyst found**, each fixed in its own pull request: + +| Found by | Defect | Fixed in | +|---|---|---| +| C21's first run (#40) | F3: a nullable value object over an enum written by name left out the `null` ASP.NET lists among a parameter's values | #45 (8ffad55, `Found-By: PR`), with a sibling found by the catalogue (25118b8, `VA`) | +| C11's conversion (#54) | a value object over `string` did not implement `ISpanParsable`, as `string` does | #52 (9247406, `GTS`, `PR`) | +| the code-analyst run of 2026-10-16 | A1: a non-abstract polymorphic base's own branch undescribed; A2: a query or header array of a described type lost its `items`; A3: a value object over a described type was not the parameter's schema outside the body | #51 (4917a03, ccfcb5d, 66b4d62, `CA`), with two siblings (5a8f048, 0293dcd, `VA`) | +| the code-analyst | `.Value` through a type parameter; Greatest, Least and row values over an own `CompareTo`; a Guid or string key generated on add | #44 (0764462), #43 (7c6c48f), #46 (d9ccfe0), `EFT`, `CA` | + +**Seeds.** `prober seeds --probe P` for every registered probe, from a copy of the final replay's state, in two lanes of +separate worktrees (10,941 s and 11,027 s wall; 21,855 s of seed runs). **170 seeds, 170 caught, each by a check of its own obligation**; BaseProbe's one seed names none, and its `claim` check catches it. Each line names the row and the obligation of the check that caught it: + +| Probe | Seed's obligation | Outcome | Rows violating; e.g. the row and the check that caught it | +|---|---|---|---| +| `BaseProbe` | none (`claim`) | caught | 55 of 411 rows; #10 `claim` | +| `C01DecimalSpellingProbe` | `smallest-scale` | caught | 57 of 117 rows; #0 `smallest-scale` | +| `C01DecimalSpellingProbe` | `number-is-a-number` | caught | 43 of 117 rows; #0 `number-is-a-number` | +| `C01DecimalSpellingProbe` | `string-is-a-string` | caught | 54 of 117 rows; #1 `string-is-a-string` | +| `C01DecimalSpellingProbe` | `reads-a-number-or-a-string` | caught | 101 of 117 rows; #0 `reads-a-number-or-a-string` | +| `C01DecimalSpellingProbe` | `keeps-the-scale-it-reads` | caught | 64 of 117 rows; #0 `keeps-the-scale-it-reads` | +| `C01DecimalSpellingProbe` | `covers-every-decimal` | caught | 4 of 117 rows; #32 `covers-every-decimal` | +| `C02OptionalTextProbe` | `hidden-is-an-error-in-both` | caught | 7 of 46 rows; #3 `hidden-is-an-error-in-both` | +| `C02OptionalTextProbe` | `required-blank-is-an-error` | caught | 11 of 46 rows; #2 `required-blank-is-an-error` | +| `C02OptionalTextProbe` | `optional-blank-is-no-value` | caught | 14 of 46 rows; #0 `optional-blank-is-no-value` | +| `C02OptionalTextProbe` | `null-is-blank` | caught | 6 of 46 rows; #6 `null-is-blank` | +| `C03ComposedProbe` | `each-error-own-set` | caught | 487 of 648 rows; #0 `each-error-own-set` | +| `C03ComposedProbe` | `detail-and-rule-intact` | caught | 163 of 648 rows; #2 `detail-and-rule-intact` | +| `C03ComposedProbe` | `statuses-ascending` | caught | 182 of 648 rows; #1 `statuses-ascending` | +| `C03ComposedProbe` | `shared-status-types` | caught | 182 of 648 rows; #1 `shared-status-types` | +| `C03ComposedProbe` | `unmapped-set-stops-start` | caught | 648 of 648 rows; #0 `unmapped-set-stops-start` | +| `C04WithoutFaultsProbe` | `doc-omits-left-out` | caught | 78 of 297 rows; #1 `doc-omits-left-out` | +| `C04WithoutFaultsProbe` | `reporting-left-out-throws` | caught | 238 of 297 rows; #0 `reporting-left-out-throws` | +| `C04WithoutFaultsProbe` | `narrows-wider-status` | caught | 93 of 297 rows; #0 `narrows-wider-status` | +| `C04WithoutFaultsProbe` | `left-out-everywhere-stops-start` | caught | 59 of 297 rows; #11 `left-out-everywhere-stops-start` | +| `C05RuleProbe` | `rule-qualified` | caught | 129 of 645 rows; #4 `rule-qualified` | +| `C05RuleProbe` | `nested-chain` | caught | 125 of 645 rows; #3 `nested-chain` | +| `C05RuleProbe` | `stays-the-errors` | caught | 376 of 645 rows; #2 `stays-the-errors` | +| `C05RuleProbe` | `unnamed-value-throws` | caught | 269 of 645 rows; #0 `unnamed-value-throws` | +| `C05RuleProbe` | `no-namespace` | caught | 247 of 645 rows; #2 `no-namespace` | +| `C06FaultKindsProbe` | `kind-gives-status` | caught | 131 of 306 rows; #3 `kind-gives-status` | +| `C06FaultKindsProbe` | `description-as-detail` | caught | 260 of 306 rows; #1 `description-as-detail` | +| `C06FaultKindsProbe` | `no-description-no-detail` | caught | 306 of 306 rows; #0 `no-description-no-detail` | +| `C06FaultKindsProbe` | `member-must-have-kind` | caught | 306 of 306 rows; #0 `member-must-have-kind` | +| `C07BlanketMappingProbe` | `by-kind` | caught | 40 of 411 rows; #22 `by-kind` | +| `C07BlanketMappingProbe` | `own-mapping` | caught | 27 of 411 rows; #2 `own-mapping` | +| `C07BlanketMappingProbe` | `own-set-keeps-mapping` | caught | 64 of 411 rows; #1 `own-set-keeps-mapping` | +| `C07BlanketMappingProbe` | `enum-needs-own-mapping` | caught | 66 of 411 rows; #0 `enum-needs-own-mapping` | +| `C07BlanketMappingProbe` | `member-without-kind-stops-start` | caught | 33 of 411 rows; #7 `member-without-kind-stops-start` | +| `C07BlanketMappingProbe` | `mapping-refusal-stops-start` | caught | 70 of 411 rows; #4 `mapping-refusal-stops-start` | +| `C08SameNameWarningProbe` | `bare-names-warn` | caught | 90 of 302 rows; #0 `bare-names-warn` | +| `C08SameNameWarningProbe` | `qualified-chain-warns` | caught | 74 of 302 rows; #8 `qualified-chain-warns` | +| `C08SameNameWarningProbe` | `warning-names-both` | caught | 164 of 302 rows; #0 `warning-names-both` | +| `C08SameNameWarningProbe` | `qualified-names-fix` | caught | 74 of 302 rows; #8 `qualified-names-fix` | +| `C08SameNameWarningProbe` | `leave-one-out-fixes` | caught | 138 of 302 rows; #1 `leave-one-out-fixes` | +| `C09BaseRecordEqualityProbe` | `in-a-collection-of-the-base` | caught | 12 of 101 rows; #12 `in-a-collection-of-the-base` | +| `C09BaseRecordEqualityProbe` | `equal-by-the-wrapped-value` | caught | 21 of 101 rows; #1 `equal-by-the-wrapped-value` | +| `C10PreserveGuardProbe` | `refused-where-references-could-be-written` | caught | 4 of 306 rows; #221 `refused-where-references-could-be-written` | +| `C10PreserveGuardProbe` | `value-converters-unaffected` | caught | 4 of 306 rows; #59 `value-converters-unaffected` | +| `C10PreserveGuardProbe` | `own-converters-unaffected` | caught | 15 of 306 rows; #1 `own-converters-unaffected` | +| `C10PreserveGuardProbe` | `ignore-cycles-unaffected` | caught | 4 of 306 rows; #72 `ignore-cycles-unaffected` | +| `C11ParseShapesBuildProbe` | `implicit-result` | caught | 28 of 247 rows; #2 `implicit-result` | +| `C11ParseShapesBuildProbe` | `ref-text` | caught | 22 of 247 rows; #4 `ref-text` | +| `C11ParseShapesBuildProbe` | `abstract-string-constructor` | caught | 22 of 247 rows; #3 `abstract-string-constructor` | +| `C11ParseShapesBuildProbe` | `parsing-where-it-can-be-parsed` | caught | 16 of 247 rows; #37 `parsing-where-it-can-be-parsed` | +| `C12DescribedConverterProbe` | `own-converter-describes-itself` | caught | 115 of 301 rows; #1 `own-converter-describes-itself` | +| `C12DescribedConverterProbe` | `options-describe` | caught | 30 of 301 rows; #26 `options-describe` | +| `C12DescribedConverterProbe` | `every-position` | caught | 145 of 301 rows; #1 `every-position` | +| `C12DescribedConverterProbe` | `parameter-keeps-aspnet` | caught | 73 of 301 rows; #2 `parameter-keeps-aspnet` | +| `C13PatchRequiredProbe` | `doc-required-where-rejected` | caught | 35 of 296 rows; #4 `doc-required-where-rejected` | +| `C13PatchRequiredProbe` | `doc-optional-where-absent` | caught | 120 of 296 rows; #0 `doc-optional-where-absent` | +| `C13PatchRequiredProbe` | `absent-is-default` | caught | 200 of 296 rows; #0 `absent-is-default` | +| `C14DeclaredProblemProbe` | `declared-describes-fault` | caught | 30 of 421 rows; #15 `declared-describes-fault` | +| `C14DeclaredProblemProbe` | `declared-names-types` | caught | 77 of 421 rows; #1 `declared-names-types` | +| `C14DeclaredProblemProbe` | `ascending-with-declared` | caught | 105 of 421 rows; #1 `ascending-with-declared` | +| `C14DeclaredProblemProbe` | `application-json-declared` | caught | 38 of 421 rows; #1 `application-json-declared` | +| `C15MvcFaultStatusDocProbe` | `builder-reaches-doc` | caught | 24 of 405 rows; #30 `builder-reaches-doc` | +| `C15MvcFaultStatusDocProbe` | `without-faults-leaves-doc` | caught | 45 of 405 rows; #20 `without-faults-leaves-doc` | +| `C15MvcFaultStatusDocProbe` | `status-per-scope` | caught | 76 of 405 rows; #0 `status-per-scope` | +| `C16OptionsAgreementProbe` | `differing-refused` | caught | 42 of 296 rows; #6 `differing-refused` | +| `C16OptionsAgreementProbe` | `error-names-type-and-descriptions` | caught | 42 of 296 rows; #6 `error-names-type-and-descriptions` | +| `C16OptionsAgreementProbe` | `patch-held-refused` | caught | 42 of 296 rows; #6 `patch-held-refused` | +| `C16OptionsAgreementProbe` | `same-converters-build` | caught | 86 of 296 rows; #1 `same-converters-build` | +| `C16OptionsAgreementProbe` | `minimal-host-not-compared` | caught | 25 of 296 rows; #0 `minimal-host-not-compared` | +| `C17Cmtk0014Probe` | `a-cast-naming-no-member` | caught | 75 of 283 rows; #4 `a-cast-naming-no-member` | +| `C17Cmtk0014Probe` | `default-and-zero` | caught | 122 of 283 rows; #0 `default-and-zero` | +| `C17Cmtk0014Probe` | `constructor-accessibility-too` | caught | 32 of 283 rows; #4 `constructor-accessibility-too` | +| `C17Cmtk0014Probe` | `reported-where-written` | caught | 139 of 283 rows; #0 `reported-where-written` | +| `C17Cmtk0014Probe` | `a-member-is-silent` | caught | 43 of 283 rows; #3 `a-member-is-silent` | +| `C18Cmtk1023Probe` | `names-each-base-member` | caught | 130 of 280 rows; #0 `names-each-base-member` | +| `C18Cmtk1023Probe` | `a-warning-and-generated` | caught | 280 of 280 rows; #0 `a-warning-and-generated` | +| `C18Cmtk1023Probe` | `generated-equality-never-calls-the-base` | caught | 83 of 280 rows; #4 `generated-equality-never-calls-the-base` | +| `C19Cmtk0010KindsProbe` | `methods-the-compiler-calls` | caught | 59 of 282 rows; #6 `methods-the-compiler-calls` | +| `C19Cmtk0010KindsProbe` | `interface-and-unsealed` | caught | 32 of 282 rows; #1 `interface-and-unsealed` | +| `C19Cmtk0010KindsProbe` | `value-object-and-closed-set` | caught | 28 of 282 rows; #0 `value-object-and-closed-set` | +| `C19Cmtk0010KindsProbe` | `flags-enum-and-read-only-struct` | caught | 33 of 282 rows; #2 `flags-enum-and-read-only-struct` | +| `C19Cmtk0010KindsProbe` | `a-pair-once-at-the-later-argument` | caught | 80 of 282 rows; #6 `a-pair-once-at-the-later-argument` | +| `C20ShouldHoldProbe` | `fails-unless-it-holds-that-reason` | caught | 111 of 261 rows; #0 `fails-unless-it-holds-that-reason` | +| `C20ShouldHoldProbe` | `whichever-of-its-types` | caught | 120 of 261 rows; #1 `whichever-of-its-types` | +| `C20ShouldHoldProbe` | `hands-back-detail-and-rule` | caught | 111 of 261 rows; #0 `hands-back-detail-and-rule` | +| `C20ShouldHoldProbe` | `uninitialized-fails` | caught | 28 of 261 rows; #6 `uninitialized-fails` | +| `C20ShouldHoldProbe` | `a-failure-says-the-outcome` | caught | 122 of 261 rows; #2 `a-failure-says-the-outcome` | +| `C21OpenapiSchemaParityProbe` | `the-underlying-types-schema` | caught | 10 of 294 rows; #15 `the-underlying-types-schema` | +| `C21OpenapiSchemaParityProbe` | `container-elements` | caught | 16 of 294 rows; #0 `container-elements` | +| `C21OpenapiSchemaParityProbe` | `dictionary-key` | caught | 1 of 294 rows; #137 `dictionary-key` | +| `C21OpenapiSchemaParityProbe` | `every-document-version` | caught | 52 of 294 rows; #5 `every-document-version` | +| `C21OpenapiSchemaParityProbe` | `nullable-admits-null-as-the-wrapped-does` | caught | 7 of 294 rows; #63 `nullable-admits-null-as-the-wrapped-does` | +| `C22GeneratorRefusalsProbe` | `every-error-links-the-table` | caught | 79 of 144 rows; #1 `every-error-links-the-table` | +| `C22GeneratorRefusalsProbe` | `unsealed-record-class` | caught | 21 of 144 rows; #5 `unsealed-record-class` | +| `C22GeneratorRefusalsProbe` | `over-value-object` | caught | 20 of 144 rows; #1 `over-value-object` | +| `C22GeneratorRefusalsProbe` | `generic-wrapped` | caught | 24 of 144 rows; #3 `generic-wrapped` | +| `C22GeneratorRefusalsProbe` | `abstract` | caught | 20 of 144 rows; #4 `abstract` | +| `C22GeneratorRefusalsProbe` | `over-system-enum` | caught | 7 of 144 rows; #8 `over-system-enum` | +| `C22GeneratorRefusalsProbe` | `named-as-generated-member` | caught | 20 of 144 rows; #2 `named-as-generated-member` | +| `C22GeneratorRefusalsProbe` | `closed-set-named-as-generated-member` | caught | 4 of 144 rows; #11 `closed-set-named-as-generated-member` | +| `C22GeneratorRefusalsProbe` | `equality-operator` | caught | 20 of 144 rows; #0 `equality-operator` | +| `C22GeneratorRefusalsProbe` | `no-type-half-generated` | caught | 79 of 144 rows; #1 `no-type-half-generated` | +| `C22GeneratorRefusalsProbe` | `over-object` | caught | 6 of 144 rows; #15 `over-object` | +| `E01ColumnParityProbe` | `column-type` | caught | 12 of 175 rows; #0 `column-type` | +| `E01ColumnParityProbe` | `nullable` | caught | 36 of 175 rows; #1 `nullable` | +| `E01ColumnParityProbe` | `facet-kept` | caught | 108 of 175 rows; #1 `facet-kept` | +| `E02ValueTranslatesProbe` | `value` | caught | 58 of 215 rows; #0 `value` | +| `E02ValueTranslatesProbe` | `get-value` | caught | 58 of 215 rows; #1 `get-value` | +| `E02ValueTranslatesProbe` | `value-or-null` | caught | 51 of 215 rows; #2 `value-or-null` | +| `E02ValueTranslatesProbe` | `column-not-cast` | caught | 203 of 215 rows; #0 `column-not-cast` | +| `E03ColumnRoundTripProbe` | `stored-as-underlying` | caught | 5 of 177 rows; #13 `stored-as-underlying` | +| `E03ColumnRoundTripProbe` | `keys` | caught | 35 of 177 rows; #0 `keys` | +| `E03ColumnRoundTripProbe` | `nullable-properties` | caught | 35 of 177 rows; #1 `nullable-properties` | +| `E04IntegerKeyProbe` | `generated-on-add` | caught | 5 of 28 rows; #8 `generated-on-add` | +| `E04IntegerKeyProbe` | `byte-not-generated` | caught | 1 of 28 rows; #9 `byte-not-generated` | +| `E04IntegerKeyProbe` | `char-key-yours` | caught | 1 of 28 rows; #16 `char-key-yours` | +| `E04IntegerKeyProbe` | `guid-key-yours` | caught | 1 of 28 rows; #12 `guid-key-yours` | +| `E04IntegerKeyProbe` | `not-on-efs-list` | caught | 1 of 28 rows; #7 `not-on-efs-list` | +| `E04IntegerKeyProbe` | `own-configuration-wins` | caught | 2 of 28 rows; #18 `own-configuration-wins` | +| `E05OwnCompareToProbe` | `comparison` | caught | 30 of 90 rows; #0 `comparison` | +| `E05OwnCompareToProbe` | `ordering` | caught | 24 of 90 rows; #7 `ordering` | +| `E05OwnCompareToProbe` | `min-max` | caught | 12 of 90 rows; #4 `min-max` | +| `E05OwnCompareToProbe` | `equality-allowed` | caught | 6 of 90 rows; #1 `equality-allowed` | +| `E05OwnCompareToProbe` | `value-order-allowed` | caught | 6 of 90 rows; #11 `value-order-allowed` | +| `E05OwnCompareToProbe` | `query-filter` | caught | 12 of 90 rows; #2 `query-filter` | +| `E06OwnConverterNoValueProbe` | `value-not-translated` | caught | 5 of 12 rows; #1 `value-not-translated` | +| `E06OwnConverterNoValueProbe` | `value-object-comparison` | caught | 7 of 12 rows; #0 `value-object-comparison` | +| `E07ClosedSetNameProbe` | `stored-as-name` | caught | 3 of 9 rows; #2 `stored-as-name` | +| `E07ClosedSetNameProbe` | `in-collections-and-json` | caught | 2 of 9 rows; #2 `in-collections-and-json` | +| `E07ClosedSetNameProbe` | `unknown-name-throws` | caught | 3 of 9 rows; #0 `unknown-name-throws` | +| `E07ClosedSetNameProbe` | `non-member-throws` | caught | 3 of 9 rows; #3 `non-member-throws` | +| `E08OptionResultNotColumnsProbe` | `model-fails` | caught | 10 of 20 rows; #4 `model-fails` | +| `E08OptionResultNotColumnsProbe` | `error-says-so` | caught | 20 of 20 rows; #0 `error-says-so` | +| `E09OrNoneInDbProbe` | `first-reads-one-row` | caught | 3 of 18 rows; #2 `first-reads-one-row` | +| `E09OrNoneInDbProbe` | `single-asks-for-two` | caught | 1 of 18 rows; #9 `single-asks-for-two` | +| `E09OrNoneInDbProbe` | `element-at-skips` | caught | 3 of 18 rows; #4 `element-at-skips` | +| `E10ReadBackSkipsValidationProbe` | `reads-without-create` | caught | 30 of 30 rows; #0 `reads-without-create` | +| `E10ReadBackSkipsValidationProbe` | `revalidate-finds` | caught | 30 of 30 rows; #0 `revalidate-finds` | +| `E11UseTypeKitOrderProbe` | `order-does-not-matter` | caught | 14 of 14 rows; #0 `order-does-not-matter` | +| `E12ConventionByTypeProbe` | `value-object-type-reaches` | caught | 4 of 12 rows; #1 `value-object-type-reaches` | +| `E12ConventionByTypeProbe` | `underlying-type-does-not-reach` | caught | 7 of 12 rows; #0 `underlying-type-does-not-reach` | +| `E13PrimitiveCollectionProbe` | `element-value` | caught | 5 of 10 rows; #0 `element-value` | +| `E13PrimitiveCollectionProbe` | `contains` | caught | 5 of 10 rows; #1 `contains` | +| `E14InheritanceKeyProbe` | `tph-tpt-root` | caught | 4 of 9 rows; #0 `tph-tpt-root` | +| `E14InheritanceKeyProbe` | `tpc-assign-on-sqlite` | caught | 2 of 9 rows; #4 `tpc-assign-on-sqlite` | +| `E15CompositeKeyProbe` | `as-for-primitives` | caught | 6 of 8 rows; #0 `as-for-primitives` | +| `E16OwnConfigurationWinsProbe` | `never` | caught | 2 of 25 rows; #17 `never` | +| `E16OwnConfigurationWinsProbe` | `on-add` | caught | 2 of 25 rows; #9 `on-add` | +| `E16OwnConfigurationWinsProbe` | `column-type` | caught | 5 of 25 rows; #0 `column-type` | +| `E16OwnConfigurationWinsProbe` | `conversion` | caught | 5 of 25 rows; #1 `conversion` | +| `E16OwnConfigurationWinsProbe` | `optional` | caught | 5 of 25 rows; #2 `optional` | +| `L01JsonBytesProbe` | `as-a-dictionary-key-too` | caught | 11 of 581 rows; #56 `as-a-dictionary-key-too` | +| `L01JsonBytesProbe` | `the-underlying-types-json` | caught | 12 of 581 rows; #11 `the-underlying-types-json` | +| `L01JsonBytesProbe` | `read-as-the-underlying-type-reads` | caught | 26 of 581 rows; #20 `read-as-the-underlying-type-reads` | +| `L02NoQuoteProbe` | `parsing-never-repeats` | caught | 183 of 586 rows; #0 `parsing-never-repeats` | +| `L02NoQuoteProbe` | `json-never-repeats` | caught | 199 of 586 rows; #0 `json-never-repeats` | +| `L02NoQuoteProbe` | `no-inner-that-quotes` | caught | 22 of 586 rows; #5 `no-inner-that-quotes` | +| `L03OneRuleProbe` | `parsing-runs-create` | caught | 109 of 586 rows; #3 `parsing-runs-create` | +| `L03OneRuleProbe` | `tryfrom-runs-create` | caught | 30 of 586 rows; #7 `tryfrom-runs-create` | +| `L03OneRuleProbe` | `json-runs-create` | caught | 157 of 586 rows; #0 `json-runs-create` | +| `L03OneRuleProbe` | `refusals-name-the-fault` | caught | 196 of 586 rows; #0 `refusals-name-the-fault` | +| `L04RoundTripProbe` | `invariant-whatever-the-culture` | caught | 21 of 290 rows; #15 `invariant-whatever-the-culture` | +| `L04RoundTripProbe` | `the-type-converter-writes-the-same` | caught | 5 of 290 rows; #14 `the-type-converter-writes-the-same` | +| `L04RoundTripProbe` | `text-reads-back` | caught | 1 of 290 rows; #64 `text-reads-back` | +| `L04RoundTripProbe` | `json-refusal-is-a-json-exception` | caught | 45 of 290 rows; #10 `json-refusal-is-a-json-exception` | +| `L05EqualityProbe` | `through-every-base` | caught | 41 of 309 rows; #0 `through-every-base` | +| `L05EqualityProbe` | `equality-is-the-underlying-values` | caught | 22 of 309 rows; #2 `equality-is-the-underlying-values` | +| `L05EqualityProbe` | `ordering-follows-the-underlying-type` | caught | 238 of 309 rows; #0 `ordering-follows-the-underlying-type` | +| `L06OffTheWireProbe` | `never-written` | caught | 268 of 325 rows; #0 `never-written` | +| `L06OffTheWireProbe` | `never-read` | caught | 267 of 325 rows; #0 `never-read` | + +**Tests**, through `build-verdict test --project P` at 5c5affe: Prober.Tests 268 passed (11 new: `FixTests`, +`CoveringTests`, one `GapsTests` case); Conventions.Tests 1,491 passed; Facts 87 passed, 9 skipped (the PostgreSQL facts, +without `TYPEKIT_PG`). `docs-query check`: 0 problems. Each new test fails against a seeded defect, one change at a +time: `fix` writing the commit as given, accepting any status, taking an unknown id, dropping the claim or the probe, +accepting any commit; `Covering` picking a blocked value, the seeded fill taking the first value or ignoring +exclusions; `gaps` counting a check that did not pass, an obligation's reads for a pair it does not want, a claim +without evidence, or no section. `ToolchainTests.A_dotnet_without_the_pinned_sdk_falls_back_to_the_installers` failed +with `PROBE_STATE_DIR` set and passes with it set now. A removed note key called again fails the probes' build with +CS0117. + +**The gate**, `prober gate --since v2.1.0` on the final replay's state: + +``` +probe gate: refused for build/ws2-close-out since v2.1.0 (44 claim(s) touched, 2838 pair(s) uncovered): + C22GeneratorRefusalsProbe: not replayed at its current identity and footprint (run `prober replay --all`) + C02-optional-text: 7 of 126 pairs uncovered (touched since v2.1.0) + C03-composed-faults: 2 of 15607 pairs uncovered (touched since v2.1.0) + C05-with-rule: 2 of 15783 pairs uncovered (touched since v2.1.0) + C12-described-converter: 821 of 14904 pairs uncovered (touched since v2.1.0) + C13-patch-required: 48 of 14731 pairs uncovered (touched since v2.1.0) + C16-options-agreement: 455 of 14904 pairs uncovered (touched since v2.1.0) + C17-cmtk0014: 41 of 1832 pairs uncovered (touched since v2.1.0) + C19-cmtk0010-kinds: 50 of 2292 pairs uncovered (touched since v2.1.0) + C21-openapi-schema-parity: 698 of 13883 pairs uncovered (touched since v2.1.0) + C22-generator-refusals: 621 of 621 pairs uncovered (touched since v2.1.0) + E02-value-translates-to-column: 6 of 406 pairs uncovered (touched since v2.1.0) + L01-json-bytes: 5 of 6478 pairs uncovered (touched since v2.1.0) + L02-no-quote: 74 of 8314 pairs uncovered (touched since v2.1.0) + L03-one-rule: 5 of 7084 pairs uncovered (touched since v2.1.0) + L04-round-trip: 2 of 8314 pairs uncovered (touched since v2.1.0) + L06-off-the-wire: 1 of 8022 pairs uncovered (touched since v2.1.0) +``` + +| Refusal | Since | What it needs | +|---|---|---| +| C22GeneratorRefusalsProbe not current at its footprint | #47 | The probe builds its consumer in a child process, so the in-process coverage shows no file under `src/` and `footprint` refuses an empty one (#54): `watched` paths for the generators, as C18 has, so its footprint records them. Its 621 pairs follow. | +| C12 821, C13 48, C16 455, C21 677 | this record | Checks that compared nothing but a document both hosts refuse no longer pass. A declaration of the document obligations' inapplicability under `json.ReferenceHandler=Preserve` and `json.MaxDepth=3`, citing record 0099 or a fact, and rows (`rows --probe`) for the pairs that remain. C21's other 21 were open at ac4c561. | +| C17 41, C19 50 | #49, #54 | The stored rows substitute excluded refused kinds and credit other values; rows written again with `rows --probe`, which now leaves such an axis out instead. | +| L02 74 | #57 | The own-converter pairs of its TryFrom and formatting rows, whose only check is declared inapplicable there: a declaration on the claim's other obligations or rows that reach them. | +| E02 6 | before WS2 (#56) | `where-value-compare` with a wrapped type that has no `>=` (the probe substitutes `where-equals`): an exclusion with that reason; and one `group-by` row for `nested-two-levels` × `DateTimeOffset`. | +| C02 7, C03 2, C05 2, L01 5, L03 5, L04 2, L06 1 | before or during WS2 | Scattered pairs no stored row holds: rows from `rows --claim`, or exclusions with their reasons. | + +None is a finding: 0 are open. Every refusal is answered by a probe's rows, a declaration, an exclusion, or a waiver naming the maintainer as reviewer (decision 97); this record answers none of them. diff --git a/design/adr/README.md b/design/adr/README.md index a3452bbb..b4692bc5 100644 --- a/design/adr/README.md +++ b/design/adr/README.md @@ -58,7 +58,7 @@ per area ([design](../plan.md)) link to the records they rest on. | [0049](0049-fault-responses-client-can-act.md) | Fault responses a client can act on | 2026-10-13 | accepted | | [0050](0050-warnings-cmtk0003-alone-becomes-error-2.md) | Of the Warnings, CMTK0003 alone becomes an Error in 2.0.0 | 2026-10-13 | accepted | | [0051](0051-closed-set-parses-ignoring-case-only.md) | A closed set parses ignoring case only when asked, and refuses members that differ only in case | 2026-10-13 | accepted | -| [0052](0052-cause-on-fault-joins-2-1.md) | A `Cause` on `Fault` joins 2.1 | 2026-10-13 | accepted; moved to 2.2 by [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md) | +| [0052](0052-cause-on-fault-joins-2-1.md) | A `Cause` on `Fault` joins 2.1 | 2026-10-13 | accepted; moved to 2.2 by [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md); to 2.3 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | | [0053](0053-fault-made-by-fault-of-alone.md) | A `Fault` is made by `Fault.Of` alone | 2026-10-13 | accepted | | [0054](0054-abstract-value-object-refused.md) | An abstract type with a value-object marker is refused (CMTK1022) | 2026-10-13 | accepted | | [0055](0055-component-name-claimed-twice-refused.md) | A component name two types claim is refused; a qualified fault name follows the nesting chain | 2026-10-13 | accepted | @@ -69,8 +69,8 @@ per area ([design](../plan.md)) link to the records they rest on. | [0060](0060-value-object-over-closed-set-generated-in-full.md) | A value object over a closed set is generated in full wherever the set is declared | 2026-10-13 | accepted | | [0061](0061-scope-s-fault-status-applies-where-fault-type-answered.md) | A scope's fault status applies where its fault type is answered | 2026-10-13 | accepted | | [0062](0062-asynchronous-callbacks-run-where-awaited-task-completed.md) | An asynchronous callback runs where the task it waited for completed | 2026-10-13 | accepted | -| [0063](0063-char-keys-application-assigned-ulong-documented.md) | A key over `char` is the application's to assign; a default key over `ulong` throws at `Add` | 2026-10-13 | accepted | -| [0064](0064-fault-name-reaches-client-causes-not-told-apart-one-fault.md) | A fault's name reaches the client; causes it must not tell apart are one fault | 2026-10-13 | accepted; its opt-in moved to 2.2 by [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md) | +| [0063](0063-char-keys-application-assigned-ulong-documented.md) | A key over `char` is the application's to assign; a default key over `ulong` throws at `Add` | 2026-10-13 | accepted; to 2.3 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | +| [0064](0064-fault-name-reaches-client-causes-not-told-apart-one-fault.md) | A fault's name reaches the client; causes it must not tell apart are one fault | 2026-10-13 | accepted; its opt-in moved to 2.2 by [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md); to 2.3 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | | [0065](0065-2-0-0-ships-without-preview.md) | 2.0.0 ships without a preview | 2026-10-14 | accepted | | [0066](0066-precondition-failed-malformed-fault-kinds.md) | Two more fault kinds: `PreconditionFailed` (412) and `Malformed` (400) | 2026-10-14 | accepted | | [0067](0067-rule-that-refused-on-fault.md) | A fault names the rule that refused, as a member of a fixed set | 2026-10-14 | accepted | @@ -94,9 +94,24 @@ per area ([design](../plan.md)) link to the records they rest on. | [0085](0085-an-interface-is-no-value-object.md) | An interface is no value object to `[ValueObject]` | 2026-10-14 | accepted | | [0086](0086-a-nullable-s-converter-is-seen-through-only-when-it-is-the-serializer-s-adapter.md) | A nullable's converter is seen through only where it is the serializer's adapter, and a hand-built nullable contract is described by its value's | 2026-10-14 | accepted | | [0087](0087-a-deeper-base-s-explicit-equatable-is-hidden-by-the-value-object.md) | A deeper base's explicit `IEquatable` is taken over by a `new Equals(TBase)` the value object declares | 2026-10-14 | accepted | -| [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md) | 2.1.0 ships without its open roadmap items, which move to 2.2 | 2026-10-14 | accepted | +| [0088](0088-2-1-0-ships-without-its-open-roadmap-items.md) | 2.1.0 ships without its open roadmap items, which move to 2.2 | 2026-10-14 | accepted; 2.2 items moved to 2.3 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | | [0089](0089-cmtk0010-at-methods-bound-by-pattern.md) | CMTK0010 checks a method bound by pattern, for the kinds 2.1.0 adds; 2.0's four join at 3.0.0 | 2026-10-14 | accepted | | [0090](0090-faults-alike-are-a-warning-until-3-0.md) | Two fault types writing one name are a startup warning until 3.0.0 | 2026-10-14 | accepted | | [0091](0091-an-exclusion-narrows-a-wider-status.md) | An endpoint's exclusion narrows a wider scope's status; a status every endpoint leaves out is refused | 2026-10-14 | accepted | -| [0092](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) | Repository tooling is .NET, a file-based app or a tool project by how it is used; git and the pinned SDK are the only prerequisites; the tests run on Linux, Windows and macOS; CI never depends on an AI | 2026-10-15 | accepted | -| [0093](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md) | Verified facts are pinned, dated and executable where they can be: one fact per file in `design/knowledge/`, a characterization test per observable fact, and `docs-query check` holding the pins to what is installed | 2026-10-15 | accepted | +| [0092](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) | Repository tooling is .NET, a file-based app or a tool project by how it is used; git and the pinned SDK are the only prerequisites; the tests run on Linux, Windows and macOS; CI never depends on an AI | 2026-10-15 | accepted; session-start builds by [0105](0105-every-tool-is-built-in-the-session-s-checkout.md) | +| [0093](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md) | Verified facts are pinned, dated and executable where they can be: one fact per file in `design/knowledge/`, a characterization test per observable fact, and `docs-query check` holding the pins to what is installed | 2026-10-15 | accepted; a cited fact is executable and an answer is a fixture beside the test by [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) | +| [0094](0094-topic-branches-into-a-release-branch-and-fix-trailers.md) | From 2.2.0, topic branches merge into a release branch with merge commits only, and every `fix:` commit carries `Defect-Class:` and `Found-By:` trailers, held by a test | 2026-10-15 | accepted | +| [0095](0095-a-fix-is-tested-across-its-input-class.md) | A fix is tested across its input class: the fixer names the class, runs the fix's test over that class's variant catalogue (`TypeArgumentSites` for a type argument, the `shape.*` axes, the settings axes) and fixes the siblings it finds; `Found-By: VA` | 2026-10-15 | accepted | +| [0096](0096-ci-replays-every-probe-and-the-release-runs-the-gate.md) | CI replays every probe, from an empty state, on every push and pull request, and a failing probe fails the run; the release replays them and runs the gate before it packs, the gate advisory; held by `ReleaseWiringTests` | 2026-10-15 | accepted; the gate binds from 2.2.0 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md); the build workflow's replay runs by family and footprint by [0101](0101-ci-replays-by-family-and-footprint.md) | +| [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | 2.2.0 is a consolidation release: no public API, its additive items move to 2.3.0, and the probe gate binds from it | 2026-10-15 | accepted | +| [0098](0098-a-null-passes-no-comparison-in-a-query.md) | A nullable record-class value object compared with `<`, `<=`, `>` or `>=` in a query answers as SQL does, leaving out its null rows; documented, not refused | 2026-10-15 | accepted | +| [0099](0099-a-probe-row-says-how-it-ended.md) | A probe row says how it ended: notes are a closed catalogue of keys and outcomes, a row that earned nothing is a skipped test labelled with its settings, a skip for a finding fails the replay once the finding is fixed and the gate while it stands, and a refused document is judged against ASP.NET alone | 2026-10-15 | accepted; note keys are a typed catalogue the compiler checks by [0103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md); a refused document is compared by type and stage, and earns nothing in a check that compared nothing else, by [0108](0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md) | +| [0100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md) | Every registered probe names a seeded product defect, and `prober seeds` (local, a build per seed) requires a violation it causes; the prober tests hold that every seed exists and applies; pairwise coverage is a floor, three-way combinations belong to the variant catalogue | 2026-10-15 | accepted; a seed per obligation of a claim by [0104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md); seeds run by footprint, a release `--all`, by [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) | +| [0101](0101-ci-replays-by-family-and-footprint.md) | The build workflow replays the probes in legs by family, each running its probes in parallel, under a time limit: a push every probe with coverage and the merged state saved, a pull request only the probes it stales against its base branch's saved state and without a footprint; `prober merge` joins the legs and refuses a probe no leg saw | 2026-10-16 | accepted | +| [0102](0102-an-exclusion-carries-its-reason.md) | Each `exclude` entry of claims.json is a required one-sentence reason and its pairs; the prober refuses a claims file its schema does not match, naming the claim and the field, so an invented reason field is refused; `prober universe` prints each exclusion's reason beside the claim's narrowings; the prober writes JSON with only JSON's own escapes, so an unchanged publish leaves a tracked file as it is | 2026-10-16 | accepted | +| [0103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md) | A harness error is never a catch: `Rec.Run` ends a row whose check throws as a harness error, not a violation, and `prober seeds` credits only a violation no harness error raised; note keys are members of a typed `NoteKeys` the compiler checks, and `register`, `publish` and a conventions test refuse a note or a skip whose key or finding is not written out | 2026-10-16 | accepted | +| [0104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md) | A claim lists its `obligations`, each a sentence quoted verbatim from its source, and what it does not promise in `not-promised`, each quoted from the document or record that makes it intended; a claim without the field has one obligation, `claim`; a probe carries a seed per obligation it checks, and the check without a build refuses a seed naming an unlisted obligation and a listed obligation no seed names; `status` shows each claim's obligations, and `prober gaps` reports what the corpus leaves out, deciding nothing | 2026-10-16 | accepted; `gaps` reads the last replay's evidence too by [0108](0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md) | +| [0105](0105-every-tool-is-built-in-the-session-s-checkout.md) | SessionStart builds the guard in `$CLAUDE_PROJECT_DIR` and every other tool, through `tools/AgentTools.slnf`, in the session's own checkout; a hook, the prober, the smoke test and the verdict fall back to the SDK installer's `dotnet` when the one on PATH lacks the pinned SDK, and a session start that builds nothing tells the agent; the guard lets `dotnet msbuild -getProperty/-getItem` without a target through | 2026-10-17 | accepted | +| [0106](0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md) | A value object over a type the JSON options describe is written inline as a route, query, header or form parameter and as an element of a query or header array, where value objects are components: the component is its JSON positions'; every other value object keeps its reference; the schema below a parameter is asked for with the parameter's source and type alone | 2026-10-17 | accepted | +| [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) | A fact that prunes or excuses a row is executable: `docs-query check` fails `fact-not-executable-cited` where a refused entry, a not-promised entry, an obligation's declaration or the harness cites a fact without its test; a fact's measured answer is `KBNNN.fixture.json` beside its test, named by `fixture:`, read by a BCL-only reader the harness will use; `prober seeds` runs only the probes whose seeds identity or product footprint changed since every seed was caught, and a release runs `--all` | 2026-10-17 | accepted | +| [0108](0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md) | A probe row runs a check per obligation, each against a baseline, and the ledger credits checks; an excuse is a closed citation, applicability is declared on the obligation, the http, json and text harness compares three sides by exception type and stage, decision 99's refused document among them, and the twins and the context are generated with the shapes; `NoteKeys` keeps its pass keys and `framework-refuses`; `prober fix`, a covering array that places no blocked pair, and `gaps` listing the pairs no passing check read both axes of; WS2 closes, and WS3 to WS6 are dropped | 2026-10-17 | accepted; the gate passed on release/2.2.0 on 2026-10-18 (#62, with the fix #63 for the finding F4 the regenerated C21 rows raised) | diff --git a/design/aspects.md b/design/aspects.md index 310172e0..3c1a23bd 100644 --- a/design/aspects.md +++ b/design/aspects.md @@ -219,6 +219,12 @@ process to a +05:30 zone, since CI runs in UTC. - **`From(null)` throws** `ArgumentNullException` for a reference-type wrapped value, as `Option.Some(null)` does. It wrapped a null that `Value` promises it never holds. +- **Span parsing follows the wrapped type, not the strategy (2026-10-17).** `ISpanParsable` is + generated wherever the wrapped type implements it (`SupportsSpan`), as `IUtf8SpanParsable` already + was, and parses through the wrapped type's own span `TryParse`. The string strategy, picked before + the span check, implemented `IParsable` alone, so a value object over `string` had no span parsing + although `string` has it (the prober, claim C11). The string strategy still parses text as it is. + Span parsing is not added over a type without it (`Uri`, enums, NodaTime): that would be a feature. - **Enums** parse by name through `Enum.TryParse`, with a `FormatException` for an unknown name. `Convert.ChangeType` cannot make an enum from a string, so the former `IConvertible` strategy threw for every input while the type advertised `IParsable`. Nothing else that is convertible diff --git a/design/ef-core.md b/design/ef-core.md index 815e7682..b1b3a94b 100644 --- a/design/ef-core.md +++ b/design/ef-core.md @@ -44,7 +44,10 @@ `.Value` re-typed as the wrapped type queried the wrong one: `StartsWith("a")` against `"C:abc"` answered no row. The translators act only where the operand's to-provider expression is `ValueObjectConverter<,>.ProviderValue`, composed with an enum's converter where there is one, - which holds in a compiled model too; anywhere else EF refuses the query. + which holds in a compiled model too; anywhere else EF refuses a query that filters, orders, groups + or aggregates by `.Value`. A final `Select` or `Distinct` of it EF evaluates in memory, after the + application's converter has read the value object, and answers correctly (measured 2026-10-15, the + EF variant catalogue). - **A hand-written ordering is refused in a query (decided 2026-10-13, [decision 57](adr/0057-hand-written-ordering-refused-in-query.md)).** EF translates a comparison, an ordering and `Min`/`Max` to the column's order, not to a hand-written `CompareTo`. The comparable aspect marks such a type `[HandWrittenOrdering]`, and an @@ -56,6 +59,12 @@ model is built (and so at `dbcontext optimize` and `migrations add`). A comparison in a filter that reads neither the entity nor a query is left alone: EF evaluates it in memory, by `CompareTo` (measured: `Floor > From(2)` on the context kept the rows memory keeps). + - **A null passes no comparison in a query (decided 2026-10-15, [decision 98](adr/0098-a-null-passes-no-comparison-in-a-query.md)).** + A record class's generated operators sort null first, so `null < x` is true in memory, and SQL's + `"N" < @x` leaves the null row out, as for the underlying type's nullable column. Not refused: a + guard cannot tell a nullable record-class property from a required one by its type, and the answer + is the underlying type's. The EF README and `docs/ef-core.md` say so, with + `r.N == null || r.N < x` to keep the null rows. - **The element of a primitive collection is converted, not re-typed (decided 2026-09-28).** EF infers a collection's element mapping from the columns that read its table expression (`unnest`, `json_each`, `OPENJSON`, `VALUES`), and a re-typed element column handed it the wrapped type's diff --git a/design/finder-benchmark-2026-10-16.md b/design/finder-benchmark-2026-10-16.md new file mode 100644 index 00000000..66b433ad --- /dev/null +++ b/design/finder-benchmark-2026-10-16.md @@ -0,0 +1,217 @@ +# Finder benchmark, 2026-10-16: the escapes as seeds + +The 24 confirmed release escapes of the [fix history](fix-history.md#escapes-by-class) (escape `Y`), +each put back into today's code as a seed, and run against the two finders that need no model: the +test suite and the probe corpus. One row per escape, then totals in Magma's split (seeds that applied, +seeds a finder reached, seeds it detected), then the accounting every probe round and analyst run +reports from now on. This is the first half of the concept's WS5, detection of a reintroduced defect; the +second half, discovery of a defect no finder has seen, has not run. + +Measured at `265309f` (`release/2.2.0`), whose `src/` is the same as `e6cdb5e`'s, the commit the probe +footprints below were recorded at. The suite ran on the maintainer's Mac and on a Linux host (see +"Where it ran"), the probes on the Linux host; nothing was changed in the repository to run either. + +## What this shows and what it does not + +**Today's suite holds the regression test each of these fixes added.** A seed that reverses a fix is +therefore expected to fail that fix's own test, and does: the suite column measures that the guards are +still there and still fail for the right reason (the [verify-the-guard](../.claude/skills/verify-the-guard/SKILL.md) +loop, run again a week to a month later), not that the suite would have found the defect before it shipped. +Each suite cell says whose tests failed: **own** (added or extended by the fix itself), **later** (a test +added after the fix, which still knew the class of defect: a variant catalogue, a docs test), or **older** +(a test that existed before the fix). An older test that fails only on a fixture or an expected text the +fix itself added or changed counts as own. + +The interesting rows are the ones only a probe detects, or that nothing detects. The probe corpus was also +written after most of these fixes, and four of its claims are quoted from the 2.1.0 changelog's Fixed +list (C09, C11, C14, C15), so a probe's catch is not independent of the fix either; but no probe was written +from a seed, and none had been run against one before this. + +What it does not answer: how fast a finder finds a defect nobody has described to it (that needs held-out +seeds, a separate run), what a finder costs per defect (the suite and the corpus need no model; the rounds +that wrote the corpus are not costed here), or how the review sessions, outside reviews and the +code-analyst would fare (they found 21 of these 24, but they are not re-runnable without a model and a +budget, and they would be reading code that now carries the fix's comments). + +## Method + +**Seeds.** For each escape, the fix's changes to `src/**/*.cs` reverse-applied at `265309f`, the tests and +docs left as they are. Ten applied as `git diff -R ^ -- 'src/**/*.cs'` (the brief's +`git revert --no-commit`, restricted to the product source, since a revert of the whole commit removes the +fix's tests as well). Fourteen conflicted with later changes and were reconstructed by hand: the guard the +fix added is neutralised where it now sits, keeping every member the tests call (a condition that can no +longer hold, a call removed, a list cut back to the fix's parent), so the test projects compile and a test +fails for the defect, not for a missing symbol. A bundle fix (`e4b751c`, `92d01dd`) is seeded whole; of +`415ce73`, whose escape was one of five items, only that item. No seed was impossible to reconstruct: for +every fix a line of the guarded code remains. The patches are not kept in the repository. + +**Suite.** The whole solution through the verdict tool (`build-verdict.dll test`), once unseeded on each +host and once per seed. A test detects a seed when it fails seeded and passes unseeded on the same host. +`SeedsTests.Every_probe_in_the_repository_has_a_seed_that_applies` (the prober's tests) fails wherever a +seed touches a line a probe's own seed patches, which is a collision of two patches, not a detection; it +is left out of every count. + +**Probes.** `prober try` per probe, on a Linux host, against a commit holding the seed (`try` runs the +checkout's `HEAD`). The unseeded baseline ran every one of the 43 registered probes; all pass, no row +violates (L01's row #242 ends inconclusive, seeded and unseeded alike). A probe detects a seed as +`prober seeds` counts a catch ([decision 103](adr/0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md)): +a row that ends as a violation, raised no harness error, and does not violate unseeded. A seed was run +against the probes of the surfaces its fix touched (the surfaces in `claims.json`, matched to the files +the fix changed by the `watched` paths of [.claude/probe-axes.md](../.claude/probe-axes.md); a +Generators fix took the generation, text and JSON probes): HTTP 12 probes, EF 16, generators 13, +Option/Result 8. To save builds, the seeds of one surface ran together first and each probe that failed +was then run on each seed alone; every catch attributed this way is a single seed's, and a probe that +passed with all seeds of its surface applied is counted as detecting none of them. Where the probes' +own project no longer compiles under a seed, that is recorded as **build**: a probe's generated +declarations failed to build, which is the C11 claim's check ("these shapes build"). + +**Reached.** For a probe, a seed is reached when a probe it ran against executed a file the seed changes, +read from the replay's footprints (`footprints.jsonl`, the product files each probe ran, at `e6cdb5e`): +file-level, so an upper bound. A seed that changes only the Generators package is woven at build time +into every probe, and a footprint cannot say whether the woven code it changes ran; those rows say +**compile**. For the suite, reach was not measured: a suite run records no coverage, so the suite's +reached column is its detected column, a lower bound. + +**Time to detect** is the probe's own `prober try` time on the Linux host with the probes already built +(`--no-build`), which is what a replay of that probe costs after a commit; a cold build of the probes took +54 to 98 s more there. For a build catch, the build's time. + +## The table + +Class and finder are the fix history's (these commits predate the `Defect-Class` and `Found-By` trailers +of [decision 94](adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md), so the appendix of +the fix history is the source). Seed: **rev** reverse-applied, **hand** reconstructed. + +Suite cells name the failing tests; `xN` counts failing rows. **Probe** names the first probe and row that +caught the seed (`+n` more rows of it), and any other probe that caught it; **Reach** is the probes, of +those run against the seed, whose footprint holds a file the seed changes. A probe marked "(changelog)" checks a +claim quoted from the 2.1.0 changelog's Fixed list, which names that very fix. + +| # | Fix | Class | Found by | Seed | Suite | Probe | Probe s | Reach | +|---|---|---|---|---|---|---|---|---| +| 1 | `f66bb79` | FCI | US | hand | own: `ParameterTests.A_value_object_parameter_publishes_the_schema_of_a_wrapped_type_parameter` x4, the rows the fix added | BaseProbe #37 (+4): an MVC body parameter's schema differs from the wrapped type's | 35.7 | 11 of 12 | +| 2 | `b1bf428` | EFT | FR | hand | own: `QueryOptionTests` x3; later: `A_Func_or_a_method_group_reads_every_row…` (`9422c21`) | E09 #0 (+5): the condition did not reach the database | 4.6 | 8 of 8 | +| 3 | `1ef30be` | GTS | RS | hand | own: `BuildOutcomeTests` CMTK1022 x6, and the two older inventories of build errors, which miss the fix's CMTK1022 | none | | compile | +| 4 | `e779dd3` | CRP | RS | rev | own: `NullTaskTests.A_delegate_that_returns_no_task_is_refused_by_name` x20 | none | | 8 of 8 | +| 5 | `63471e7` | EFT | RS | rev | own: `ForeignConverterTests` x4; later: `QueryTranslationVariantTests` x627 (`a849b57`) | E06 #1 (+4): `.Value` on a column the application converts answered 0 rows instead of failing | 5.2 | 15 of 16 | +| 6 | `92dfe16` | EFT | RS | hand | own: `KeyGenerationTests` x4; later: `KeyGenerationVariantTests` x32 (`a849b57`) | E14 #2 (+3): a TPH root's default keys differ from the plain hierarchy's | 5.0 | 16 of 16 | +| 7 | `e644a8a` | EFT | RS | hand | own: `KeyGenerationTests` x13; later: `KeyGenerationVariantTests` x74, the ulong key at `Add` x2 (`84e9776`) | E04 #8 (+1): the value-object key threw where the plain key is generated | 4.9 | 16 of 16 | +| 8 | `717de54` | EFT | RS | hand | own: `HandWrittenOrderingTests` x17; later: `QueryTranslationVariantTests` x132 | E05 #0 (+65): a comparison by the own `CompareTo` answered instead of throwing | 6.7 | 15 of 16 | +| 9 | `719ee82` | EFT | RS | hand | own: `HandWrittenOrderingTests` query filter x3; later: `QueryTranslationVariantTests` x12 | E05 #2 (+11): a query filter by the own `CompareTo` answered instead of throwing | 6.4 | 16 of 16 | +| 10 | `8f6af8b` | EFT | SF | rev | own: `HandWrittenOrderingTests` "ThenBy a key cast to object" x2; later: `QueryTranslationVariantTests` x12 | none | | 15 of 16 | +| 11 | `53188ed` | GTS | SF | hand | own: `BuildOutcomeTests` x2 (CMTK1005 on a value object over `object`) | none | | compile | +| 12 | `e4b751c` | GTS | RS | hand | own: `BuildOutcomeTests`, `EqualityOperatorBuildTests`, 8 methods x18 | none | | compile | +| 13 | `a4e2fd7` | GTS | RS | rev | own: `ThrowingCreateTests.An_exception_from_Create_leaves_TryParse_as_it_leaves_Parse` x3 | none | | compile | +| 14 | `bef5770` | GTS | RS | rev | own: `DefaultInstanceFormattingTests` x5 | none | | compile | +| 15 | `9534751` | FCI | SF | rev | own: `InlinedValueObjectTests…inlined_enum…` x2 | none | | 11 of 12 | +| 16 | `92d01dd` | GTS | OR | hand | build: `Generators.Tests` does not compile (`ParsingTests.cs:67`, LAMA0611 CS0029) | build: C11 (changelog) (LAMA0611 CS0029 in its declarations) | 58 | 8 of 13, and compile | +| 17 | `415ce73` | FCI | OR | hand, 1 of 5 items | build: `Generators.Tests` does not compile (`ParsingTests.cs:36`, LAMA0611 CS0144) | build: C11 (changelog) (CS0144) | 60–70 | compile | +| 18 | `1ee02fc` | POS | PR | rev | own: `ParsingTests.A_type_whose_converter_was_looked_up_first_still_parses_through_it` x1 | BaseProbe #59 (+5): a form body's parse differs; also L02, L03, L04 | 26.6 | 5 of 13 | +| 19 | `4489c05` | GTS | RS | hand | own: `AssertionTests`, 8 methods: two the fix added, six whose expected text it changed; `ComposedFaultAssertionTests` x3 (`afdd1cd`) fail on that text too, not on the defect, and are not counted as later | none | | 1 of 8 | +| 20 | `b8f9432` | FCI | OR | rev | own: `PatchSchemaTests.A_patch_the_serializer_requires_stays_required` x2 | C13 #3 (+50): a required constructor parameter is not in `required` | 12.5 | 11 of 12 | +| 21 | `27c59d0` | FCI | SF | hand | own: `DescribedSchemaTests` x1; later: `A_factory_in_the_options_is_asked…` (`1b6c805`) | none | | 11 of 12 | +| 22 | `d565948` | FCI | CA | rev | own: `FaultExclusionTests` x2, `FaultStatusScopeTests` x1 | C04 #1 (+33): a status set on `MapControllers()` is missing; also C06, C07, C14 (changelog), C15 (changelog) | 22.6 | 11 of 12 | +| 23 | `92f9bc4` | FCI | CA | hand | own: `FaultResponseTests` x2; later: the declared-problem matrix x252 (`e32a94c`) | C14 (changelog) #1 (+76): a declared 404 names no problem type | 35.7 | 11 of 12 | +| 24 | `461c920` | FCI | PR | rev | own: `ParameterTests.A_parameter_MVC_names_no_type_for…` x3 | BaseProbe #11 (+7): `AddTypeKit()` fails the document; also C03 to C07, C14, C15 | 32.3 | 12 of 12 | + +The suite ran on the Mac for rows 1 to 5 and on the Linux host for the rest. The `SeedsTests` collision +(see Method) came up for 9 seeds and is left out above. + +## Totals + +| Finder | Seeds that applied | Reached | Detected | Detected by a test or row the fix did not add | +|---|---|---|---|---| +| Test suite | 24 | not measured; at least the 24 it detected | 24: 22 by a failing test, 2 by the test projects' build | 9, each a test added after the fix (rows 2, 5 to 10, 21, 23); none older than the fix | +| Probe corpus | 24 | 18 by footprint; the other 6 change the Generators only (compile) | 14: 12 by a row, 2 by the build of C11's declarations | 11 (the 14 less the 3 caught first by a claim quoted from the fix's changelog line: rows 16, 17, 23) | +| Either | 24 | | 24 | 9 by a later test, 11 by a probe, 14 by one or the other | + +## What the table shows + +- **Nothing is missed.** All 24 escapes, reintroduced, fail the suite today, every one through the + regression test its own fix added, two by no longer compiling. That is the verify-the-guard loop + holding a week to a month on, not evidence that the suite would have found them first. +- **Eight escapes rest on their own regression test alone** (rows 3, 4, 11 to 15, 19): no later test and + no probe catches them. Five of the eight change the Generators alone (rows 3, 11 to 14): refusals the + generators make at build time (CMTK1005, CMTK1022, CMTK1011, CMTK1021) and generated members' edge + behaviour (`TryParse` after a throwing `Create`, a default instance's formatting). The axis of refused + declarations, `shape.refused-kind`, is in the axes file, but every claim that meets it excludes its + pairs (C09, C11, C18) or narrows it away (L05), so no probe row declares a type the generators must + refuse; the build-outcome consumers of `Generators.Tests` are the only finder there. + Since 2026-10-17 the claim `C22-generator-refusals` declares every refused kind, and its probe detects rows + 3, 11 and 12 when each is seeded again (20, 6 and 51 of 144 rows violate); rows 13 and 14 are generated + members' edges, which no refusal reaches, and stay with their own tests. +- **The variant catalogues are the strongest second finder.** `QueryTranslationVariantTests` and + `KeyGenerationVariantTests` ([decision 95](adr/0095-a-fix-is-tested-across-its-input-class.md), written + 2026-10-15) catch six of the seven EF escapes (rows 5 to 10), with 12 to 627 rows each, `8f6af8b` + among them, which no probe catches; the seventh (row 2) fails a later docs test. Every EF escape is + caught by something besides its own test. +- **The probes catch 14 of 24**, every row catch within 36 s once the probes are built (median 9.6 s), and every catch + but three comes from a claim quoted from a README or a docs page, not from the fix. They catch six of + the eight framework-interplay (FCI) escapes; the two they miss, `9534751` (a nullable value object over + an inlined enum) and `27c59d0` (a converter of the options' own), are reached by 11 HTTP probes, none of + whose rows failed. +- **Reached but not detected**, Magma's interesting split: five seeds whose changed file ran in a probe + without a row failing (rows 4, 10, 15, 19, 21). By a reading of the claims that reach them, not a + measurement, each wants a row or an obligation the corpus lacks, not a new probe: a delegate that returns + no task, an ordering key cast to `object`, a nullable value object over an inlined enum, a type nested in + a generic one named in an assertion's message (C20 checks the verdict, not the text), and a converter in + the options that replaces the generated one. +- The suite is also slower to tell: a whole run took 53 to 83 s on the Linux host (warm) and 3.7 to 8.7 + minutes on the loaded Mac, against a probe's 5 to 36 s; a replay re-runs only the probes whose + footprint a commit touched. + +## Where it ran + +- **Mac** (the maintainer's, macOS, Apple M4 Max): the suite for rows 1 to 5, unseeded 12369 passed, 342 + skipped, 0 failed. The machine was shared with other sessions' replays (load average 50 to 200 on + 16 cores), so its times are long. +- **Linux** (Ubuntu 26.04.1, AMD Ryzen AI Max+ 395, SDK 10.0.402, the system's libicu 78, never in + globalization-invariant mode): every probe, and the suite for rows 6 to 24. Unseeded, the suite failed + the 8 `PostgreSqlRoundTripTests` only (no Docker there), which never count; a seed only PostgreSQL could + catch would not show there. Unseeded, every probe passed with no violating row. +- **Probe groups**: the seeds of one surface applied together, each surface's probes run once, then per + seed. For every probe the seeds' rows add up to the group's, except one process-wide failure of 1ee02fc's + (a static `TypeConverters` initializer throws once per process, so a different row reports it); no + catch needed two seeds together. + +## Seeds that could not be reconstructed + +None. Every fix left a line of the guarded code in place to neutralise. + +## Finder accounting, from now on + +The fix history counts findings per finder, never a rate, and nothing here was ever held out. From this +page on, every probe round (`prober round`, `/probe explore` or `full`) and every analyst run +(`code-analyst`, a review session's reviewers) ends with this block in its report, and the run that +writes a dated page under `design/` copies it there. The precedents: Anthropic's documentation-grounded +agent reports valid-bug rates, 984 reports over 100 packages, of a reviewed 50 56% valid and 32% valid and +worth reporting ([Anthropic, 2026-01-14](https://www.anthropic.com/research/property-based-testing)); +Magma reinserts real bugs with oracles for reached, triggered and detected +([arXiv 2009.01120](https://arxiv.org/abs/2009.01120)); DARPA's AIxCC scored systems on injected and +real vulnerabilities with cost per task, 54 of 63 injected found +([DARPA, 2025-08-08](https://www.darpa.mil/news/2025/aixcc-results)). The three are cited from the +research digest of 2026-10-16 (sections a and g), which is local to the maintainer's checkout. + +```text +Run: >, , , +Budget: +Valid findings: (a product defect, confirmed; list the ids and, once fixed, the commits) +False findings: (reported as a defect, refuted on reproduction or by the code-analyst) +Dismissed findings: (real behaviour, not TypeKit's defect or intended: `prober dismiss`, with the reason) +Harness errors: (rows that ended as harness-error; their probes earn nothing) +Triage minutes: (wall time from the first failing row to its probe-triage verdict, summed) +Model minutes: (and USD where known) +Escapes detected: (this page's seeds the run's new probes or tests catch; "not run" is allowed) +``` + +Where each figure comes from today, and where nothing records it: + +| Figure | Recorded by | Not recorded | +|---|---|---| +| Valid | the `Found-By` trailer of the fix commit that repairs it (decision 94) | a finding not yet fixed: the round's report lists it, nothing counts it | +| False | nothing | a finding the code-analyst refutes is said in its report only; count it from the report | +| Dismissed | `tools/probe/corpus/dismissed.jsonl` (`prober dismiss`: id, reason, date) | the analyst's dismissals, which have no file | +| Harness errors | each row's `category` in the replay's evidence (`harness-error`), and `prober try`'s row lines | no total per run: count the rows | +| Triage minutes | nothing | the probe-triage skill records the verdict, not the time it took; note the times in the report | +| Model minutes | `round-last.json` under the prober's state for the branch: `duration_ms`, `num_turns`, `total_cost_usd` of a `prober round` | a round stopped at its cap (the CLI's JSON never arrives, only `seconds`), a round started from the `/probe` skill inside a session, and every `code-analyst` run: take them from the session's usage | +| Escapes detected | this page's method: the seeds, `prober try` per probe, the verdict tool for the suite | the seeds are not in the repository; a run that wants the figure rebuilds them as above | diff --git a/design/fix-history.md b/design/fix-history.md index 2816ff89..d3be005c 100644 --- a/design/fix-history.md +++ b/design/fix-history.md @@ -2,6 +2,8 @@ Read-only analysis of the 133 `fix:` commits on `feat/2.1.0` (`git log --grep='^fix' -E feat/2.1.0`, head `dc459b0`). Nothing in the repository was changed. This note is the baseline for the review approach (variant analysis, shape catalogues, redesign reviews, lens reviews, defect tracking). Consumers are called "downstream". +From the 2.2.0 cycle on, each `fix:` commit records its class and finder itself, in the trailers `Defect-Class:` and `Found-By:` with the codes below ([decision 94](adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md)), so the next classification reads them from `git log` instead of reconstructing them. + ## How to read this - **Class** is the primary cause of the defect. A commit that fixes several defects (16 of 133, flagged **B**) is counted once, under the class of its main defect. **S** marks a defect whose symptom was silent (compiled or ran and did less or something else); it is a flag, not a class, so a defect can be class `GTS` and still silent. 58 of 133 carry S. Class `SDL` is kept for the three commits where nothing more specific than "silent" could be said. @@ -80,7 +82,7 @@ The 24 confirmed escapes, by earliest tag with the defect: The 6 `P`: `37317de`, `780353b`, `e269ec8`, `71a4565`, `a7003bf`, `2e68a70`. CMTK0003 was a Warning in 1.x with the same false positives (`await held.ConfigureAwait(false)` read as a dropped result, a tap on a held result) and the same missed forms; 2.0.0 made it an Error and widened it as decision 50. They are counted separately because a Warning false positive is not the same harm as a wrong EF query. -Finder and escape (post-1.0.0 fixes only): +Finder and escape (post-1.0.0 fixes only). Which of today's finders detect these escapes when each is put back as a seed is measured in the [finder benchmark of 2026-10-16](finder-benchmark-2026-10-16.md). | Found by | Fixes after v1.0.0 | Y | P | |---|---|---|---| @@ -149,6 +151,16 @@ Twelve fixes repair a regression an earlier fix introduced or finish an earlier - `8d1ebd8` finishes `66a5cec` (the transformer sorted only the statuses it added). - `27c59d0` follows `564a51e` (found while fixing it). +Two more, measured after this note was written by running the variant catalogue at each commit of chain A +([decision 95](adr/0095-a-fix-is-tested-across-its-input-class.md)); the counts above are this note's reading +and leave them out: + +- `7c30840` repairs `e4361e9`, which moved CMTK0010's reporting to `context.ReportDiagnostic`, so a breach in + generated code was reported from `e4361e9` on (the catalogue's four generated-code sites fail at `e4361e9`, + `a0ddf61` and `370414a`, and pass at `a77bdda` and `37317de`). +- `cdab810` repairs `e4361e9`: a default interface method called through a derived interface overflows the stack + from `e4361e9` on (the test host aborts at `e4361e9`, `a0ddf61`, `370414a` and `7c30840`; `37317de` completes). + ## Source files with the most fixes Counts are `.cs` files under `src/` touched by a fix; READMEs are left out (they are touched by 24, 24, 18, 15 and 10 fixes for Generators, Analyzers, base, AspNetCore and EF Core). Classes are those of the commits that touch the file, so a file touched by a cross-cutting commit also shows that commit's class. diff --git a/design/knowledge/KB-001-openapi-2-nullable-body-drops-pattern.md b/design/knowledge/KB-001-openapi-2-nullable-body-drops-pattern.md index 5468af30..347d7641 100644 --- a/design/knowledge/KB-001-openapi-2-nullable-body-drops-pattern.md +++ b/design/knowledge/KB-001-openapi-2-nullable-body-drops-pattern.md @@ -1,11 +1,12 @@ --- -verified: 2026-10-15 +verified: 2026-10-16 pins: - Microsoft.AspNetCore.OpenApi 10.0.12 - sdk 10.0.4xx -source: the document Microsoft.AspNetCore.OpenApi writes for an MVC [FromBody] int and int? under OpenApiSpecVersion.OpenApi2_0 and OpenApi3_1, measured in a host without TypeKit +source: the document Microsoft.AspNetCore.OpenApi writes for an MVC [FromBody] int and int?, and a minimal-API [FromBody] enum?, under OpenApiSpecVersion.OpenApi2_0 and OpenApi3_1, measured in a host without TypeKit turned-on: the probe finding G1, dismissed as no TypeKit bug (tools/probe/corpus/dismissed.jsonl), and the probe harness comparing a nullable value object with the nullable wrapped type test: test/CodoMetis.TypeKit.Facts/KB001.cs +fixture: test/CodoMetis.TypeKit.Facts/KB001.fixture.json --- # KB-001. Under OpenAPI 2.0, ASP.NET writes a nullable request body as `allOf: [{x-nullable: true}]` and drops its `pattern` @@ -18,6 +19,9 @@ as its `format` and `allOf: [{"x-nullable": true}]`, without the `pattern` the n | `int` | `"required": true`, `{"format":"int32","pattern":"^-?(?:0\|[1-9]\\d*)$"}` | | `int?` | not required, `{"format":"int32","allOf":[{"x-nullable":true}]}` | +A minimal API writes it the same, and a nullable enum body, which has no `format`, keeps nothing but the null: +`{"allOf":[{"x-nullable":true}]}` (row 257 of C21OpenapiSchemaParityProbe, measured 2026-10-16). + OpenAPI 3.1 keeps the pattern, inside `oneOf: [{"type":"null"}, {…}]`. In MVC a body parameter of a class type is nullable too, so a class value object over `int` loses the pattern beside a required `int`, and has the same bytes as `int?`. A probe that compares a nullable body with a required one measures this, not TypeKit. diff --git a/design/knowledge/KB-025-the-coverage-collector-rewrites-assemblies-on-disk.md b/design/knowledge/KB-025-the-coverage-collector-rewrites-assemblies-on-disk.md new file mode 100644 index 00000000..5d6833ab --- /dev/null +++ b/design/knowledge/KB-025-the-coverage-collector-rewrites-assemblies-on-disk.md @@ -0,0 +1,32 @@ +--- +verified: 2026-10-16 +pins: + - external Microsoft.Testing.Extensions.CodeCoverage 18.12.0 + - sdk 10.0.4xx +source: the probe replay on macOS (arm64), 43 probes run four and sixteen at a time from one output directory with --coverage, the kept logs of the failed hosts, and the collector's runsettings as the Microsoft.Testing.Platform repository documents them, measured 2026-10-16 +turned-on: decision 101 (a replay that records a footprint runs one host at a time; parallel runs record none) +test: none, a race between processes that no single test can hold; the replay's --parallel runs with and without a footprint are the measurement +--- + +# KB-025. The code coverage collector instruments the assemblies on disk, so test hosts started together from one output directory corrupt each other; its in-memory mode records no coverage here + +On macOS (arm64) with SDK 10.0.402, `Microsoft.Testing.Extensions.CodeCoverage` 18.12.0 run with `--coverage` and +no settings file instruments the assemblies of the output directory in place (static managed instrumentation) +and restores them when the host ends. Hosts started together from one directory load each other's half-written +or restored files: of 68 processes of the probe replay started sixteen at a time, 42 exited 7 having run zero +tests; four at a time, 14 and then 56. The kept logs of 61 failed hosts show, in 53, a +`BadImageFormatException` loading an assembly with a garbage identity (`''. Index not found`, +`Version=1024.32.11.22528`), and in one a `MissingMethodException` for an instrumented constructor +(`zjo7…ctor`). The same 68 processes four at a time without `--coverage` all ran (40 passed, the 3 expected +failures), and six hosts of one light probe with coverage happened to survive, so the race is one of timing, +not of count. + +A runsettings file passed with `--coverage-settings` that turns static instrumentation off and dynamic managed +instrumentation on (`EnableStaticManagedInstrumentation` False, `EnableDynamicManagedInstrumentation` True, +`EnableStaticNativeInstrumentation` False, `EnableDynamicNativeInstrumentation` False, the form the platform's +own integration tests use) lets eight hosts run at once with no crash, but every cobertura report then names no +class under `src/` and no line with a hit: on this platform the dynamic mode collects nothing the footprint can +read. Not measured on Linux x64 or Windows. + +What follows for TypeKit: a replay that records a footprint runs one host at a time; the parallel runs are the +ones that record none ([decision 101](../adr/0101-ci-replays-by-family-and-footprint.md)). diff --git a/design/knowledge/KB-026-openapi-2-nullable-component-property-loses-its-reference.md b/design/knowledge/KB-026-openapi-2-nullable-component-property-loses-its-reference.md new file mode 100644 index 00000000..c7319fbe --- /dev/null +++ b/design/knowledge/KB-026-openapi-2-nullable-component-property-loses-its-reference.md @@ -0,0 +1,27 @@ +--- +verified: 2026-10-16 +pins: + - Microsoft.AspNetCore.OpenApi 10.0.12 + - sdk 10.0.4xx +source: the document Microsoft.AspNetCore.OpenApi writes for an MVC [FromBody] record with a property of a record struct and one of its Nullable, under OpenApiSpecVersion.OpenApi2_0 and OpenApi3_1, measured in a host without TypeKit +turned-on: row 38 of C21OpenapiSchemaParityProbe (claim C21-openapi-schema-parity), where a nullable value-object property that is a component had no type under OpenAPI 2.0 +test: test/CodoMetis.TypeKit.Facts/KB026.cs +fixture: test/CodoMetis.TypeKit.Facts/KB026.fixture.json +--- + +# KB-026. Under OpenAPI 2.0, ASP.NET writes a nullable property of a component as `allOf: [{x-nullable: true}]`, without the reference + +A property whose type is a component (a record struct here) refers to it by `$ref`. The same property made +nullable is written by the OpenAPI 2.0 writer as `allOf: [{"x-nullable": true}]` and nothing else: the reference, +and with it the type, is gone. + +| Property | OpenAPI 2.0 | OpenAPI 3.1 | +|---|---|---| +| `Spot Always` | `{"$ref":"#/definitions/Spot"}` | `{"$ref":"#/components/schemas/Spot"}` | +| `Spot? Maybe` | `{"allOf":[{"x-nullable":true}]}` | `{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Spot"}]}` | + +A value object keeps a component of its own (decision 36), so a nullable value-object property loses its type +under 2.0 where the wrapped type's nullable property, which is no component, keeps it. With TypeKit the wrapped +type's `format` survives beside the `allOf` (`{"format":"time","allOf":[{"x-nullable":true}]}` for a value object +over `TimeOnly`); inlined, the value object's property is the wrapped type's, +`{"type":"string","format":"time","x-nullable":true}`. diff --git a/design/knowledge/KB-027-an-enum-component-is-its-first-use.md b/design/knowledge/KB-027-an-enum-component-is-its-first-use.md new file mode 100644 index 00000000..ecc69392 --- /dev/null +++ b/design/knowledge/KB-027-an-enum-component-is-its-first-use.md @@ -0,0 +1,28 @@ +--- +verified: 2026-10-16 +pins: + - Microsoft.AspNetCore.OpenApi 10.0.12 + - sdk 10.0.4xx +source: the document Microsoft.AspNetCore.OpenApi writes for minimal API header parameters of two enums written by name (JsonStringEnumConverter), each used once nullable and once not, in opposite orders, under OpenApiSpecVersion.OpenApi3_1 and OpenApi3_0, measured in a host without TypeKit +turned-on: row 285 of C21OpenapiSchemaParityProbe (claim C21-openapi-schema-parity, finding F3), where the wrapped E? header's enum listed null through E's component and the value object's did not +test: test/CodoMetis.TypeKit.Facts/KB027.cs +fixture: test/CodoMetis.TypeKit.Facts/KB027.fixture.json +--- + +# KB-027. The component ASP.NET makes for an enum written by name is the schema of the first use it meets + +An enum written by name is a component, and every use of it, nullable or not, refers to that one component. What +the component says is whatever schema ASP.NET built for the first use it met: met first as a nullable parameter, +the component lists null among its values, for its non-nullable uses too; met first as itself, it does not, for +its nullable uses too. + +| Uses, in the order ASP.NET meets them | The component | +|---|---| +| `E? v`, then `E v` | `{"enum":["X","Y",null]}` | +| `E v`, then `E? v` | `{"enum":["X","Y"]}` | + +So whether a parameter of the wrapped `E?` admits null in a document with components depends on the endpoints +and their order, not on the parameter. A value object over `E` describes a nullable parameter outside the body +the same way in every document: with null among its values where the enum is inlined, as ASP.NET writes an +inlined `E?` there, and as the enum's content without the null where the enum is a component (decision 36: a +component shared with the non-nullable uses cannot admit null). diff --git a/design/knowledge/KB-028-postgresql-nan-is-greater-than-every-number.md b/design/knowledge/KB-028-postgresql-nan-is-greater-than-every-number.md new file mode 100644 index 00000000..d54fc1a3 --- /dev/null +++ b/design/knowledge/KB-028-postgresql-nan-is-greater-than-every-number.md @@ -0,0 +1,21 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.postgresql.org/docs/17/datatype-numeric.html §8.1.2 ("PostgreSQL treats NaN values as equal, and greater than all non-NaN values"), the same for floating point in §8.1.3, measured on the postgres 17 image +turned-on: none yet, until WS6's real-database checks turn it on; measured by the analyst's PostgreSQL consumer of 2026-10-16 (Q10, Q10b, Q11) where Ratio.Value > 5 kept the NaN row for the value object and its plain twin alike, which in memory is false +test: test/CodoMetis.TypeKit.Facts/KB028.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-028. NaN is greater than every number in PostgreSQL, and sorts after Infinity + +A `double` column holding `NaN`, `1.5`, `10` and `Infinity`: + +- `Where(r => r.R > 5)` answers the `NaN` row with `10` and `Infinity`, where in .NET every comparison with + `NaN` is false; +- `OrderBy(r => r.R)` gives `[1.5, 10, Infinity, NaN]`. + +So a query over a floating-point or `numeric` column answers rows memory would not, for a plain type and a value +object over it alike. Nothing in TypeKit turns on it yet; WS6's real-database checks will. diff --git a/design/knowledge/KB-029-npgsql-truncates-ticks-below-a-microsecond.md b/design/knowledge/KB-029-npgsql-truncates-ticks-below-a-microsecond.md new file mode 100644 index 00000000..5d8ca8e4 --- /dev/null +++ b/design/knowledge/KB-029-npgsql-truncates-ticks-below-a-microsecond.md @@ -0,0 +1,23 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: measured with Npgsql 10.0.3, which Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 brings, on the postgres 17 image (https://www.npgsql.org/doc/types/datetime.html states microsecond resolution, not truncation against rounding) +turned-on: claims E03-column-round-trip and L04-round-trip under a real provider, where a value written does not read back equal below a microsecond, for the plain type and the value object alike (the analyst's consumer of 2026-10-16, Q12, Q15, Q17) +test: test/CodoMetis.TypeKit.Facts/KB029.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-029. Npgsql truncates ticks below a microsecond, for every date and time type + +PostgreSQL stores `timestamp`, `timestamptz`, `time` and `interval` to the microsecond, ten ticks. Npgsql +drops the ticks below it when it writes, and does not round them: + +- a `DateTime` of `…07.1234567Z` reads back as `…07.1234560Z` (rounding would give `…07.1234570Z`), still + `Kind` Utc; +- a `DateTimeOffset`, a `TimeOnly` and a `TimeSpan` (26 hours and 3 ticks) lose their sub-microsecond ticks + the same way. + +So a round trip through PostgreSQL is equal only at microsecond resolution, for a plain type and a value object +over it alike. diff --git a/design/knowledge/KB-030-datetime-maxvalue-reads-back-unspecified.md b/design/knowledge/KB-030-datetime-maxvalue-reads-back-unspecified.md new file mode 100644 index 00000000..cc717dfe --- /dev/null +++ b/design/knowledge/KB-030-datetime-maxvalue-reads-back-unspecified.md @@ -0,0 +1,16 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.npgsql.org/doc/types/datetime.html (infinity is mapped to the MinValue and MaxValue of the corresponding .NET types), the Kind read back measured with Npgsql 10.0.3 on the postgres 17 image, which the page does not state +turned-on: none yet, until WS6's real-database checks turn it on; measured by the analyst's PostgreSQL consumer of 2026-10-16 (W05), for the value object and its plain twin alike +test: test/CodoMetis.TypeKit.Facts/KB030.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-030. `DateTime.MaxValue` written as UTC reads back with `Kind` Unspecified + +`DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc)` written to a `timestamptz` column is stored as +`infinity`, and reads back as `DateTime.MaxValue` with `Kind` `Unspecified`, not `Utc`. The value compares +equal; the kind does not survive. diff --git a/design/knowledge/KB-031-a-space-in-character-1-disappears-as-text.md b/design/knowledge/KB-031-a-space-in-character-1-disappears-as-text.md new file mode 100644 index 00000000..2c8709a9 --- /dev/null +++ b/design/knowledge/KB-031-a-space-in-character-1-disappears-as-text.md @@ -0,0 +1,16 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.postgresql.org/docs/17/datatype-character.html ("Trailing spaces are removed when converting a character value to one of the other string types."), measured on the postgres 17 image +turned-on: none yet, until WS6's real-database checks turn it on; measured by the analyst's PostgreSQL consumer of 2026-10-16 (K06, where "[" + x.Id + "]" in a projection gave "[]" for a space, the value object and its plain twin alike; Q20 read the column back as the space, and K06b found the row by it) +test: test/CodoMetis.TypeKit.Facts/KB031.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-031. A space in `character(1)` disappears when SQL converts it to text + +Npgsql maps a `char` to `character(1)`. A row holding `' '` reads back as `' '`, but where SQL converts the +column to text, as EF's translation of `"[" + l.C + "]"` does, the trailing space is removed and the projection +is `"[]"`. diff --git a/design/knowledge/KB-032-postgresql-sorts-null-last-ascending.md b/design/knowledge/KB-032-postgresql-sorts-null-last-ascending.md new file mode 100644 index 00000000..2093b01b --- /dev/null +++ b/design/knowledge/KB-032-postgresql-sorts-null-last-ascending.md @@ -0,0 +1,22 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.postgresql.org/docs/17/queries-order.html ("By default, null values sort as if larger than any non-null value; that is, NULLS FIRST is the default for DESC order, and NULLS LAST otherwise."), measured on the postgres 17 image +turned-on: a docs gap of the analyst's run of 2026-10-16, where a nullable value object or closed set sorts null first in memory and on SQLite and last on PostgreSQL; decision 98 covers comparisons, not ordering +test: test/CodoMetis.TypeKit.Facts/KB032.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-032. PostgreSQL sorts NULL last ascending and first descending + +An `int?` column holding `null`, `1`, `3` and `2`: + +- `OrderBy(r => r.Rank)` gives `[1, 2, 3, null]`; +- `OrderByDescending(r => r.Rank)` gives `[null, 3, 2, 1]`; +- EF writes no `NULLS FIRST` or `NULLS LAST`. + +LINQ to Objects puts `null` first ascending, and so did SQLite in the analyst's run. So the same `OrderBy` answers in another order on +PostgreSQL, for a plain nullable type and a nullable value object alike +([decision 98](../adr/0098-a-null-passes-no-comparison-in-a-query.md) is about comparisons only). diff --git a/design/knowledge/KB-033-startswith-is-case-sensitive-and-order-follows-the-collation.md b/design/knowledge/KB-033-startswith-is-case-sensitive-and-order-follows-the-collation.md new file mode 100644 index 00000000..9bf5332f --- /dev/null +++ b/design/knowledge/KB-033-startswith-is-case-sensitive-and-order-follows-the-collation.md @@ -0,0 +1,21 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: measured on the postgres 17 image, whose databases take the collation en_US.utf8 from template1 (EF translates StartsWith to LIKE 'b%', which on SQLite is case-insensitive for ASCII) +turned-on: none yet, until WS6's real-database checks turn it on, beyond the EF README's "Ordering in SQL follows the database's collation" (the analyst's consumer of 2026-10-16, Q01, Q02, Q05, Q20c, the value object and its plain twin alike) +test: test/CodoMetis.TypeKit.Facts/KB033.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-033. `StartsWith` is case-sensitive, and ordering follows the database's collation, not ordinal order + +A `text` column holding `alpha`, `Bravo`, `charlie` and `Äpfel`, in a database whose collation is +`en_US.utf8`: + +- `Where(n => n.Name.StartsWith("b"))` finds nothing: `LIKE 'b%'` is case-sensitive; +- `OrderBy(n => n.Name)` gives `["alpha", "Äpfel", "Bravo", "charlie"]`; +- `OrderBy(n => EF.Functions.Collate(n.Name, "C"))` gives the ordinal `["Bravo", "alpha", "charlie", "Äpfel"]`. + +So a query's text order is the server's, which ordinal comparison in memory does not reproduce. diff --git a/design/knowledge/KB-034-numeric-p-s-rounds-on-write.md b/design/knowledge/KB-034-numeric-p-s-rounds-on-write.md new file mode 100644 index 00000000..36556fba --- /dev/null +++ b/design/knowledge/KB-034-numeric-p-s-rounds-on-write.md @@ -0,0 +1,16 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.postgresql.org/docs/17/datatype-numeric.html §8.1.2 ("If the scale of a value to be stored is greater than the declared scale of the column, the system will round the value to the specified number of fractional digits."), measured on the postgres 17 image +turned-on: claims E03-column-round-trip and L04-round-trip under a real provider, and the EF README's HavePrecision guidance, whose facet reaching the column is what makes the rounding happen (the analyst's finding E2 of 2026-10-16, consumer Q06) +test: test/CodoMetis.TypeKit.Facts/KB034.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-034. `numeric(p,s)` rounds a value with more fractional digits on write + +A `decimal` configured `HasPrecision(18, 2)` is a `numeric(18,2)` column. Written as `1.005m` and `-0.004m`, the +values read back as `1.01m` and `0.00m`: PostgreSQL rounds to the column's scale, half away from zero, and does +not refuse. diff --git a/design/knowledge/KB-035-npgsql-refuses-non-utc-for-timestamptz.md b/design/knowledge/KB-035-npgsql-refuses-non-utc-for-timestamptz.md new file mode 100644 index 00000000..e909dcc8 --- /dev/null +++ b/design/knowledge/KB-035-npgsql-refuses-non-utc-for-timestamptz.md @@ -0,0 +1,20 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: https://www.npgsql.org/doc/types/datetime.html ("trying to send a non-UTC DateTime as timestamptz will throw an exception", and DateTimeOffset "only with Offset=0"), measured with Npgsql 10.0.3 on the postgres 17 image +turned-on: the EF converter passing DateTime and DateTimeOffset through, so that a value object over either meets this refusal exactly as the plain type does (the analyst's consumer of 2026-10-16, W01, W02, Q14b, Q15c, Q15d, the same exception for both models) +test: test/CodoMetis.TypeKit.Facts/KB035.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-035. Npgsql refuses a non-UTC `DateTime` and a non-zero-offset `DateTimeOffset` for `timestamptz` + +Npgsql maps `DateTime` and `DateTimeOffset` to `timestamptz` and refuses what that column cannot hold unchanged: + +- a `DateTimeOffset` with offset +02:00, saved: a `DbUpdateException` over an `ArgumentException`, "only offset + 0 (UTC) is supported"; +- a `DateTime` of `Kind` `Unspecified`, saved: a `DbUpdateException` whose inner message names + `Kind=Unspecified`; +- a `DateTime` of `Kind` `Local` as a query parameter: an `ArgumentException` naming `Kind=Local`. diff --git a/design/knowledge/KB-036-a-char-key-gets-an-identity-postgresql-refuses.md b/design/knowledge/KB-036-a-char-key-gets-an-identity-postgresql-refuses.md new file mode 100644 index 00000000..2b1332e8 --- /dev/null +++ b/design/knowledge/KB-036-a-char-key-gets-an-identity-postgresql-refuses.md @@ -0,0 +1,18 @@ +--- +verified: 2026-10-16 +pins: + - external PostgreSQL 17.11 + - Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: measured, GenerateCreateScript and EnsureCreated of a plain model on the postgres 17 image, the error PostgreSQL's (22023, identity column type must be smallint, integer, or bigint) +turned-on: the EF README's char-key exception ("PostgreSQL rejects an identity on character(1)") and decision 63, under which a value object's char key is NOT NULL without an identity +test: test/CodoMetis.TypeKit.Facts/KB036.cs (runs only with TYPEKIT_PG set, until WS6's CI service) +--- + +# KB-036. A plain `char` key gets an identity column on Npgsql, which PostgreSQL refuses + +With no configuration, an entity keyed by a `char` is created by Npgsql's EF provider as +`"Id" character(1) GENERATED BY DEFAULT AS IDENTITY`, and `EnsureCreated` fails with PostgreSQL's +`22023`: an identity column's type must be `smallint`, `integer` or `bigint`. It is the provider's choice for +the plain type that PostgreSQL refuses, which [decision 63](../adr/0063-char-keys-application-assigned-ulong-documented.md) +cites for keys over `char`. diff --git a/design/knowledge/KB-037-mvc-under-max-validation-depth-1-runs-an-unbound-action.md b/design/knowledge/KB-037-mvc-under-max-validation-depth-1-runs-an-unbound-action.md new file mode 100644 index 00000000..e21341d9 --- /dev/null +++ b/design/knowledge/KB-037-mvc-under-max-validation-depth-1-runs-an-unbound-action.md @@ -0,0 +1,26 @@ +--- +verified: 2026-10-17 +pins: + - sdk 10.0.4xx +source: measured in a host without TypeKit (Microsoft.AspNetCore.App 10.0 of the pinned SDK, an [ApiController] with an int parameter per binding source); the API reference of MvcOptions.MaxValidationDepth says only that "ValidationVisitor will throw if traversing an object exceeds the maximum allowed validation depth" and that "The default value is 32", nothing of binding errors +turned-on: 28 rows of BaseProbe (claim C07-fault-responses-blanket) under mvc.MaxValidationDepth=1, where a validated value object answered a value its Create refuses with 200 holding its default (WS2 step 4 triage) +test: test/CodoMetis.TypeKit.Facts/KB037.cs +fixture: test/CodoMetis.TypeKit.Facts/KB037.fixture.json +--- + +# KB-037. Under `MaxValidationDepth = 1`, MVC runs an action whose parameter it could not bind, with the parameter at its default + +With `MvcOptions.MaxValidationDepth = 1`, an `[ApiController]` action whose `int` parameter MVC cannot bind from +the text it was sent runs anyway. The parameter is `0`, its default, and the action answers 200. That holds for a +route, query, header, form and body parameter alike. With the default depth, 32, every one of those requests is a +400 from the model-state filter. + +| Request to an `int` parameter | `MaxValidationDepth = 1` | default (32) | +|---|---|---| +| route, query, header or form `abc`, body `"abc"` | 200, the parameter `0` | 400 | +| the same with `5` | 200, `5` | 200, `5` | +| `[FromQuery]` class whose `int` property gets `abc` or `5` | `InvalidOperationException`: "ValidationVisitor exceeded the maximum configured validation depth '1' when validating property 'V'" | 400 for `abc`, 200 for `5` | + +So under that setting a binding failure leaves no 400 to compare: a value object whose text is rejected arrives at +the action as its default, an uninitialized instance that went through no factory, as the wrapped type arrives as +its own default. The HTTP probes compare the two in the same row and cite this entry where both hold their default. diff --git a/design/knowledge/KB-038-a-byte-array-is-a-base64-string.md b/design/knowledge/KB-038-a-byte-array-is-a-base64-string.md new file mode 100644 index 00000000..c9ccdf2e --- /dev/null +++ b/design/knowledge/KB-038-a-byte-array-is-a-base64-string.md @@ -0,0 +1,18 @@ +--- +verified: 2026-10-17 +pins: + - sdk 10.0.4xx +source: the System.Array section of Supported types in System.Text.Json (learn.microsoft.com, dotnet/docs supported-types.md), which says "`byte[]` is handled specially and serializes as a base64 string, not a JSON array."; measured with System.Text.Json of the pinned SDK +turned-on: row 83 of BaseProbe (claim C07-fault-responses-blanket), where an array of value objects over byte was written [7] and the wrapped twin, a byte[], "Bw=="; docs/json.md "The wrapped type's JSON" and the not-promised entry a-byte-array-is-base64 of claim L01-json-bytes +test: test/CodoMetis.TypeKit.Facts/KB038.cs +--- + +# KB-038. System.Text.Json writes a `byte[]` as one base64 string, and every other collection as a JSON array + +`JsonSerializer.Serialize(new byte[] { 7, 255 })` is `"B/8="`, and a `byte[]` reads back from a base64 string, +never from `[7,255]`. A `List` is `[7,255]`, and an array of any other element, a struct holding a byte +included, is a JSON array. + +So swapping `byte` for a value object over `byte` in an array changes the array's JSON: each element keeps the +byte's JSON, a number, and the array becomes a JSON array of numbers instead of the base64 string (docs/json.md, +"The wrapped type's JSON"). diff --git a/design/knowledge/KB-039-sqlite-neither-compares-nor-orders-datetimeoffset-timespan-or-ulong.md b/design/knowledge/KB-039-sqlite-neither-compares-nor-orders-datetimeoffset-timespan-or-ulong.md new file mode 100644 index 00000000..b3d7ce67 --- /dev/null +++ b/design/knowledge/KB-039-sqlite-neither-compares-nor-orders-datetimeoffset-timespan-or-ulong.md @@ -0,0 +1,26 @@ +--- +verified: 2026-10-17 +pins: + - Microsoft.EntityFrameworkCore.Sqlite 10.0.12 + - Microsoft.EntityFrameworkCore.Relational 10.0.12 +source: the Query limitations section of SQLite Database Provider - Limitations (learn.microsoft.com, dotnet/EntityFramework.Docs providers/sqlite/limitations.md), which says "SQLite doesn't natively support the following data types. EF Core can read and write values of these types, and querying for equality (`where e.Property == value`) is also supported. Other operations, however, like comparison and ordering will require evaluation on the client." and lists DateTimeOffset, decimal, TimeSpan and ulong; measured with plain EF Core and its SQLite provider, where decimal is compared and ordered +turned-on: 12 rows of E02ValueTranslatesProbe (claim E02-value-translates-to-column), a value object over DateTimeOffset, TimeSpan or ulong under where-value-compare or order-by, where both sides refused the query (WS2 step 4, the EF family); E02's obligations declare those pairs inapplicable +test: test/CodoMetis.TypeKit.Facts/KB039.cs +--- + +# KB-039. EF Core's SQLite provider neither compares nor orders a `DateTimeOffset`, a `TimeSpan` or a `ulong` + +Over a plain column of one of these types, with no TypeKit: + +| Query | `DateTimeOffset`, `TimeSpan`, `ulong` | `decimal` | +|---|---|---| +| `Where(r => r.P >= v)` | `InvalidOperationException`: "The LINQ expression ... could not be translated" | translated, the rows at or above `v` | +| `OrderBy(r => r.P)` | `NotSupportedException`: "SQLite does not support expressions of type '...' in ORDER BY clauses" | translated, in order | +| `Where(r => r.P == v)` | translated | translated | + +EF's page lists `decimal` beside the three. EF Core 10 translates its comparisons and orderings on SQLite, so the +table above, not the page's list, is what this repository relies on. + +So on SQLite a query that compares or orders such a column fails for the plain type as it does for a value object +over it, before any value-object translation is asked anything: E02's comparison and ordering rows over these types +have nothing to check there, and its obligations declare them inapplicable. PostgreSQL translates both. diff --git a/design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md b/design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md new file mode 100644 index 00000000..df4e7873 --- /dev/null +++ b/design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md @@ -0,0 +1,28 @@ +--- +verified: 2026-10-17 +pins: + - sdk 10.0.4xx +source: measured with System.Text.Json of the pinned SDK, a list of int nested in objects, written once by the serializer's own converter and once by a converter of the application's; the API reference of JsonSerializerOptions.MaxDepth says only that it is "the maximum depth allowed when serializing or deserializing JSON", nothing of converters +turned-on: the rows of BaseProbe, C10-preserve-guard, L01-json-bytes and L04-round-trip under json.MaxDepth=3 where a value object, which its converter writes, was refused at a depth its wrapped twin was written at; until WS2's three-sided differential these were told by the refusal's message (note stj-maxdepth), now by side (1), a converter of the application's for the wrapped type without TypeKit +test: test/CodoMetis.TypeKit.Facts/KB040.cs +fixture: test/CodoMetis.TypeKit.Facts/KB040.fixture.json +--- + +# KB-040. Under `MaxDepth`, System.Text.Json refuses a value a converter of one's own writes at the limit, where its own converter writes it + +With `JsonSerializerOptions.MaxDepth = 3`, a list of `int` inside two objects, `{"P":{"P":[1]}}`, is written: the +writer is at depth 3 when it writes the element. Register a converter of your own for `int` that writes the same +number, and the same document is refused with a `JsonException` whose `Path` is the list's, `$.P.P`, before the +converter is asked to write anything. One level less deep, `{"P":[1]}`, the converter's value is written. Reading +is not charged: the converter reads the element of `{"P":{"P":[1]}}` under the same limit. + +| The element of a `List` at the writer's depth | the serializer's own converter | a converter of one's own | +|---|---|---| +| 2 (`{"P":[1]}`), `MaxDepth = 3` | written | written | +| 3 (`{"P":{"P":[1]}}`), `MaxDepth = 3` | written | `JsonException` at `$.P.P` | +| 3, read back | read | read | + +A value object is written by its generated converter, so under a depth limit it is refused one level earlier than +its wrapped type, which the serializer's own converter writes, and the same holds for any converter an application +registers. The probes' side (1) for such a row is a converter of the application's for the wrapped type, without +TypeKit, or this fixture where it covers the row's limit and the depth the value is written at. diff --git a/design/knowledge/KB-041-mvc-cannot-write-its-400-under-a-context-without-validation-problem-details.md b/design/knowledge/KB-041-mvc-cannot-write-its-400-under-a-context-without-validation-problem-details.md new file mode 100644 index 00000000..a83170bd --- /dev/null +++ b/design/knowledge/KB-041-mvc-cannot-write-its-400-under-a-context-without-validation-problem-details.md @@ -0,0 +1,26 @@ +--- +verified: 2026-10-17 +pins: + - sdk 10.0.4xx +source: measured in a host without TypeKit (Microsoft.AspNetCore.App 10.0 of the pinned SDK, an [ApiController] with an int route parameter, MVC's JSON options given a source-generated context listing int alone); ASP.NET Core's web API documentation, "Automatic HTTP 400 responses", names ValidationProblemDetails as the default type of that response +turned-on: rows 286 and 309 of BaseProbe (claim C07-fault-responses-blanket) under json.TypeInfoResolver=source-generated-context, where a value Create refuses answered 500 instead of MVC's 400; until WS2's three-sided differential told by the exception's words (note mvc-400-unwritable-under-source-gen), now by side (1), the wrapped twin sent a text it cannot read on a host without TypeKit +test: test/CodoMetis.TypeKit.Facts/KB041.cs +fixture: test/CodoMetis.TypeKit.Facts/KB041.fixture.json +--- + +# KB-041. MVC cannot write its automatic 400 under a source-generated context that does not list `ValidationProblemDetails` + +An `[ApiController]` action whose parameter MVC cannot bind answers 400 with a `ValidationProblemDetails`, which +MVC writes with its own JSON options. Give those options a source-generated context that lists the action's types +but not `ValidationProblemDetails`, and the 400 is never written: the request fails with System.Text.Json's +`NotSupportedException`, which a test server passes to its client, for a parameter of any type. Under the +serializer's default resolver the same request is a 400. + +| `GET /kb041/abc` to an action's `int` route parameter | MVC's JSON options' resolver | +|---|---| +| `NotSupportedException` | a source-generated context listing `int` alone | +| 400 | the serializer's default | + +So under such a context a refused binding has no 400 to compare: a value object whose text its `Create` refuses +fails the request as the wrapped type does with a text it cannot read. The HTTP probes show it in the same row with +the twin on a host without TypeKit, or read this fixture where it covers the row. diff --git a/design/knowledge/KB-042-a-char-schema-is-refused-under-write-as-string.md b/design/knowledge/KB-042-a-char-schema-is-refused-under-write-as-string.md new file mode 100644 index 00000000..f04120fa --- /dev/null +++ b/design/knowledge/KB-042-a-char-schema-is-refused-under-write-as-string.md @@ -0,0 +1,30 @@ +--- +verified: 2026-10-18 +pins: + - Microsoft.AspNetCore.OpenApi 10.0.12 + - sdk 10.0.4xx +source: the document Microsoft.AspNetCore.OpenApi writes for a minimal API whose body holds a char, and one whose body holds an int, under JsonNumberHandling.WriteAsString and the default number handling, measured in a host without TypeKit; ASP.NET Core 10's OpenApiSchemaService builds each schema with System.Text.Json's JsonSchemaExporter under the host's options and reads the node back as its own OpenApiJsonSchema, and a char's schema is the exporter's minLength and maxLength of 1 +turned-on: row 408 of C21OpenapiSchemaParityProbe (claim C21-openapi-schema-parity), under json.NumberHandling=WriteAsString with json.MaxDepth=3, where AddTypeKit() refused the document with this JsonException, asking for the char a value object wraps, and ASP.NET alone with MaxDepth's InvalidOperationException, met first at another endpoint +test: test/CodoMetis.TypeKit.Facts/KB042.cs +fixture: test/CodoMetis.TypeKit.Facts/KB042.fixture.json +--- + +# KB-042. Under `NumberHandling` with `WriteAsString`, ASP.NET refuses the OpenAPI document of an endpoint whose schema holds a `char` + +ASP.NET builds a schema with System.Text.Json's schema exporter, under the host's JSON options, and reads the node +back as its own `OpenApiJsonSchema`. A `char`'s schema is a string of `minLength` and `maxLength` 1. With +`JsonSerializerOptions.NumberHandling` set to `WriteAsString`, those two numbers reach the reader as strings, and +generating the document fails with a `JsonException`, "The JSON value could not be converted to OpenApiJsonSchema", +whose inner exception cannot read a string token as a number. A host without TypeKit does so for any endpoint +whose body holds a `char`. A number's schema is written under the same option, and a `char`'s under the default +number handling. + +| A minimal API's body of `Holder` | `NumberHandling = WriteAsString` | the default number handling | +|---|---|---| +| `T` is `char` | `JsonException`, the document is not written | `{"maxLength":1,"minLength":1,"type":"string","format":"char"}` | +| `T` is `int` | `{"pattern":"^-?(?:0\|[1-9]\\d*)$","type":["integer","string"],"format":"int32"}` | (not measured) | + +A value object over `char` is described with that same schema, asked of ASP.NET, so under this option its document +is refused as the wrapped type's is. Which of two refusals a document meets first follows the order ASP.NET visits +its endpoints and schemas in, so under a second refusing option (`MaxDepth`) a host with TypeKit, which asks for the +`char` while it describes the value object, can meet this one where ASP.NET alone meets the other. diff --git a/design/knowledge/README.md b/design/knowledge/README.md index 743db7fb..cdcad740 100644 --- a/design/knowledge/README.md +++ b/design/knowledge/README.md @@ -15,6 +15,18 @@ falsifiable, checked against a primary source. A competitor's feature is a fact `external {name} {version}`: no check compares it, so it is as fresh as its date. Take the next number, copy an entry's frontmatter, write the test first where a test can observe it, and add a row below. +**A fact that prunes or excuses is executable** ([decision 107](../adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)). +A `refused` entry of the axes file, a `not-promised` entry, an obligation's declaration and the probe harness may +cite only a fact with a test; `docs-query check` fails `fact-not-executable-cited` otherwise. A fact without one +explains, and decides no row. + +**A measured answer that is data is a fixture.** Where the fact's answer is a document, a status or an exception at an +operation, it is `test/CodoMetis.TypeKit.Facts/KBNNN.fixture.json` beside the test, named by a `fixture:` line after +`test:`: the fact's id and its `answers`, each an `id`, a `kind` (`document` with `at`, a JSON pointer; `status`; +`exception`), the settings it was measured under (`when`, axis to value as a row names them), the `value` and a +`note`. The test is a theory over the fixture's answers, measuring each (`KB001`), and `FactFixture` reads it, for the +test and later for the harness's `Baseline.Fact`. + | Id | Fact | Pins | Test | |---|---|---|---| | [KB-001](KB-001-openapi-2-nullable-body-drops-pattern.md) | Under OpenAPI 2.0, ASP.NET writes a nullable request body as `allOf: [{x-nullable: true}]` and drops its `pattern` | OpenApi 10.0.12, SDK 10.0.4xx | `KB001` | @@ -41,3 +53,21 @@ entry's frontmatter, write the test first where a test can observe it, and add a | [KB-022](KB-022-thinktecture-smart-enums.md) | Thinktecture.Runtime.Extensions 10.5.1 smart enums have a key apart from the identifier, a `Switch` over every member, and look keys up ignoring case | Thinktecture 10.5.1 | prose | | [KB-023](KB-023-a-converter-cannot-describe-its-schema.md) | A JSON converter of one's own cannot say what it writes: the schema exporter writes the empty schema, ASP.NET only a format | OpenApi 10.0.12, SDK 10.0.4xx | `KB023` | | [KB-024](KB-024-no-library-constrains-type-arguments-by-kind.md) | No .NET library constrains a type argument to be abstract, sealed, a record or concrete, or two arguments to differ | AdvancedGenericTypeConstraints 0.4.1, RossLean.GenericsAnalyzer 1.4.0 | prose | +| [KB-025](KB-025-the-coverage-collector-rewrites-assemblies-on-disk.md) | The code coverage collector instruments the assemblies on disk, so test hosts started together from one output directory corrupt each other, and its in-memory mode records no coverage on macOS arm64 | Microsoft.Testing.Extensions.CodeCoverage 18.12.0, SDK 10.0.4xx | prose | +| [KB-026](KB-026-openapi-2-nullable-component-property-loses-its-reference.md) | Under OpenAPI 2.0, ASP.NET writes a nullable property of a component as `allOf: [{x-nullable: true}]`, without the reference | OpenApi 10.0.12, SDK 10.0.4xx | `KB026` | +| [KB-027](KB-027-an-enum-component-is-its-first-use.md) | The component ASP.NET makes for an enum written by name is the schema of the first use it meets, null included | OpenApi 10.0.12, SDK 10.0.4xx | `KB027` | +| [KB-028](KB-028-postgresql-nan-is-greater-than-every-number.md) | PostgreSQL treats NaN as greater than every number, so `> 5` keeps a NaN row and NaN sorts after Infinity | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB028`, with `TYPEKIT_PG` | +| [KB-029](KB-029-npgsql-truncates-ticks-below-a-microsecond.md) | Npgsql truncates ticks below a microsecond, for every date and time type, and does not round them | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB029`, with `TYPEKIT_PG` | +| [KB-030](KB-030-datetime-maxvalue-reads-back-unspecified.md) | `DateTime.MaxValue` written to `timestamptz` as UTC reads back with `Kind` Unspecified | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB030`, with `TYPEKIT_PG` | +| [KB-031](KB-031-a-space-in-character-1-disappears-as-text.md) | A space in `character(1)` disappears when SQL converts it to text | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB031`, with `TYPEKIT_PG` | +| [KB-032](KB-032-postgresql-sorts-null-last-ascending.md) | PostgreSQL sorts NULL last ascending and first descending, and EF adds no `NULLS FIRST` | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB032`, with `TYPEKIT_PG` | +| [KB-033](KB-033-startswith-is-case-sensitive-and-order-follows-the-collation.md) | On PostgreSQL `StartsWith` is case-sensitive, and ordering follows the database collation, not ordinal order | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB033`, with `TYPEKIT_PG` | +| [KB-034](KB-034-numeric-p-s-rounds-on-write.md) | `numeric(p,s)` rounds a value with more fractional digits on write | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB034`, with `TYPEKIT_PG` | +| [KB-035](KB-035-npgsql-refuses-non-utc-for-timestamptz.md) | Npgsql refuses a non-UTC `DateTime` and a non-zero-offset `DateTimeOffset` for `timestamptz` | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB035`, with `TYPEKIT_PG` | +| [KB-036](KB-036-a-char-key-gets-an-identity-postgresql-refuses.md) | A plain `char` key gets an identity column on Npgsql, which PostgreSQL refuses | PostgreSQL 17.11, Npgsql EF 10.0.3, EF Core 10.0.12 | `KB036`, with `TYPEKIT_PG` | +| [KB-037](KB-037-mvc-under-max-validation-depth-1-runs-an-unbound-action.md) | Under `MaxValidationDepth = 1`, MVC runs an action whose parameter it could not bind, with the parameter at its default | SDK 10.0.4xx | `KB037` | +| [KB-038](KB-038-a-byte-array-is-a-base64-string.md) | System.Text.Json writes a `byte[]` as one base64 string, and every other collection as a JSON array | SDK 10.0.4xx | `KB038` | +| [KB-039](KB-039-sqlite-neither-compares-nor-orders-datetimeoffset-timespan-or-ulong.md) | EF Core's SQLite provider neither compares nor orders a `DateTimeOffset`, a `TimeSpan` or a `ulong`, and does both for a `decimal` | EF Core 10.0.12, its SQLite provider 10.0.12 | `KB039` | +| [KB-040](KB-040-a-converter-is-charged-a-level-of-max-depth.md) | Under `MaxDepth`, System.Text.Json refuses a value a converter of one's own writes at the limit, where its own converter writes it | SDK 10.0.4xx | `KB040` | +| [KB-041](KB-041-mvc-cannot-write-its-400-under-a-context-without-validation-problem-details.md) | MVC cannot write its automatic 400 under a source-generated context that does not list `ValidationProblemDetails` | SDK 10.0.4xx | `KB041` | +| [KB-042](KB-042-a-char-schema-is-refused-under-write-as-string.md) | Under `NumberHandling` with `WriteAsString`, ASP.NET refuses the OpenAPI document of an endpoint whose schema holds a `char` | OpenApi 10.0.12, SDK 10.0.4xx | `KB042` | diff --git a/design/mutation-2026-10-16.md b/design/mutation-2026-10-16.md new file mode 100644 index 00000000..cceba1a9 --- /dev/null +++ b/design/mutation-2026-10-16.md @@ -0,0 +1,79 @@ +# Mutation survivors, 2026-10-16 + +The first mutation run, a bounded trial on one satellite: Stryker.NET 5.0.0 with its `mtp` runner over +`src/CodoMetis.TypeKit.Serilog`, tested by `test/CodoMetis.TypeKit.Serilog.Tests` (10 tests, xunit.v3 +4.0.2 on Microsoft.Testing.Platform 2.5.1), at `373a860`. How it was run, the versions and why +Stryker works here are in [spikes/MutationSpike](../spikes/MutationSpike/README.md). A survivor is an +investigation target, never a product defect: a missing assertion, unreachable behaviour, or an +equivalent mutant. + +## Counts, per package + +| Package | Mutants | Killed | Survived | Timeout | No coverage | Compile error | Ignored | Score | Run time | +|---|---|---|---|---|---|---|---|---|---| +| CodoMetis.TypeKit.Serilog | 15 | 3 | 3 | 0 | 0 | 7 | 2 | 50.00 % | 17.7 s cold, 8.4 s warm | + +Of the 7 compile errors, 4 are mutants that cannot compile (they read a pattern variable the mutation +left unassigned) and 3 are valid mutants Stryker rolled back with them; see "Lost to the compile". + +## Survivors + +| # | File:line | Mutation | Reading | +|---|---|---|---| +| 1 | [TypeKitSerilogExtensions.cs:23](../src/CodoMetis.TypeKit.Serilog/TypeKitSerilogExtensions.cs) | `ArgumentNullException.ThrowIfNull(destructure);` removed | **Missing assertion.** No test calls `WithTypeKit` on `null`. Without the guard, the next line's `destructure.With(...)` throws `NullReferenceException` instead of `ArgumentNullException` naming `destructure`, a difference a caller sees | +| 9 | [ValueObjectDestructuringPolicy.cs:43](../src/CodoMetis.TypeKit.Serilog/ValueObjectDestructuringPolicy.cs) | `GetCustomAttribute(inherit: false)` to `inherit: true` | **Equivalent.** The attribute is `[AttributeUsage(..., Inherited = false)]` ([GeneratedValueObjectAttribute.cs:57](../src/CodoMetis.TypeKit/CompilerServices/GeneratedValueObjectAttribute.cs)), so walking the base types finds nothing more; and an attribute found on a base would fail `ValueObjectType == candidate` anyway | +| 13 | [ValueObjectDestructuringPolicy.cs:44](../src/CodoMetis.TypeKit.Serilog/ValueObjectDestructuringPolicy.cs) | `GetCustomAttribute(inherit: false)` to `inherit: true` | **Equivalent**, for the same reasons: `Inherited = false` ([GeneratedClosedSetAttribute.cs:32](../src/CodoMetis.TypeKit/CompilerServices/GeneratedClosedSetAttribute.cs)), and `SetType == candidate` | + +Each survivor was confirmed outside Stryker: seeded into `src/` and built by MSBuild, every one left +all 10 tests passing. + +## The missing assertions, and where each goes + +One survivor of three is a missing assertion, so there is one, not three: + +1. **Survivor 1**, in `DestructuringTests` + ([test/CodoMetis.TypeKit.Serilog.Tests/DestructuringTests.cs](../test/CodoMetis.TypeKit.Serilog.Tests/DestructuringTests.cs)). + No existing test reaches the guard, so the assertion is a new fact beside + `Scalars_and_other_types_are_left_to_Serilog`, in the form the AspNetCore tests use for theirs + (`FaultExclusionTests`): + `Should.Throw(() => TypeKitSerilogExtensions.WithTypeKit(null!)).ParamName.ShouldBe("destructure")`. + +Not written in this trial. + +## Lost to the compile + +Stryker's mutant schemata wrap the mutated expression in a ternary over the original, so a pattern +variable the expression declares (`is { } valueObject`) is declared twice (CS0128), and every mutant +whose span holds that error is rolled back as a compile error. These three were valid and are not in +the score. Seeded by hand and built by MSBuild, each was killed: + +| # | File:line | Mutation | Tests that failed | +|---|---|---|---| +| 6 | ValueObjectDestructuringPolicy.cs:43-44 | the outer `\|\|` to `&&` | 7 of 10 | +| 10 | ValueObjectDestructuringPolicy.cs:43 | `valueObject.ValueObjectType == candidate` to `!=` | 4 of 10 | +| 14 | ValueObjectDestructuringPolicy.cs:44 | `set.SetType == candidate` to `!=` | 3 of 10 | + +The four that cannot compile: mutants 7 and 11 (`is { } v && v.X` to `||`) and 8 and 12 (`is { } v` +to `is not { } v`) each read `v` where it is unassigned (CS0165). + +## All mutants + +| # | Line | Mutator | Status | +|---|---|---|---| +| 0 | Extensions 22 | block removal | ignored (block already covered) | +| 1 | Extensions 23 | statement | survived | +| 2 | Policy 34 | block removal | ignored (block already covered) | +| 3 | Policy 35 | conditional to `true` | killed | +| 4 | Policy 35 | conditional to `false` | killed | +| 5 | Policy 37 | `is not null` to `is null` | killed | +| 6 | Policy 43 | logical `\|\|` to `&&` | compile error (rolled back, valid) | +| 7 | Policy 43 | logical `&&` to `\|\|` | compile error (CS0165) | +| 8 | Policy 43 | `is { }` to `is not { }` | compile error (CS0165) | +| 9 | Policy 43 | boolean `false` to `true` | survived | +| 10 | Policy 43 | `==` to `!=` | compile error (rolled back, valid) | +| 11 | Policy 44 | logical `&&` to `\|\|` | compile error (CS0165) | +| 12 | Policy 44 | `is { }` to `is not { }` | compile error (CS0165) | +| 13 | Policy 44 | boolean `false` to `true` | survived | +| 14 | Policy 44 | `==` to `!=` | compile error (rolled back, valid) | + +Extensions is `TypeKitSerilogExtensions.cs`, Policy `ValueObjectDestructuringPolicy.cs`. diff --git a/design/openapi.md b/design/openapi.md index e7e42544..d17eb87a 100644 --- a/design/openapi.md +++ b/design/openapi.md @@ -31,7 +31,13 @@ position, and the change lands in the hoisted component. `format` and drops `items`/`additionalProperties` for an element of one (measured, 10.0); the description goes in every JSON position and the containers are repaired where they lack one. A body is a parameter too, and every schema in one; only a non-body parameter binds through `TryParse`, and it - gets back the schema the type has without converters of one's own, beside ASP.NET's `format`. A + gets back the schema the type has without converters of one's own, beside ASP.NET's `format`; so does + the element of a query or header array of it, whose `items` ASP.NET dropped, and a value object over it, + whose wrapped type is asked for as the parameter's. Below a parameter, the wrapped type and an element are + asked for with the parameter's source and type alone, since ASP.NET applies a parameter's default and + attributes to whatever it is asked for. Where value objects are components, such a value object is written + inline as a parameter, because its component is the JSON positions' + ([decision 106](adr/0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md)). A description is parsed by `OpenApiModelFactory.Parse` (Microsoft.OpenApi 2.0 on), measured to survive Native AOT trimming in the smoke test's `aot` consumer, then written back in 3.1 and compared with what was described: a keyword the model does not hold is written aside as unrecognised, where no @@ -47,6 +53,15 @@ position, and the change lands in the hoisted component. makes and a `Nullable` before `T`. A described converter one property names for a type with a reference id fails the document as well, and so does one registered for the `Nullable` of such a type alone: the component is written once, for every position. +- **A non-abstract polymorphic base** is serialized as itself too, and its schema is an `anyOf` of each derived + type's branch and, last, the base's own (`x-schema-id` `Base`, the component `{Base}Base`). ASP.NET hands the + branches to the transformers by `PolymorphismOptions.DerivedTypes`, one per index, and never the base's own + (measured with 10.0, and read in `InnerApplySchemaTransformersAsync`), so its value objects, patches, closed sets + and described types were `{}` and its lists lost their `items`. Each transformer walks that branch from the + polymorphic schema, in ASP.NET's order (`PolymorphicBaseBranch`). A transformer context cannot be pointed at + another type from outside, so each visit gets one of its own, and a schema it asks for is asked of the context + ASP.NET made, which alone holds the document's transformers. 2.0 leaves the base's branch out of the root's + `allOf`, for any base: ASP.NET's own. - **A value object that reaches itself** through what it wraps is an `InvalidOperationException` naming the chain, not a stack overflow. - **Source-generated JSON** ([Native AOT](native-aot.md)): ASP.NET builds the wrapped type's schema from the host's JSON diff --git a/design/plan.md b/design/plan.md index 6fd3270a..9000757b 100644 --- a/design/plan.md +++ b/design/plan.md @@ -21,6 +21,9 @@ you touch; the rest is reference. | [Analyzer package and rule set](analyzers.md) | Packaging, the rule table with severities, and the limits of each rule | with the area | | [Native AOT](native-aot.md) | What is measured, how, and what each package asks of a consumer | with the area | | [Fix history](fix-history.md) | Every `fix:` to 2026-10-15 by defect class, finder, sibling chain and release escape: the baseline for reviewing | when the classification is run again | +| [Mutation survivors, 2026-10-16](mutation-2026-10-16.md) | The first Stryker.NET run, on the Serilog satellite: counts per package, each survivor read as a missing assertion or an equivalent mutant, and the valid mutants the compile lost | one dated page per run | +| [Finder benchmark, 2026-10-16](finder-benchmark-2026-10-16.md) | The 24 release escapes put back as seeds: which test, probe row or nothing detects each, totals per finder as applied, reached and detected, and the accounting every probe round and analyst run reports | one dated page per run | +| [Probing without the tooling](probing-without-tooling.md) | How BLite applied the probing concept by hand, with no prober, covering array or ledger: what each tool function became, the process as it ran, the issues and PRs it produced, and an adoption guide for trying the same here without the tooling | copied 2026-10-18; as the method is tried here | The spikes that decided something are in [spikes/](../spikes/), each with a README stating the question, the answer and the evidence. A spike's README names the decision it led to. diff --git a/design/probing-without-tooling.md b/design/probing-without-tooling.md new file mode 100644 index 00000000..5921c441 --- /dev/null +++ b/design/probing-without-tooling.md @@ -0,0 +1,958 @@ +# Probing without the tooling + +How BLite applied CodoMetis.TypeKit's probing concept by hand, with no prober, no covering arrays and no +ledger, in two days of October 2026; what it produced; and how to do the same in any repository. + +Written 2026-10-18 from the Claude Code session transcripts of the BLite work, the branches they left on +EntglDb/BLite, the issues they filed, and TypeKit's own files for contrast. Every statement carries a +source tag in square brackets; the appendix resolves each tag to a transcript position, a file path or an +issue number. Nothing here is reconstructed from memory. + +A note on names. TypeKit calls the thing a *probe*; BLite called the same thing an *oracle test*. The +document uses BLite's word where it describes BLite and TypeKit's where it describes TypeKit, and says +"catalogue test" when it means the generic idea: a test whose rows are the product of the settings a +consumer varies, each judged by an exact reference. + +--- + +## 1. The concept that was ported + +This section defines the vocabulary of TypeKit's probing in plain terms and says what each part is for. +The definitions come from TypeKit's axes file, the prober agent's instructions and the Prober README +[K1] [K3] [K2]. Knowing what each part exists *for* is what made it possible to drop the part and keep +the purpose. + +**Claim.** A sentence the project's own documents state about its behaviour, in a form a program can +check: "under these conditions, this observable result". TypeKit keeps them in `claims.json`, each with +an `id`, the `source` as `file#heading`, the surfaces it belongs to, and later `obligations` (sentences +quoted verbatim from the source) and `not-promised` entries (the sentence that makes a difference +intended) [K1 "Claim sources", "Axes"] [K2 "Files"]. A claim exists so that a test checks a promise the +project made, not an expectation the tester invented. A vague sentence ("works well with EF Core") is not +a claim and is skipped [K3 §4]. + +**Surface and axis.** An axis is a dimension a consumer can vary; its values are what they can choose. A +surface groups axes: a claim about JSON takes every JSON setting, every type shape and every position, +"because that is what a consumer can combine with it" [K3 §3]. Axes exist because, in TypeKit's own +words, "defects that survive review are combinations nobody listed: an option of a serializer, a hosting +model, a document version, a type shape. So you do not imagine the dimensions to vary. You derive them" +[K3 preamble]. TypeKit derives them two ways: *generated* axes, enumerated by reflection over every public +settable property of each options type the feature reads (`enumerate.cs`, 56 lines), and *hand-listed* +axes and a *type-shape catalogue* in a fenced `json probe-axes` block of `.claude/probe-axes.md` [K1 +"Axes"] [K8]. + +**Covering array and row.** A row sets every axis of a claim to one value. A pairwise covering array is +the smallest set of rows such that every pair of values of any two axes appears in at least one row; "a +claim with ten thousand pairs takes about three hundred rows" [K3 §6.3]. It exists to make a large +product affordable: the full product of ten axes with five values each is nearly ten million rows, the +pairwise array a few hundred. `prober rows` computes it, and "do not choose pairs yourself" [K3 §6.3]. + +**Probe.** One parameterised test per claim, fed by its rows, written in the consumer's own position (in +TypeKit, the Samples project, "the closest thing to a consumer") [K1 "Harness"]. A probe applies a row to +the consumer's configuration (options, host, declared type) and runs one *check per obligation*. A check +returns null or the failure text; it never decides what a refusal means [K1 "Harness"] [K3 preamble]. + +**Verdict, outcome, evidence.** A row ends in a category the harness assigns from the check's note key, +not from the text: pass, violation, harness error, inconclusive, expected refusal, inapplicable. "Never +match a failure's text to excuse it" [K1 "Harness"]. The replay writes one evidence line per row with its +checks, the settings that applied and the reasons; a pair counts as covered only by a pass "in a row that +was expected and executed, for the settings that actually applied, while the result's identity holds" +[K2 "Files"]. This exists so that coverage is counted by a script from facts, never by the model's +impression of what it ran. + +**Triage.** A failing row "is a question, not a finding" [K5 preamble]. TypeKit's triage skill asks four +questions in a fixed order and stops at the first that applies: a harness error; a fact about the +framework at the pinned version explains it; the claim's `not-promised` says it is intended; else a +finding, handed to the code-analyst agent for confirmation [K5 headings]. The order exists because "a +harness that compares unlike things produces the most convincing product bugs there are" [K5 preamble], +and because a wrong finding costs more than a missed one: the caller stops trusting the rounds [K3 +preamble]. + +**Seed.** A patch against `src/` that breaks what the claim promises, kept beside the probe, which the +probe was shown to catch. "A probe that passes proves something only if it can fail" [K1 "Seeds"]. Since +decision 104 a seed per *obligation*, so that a probe is shown to check each promise, not merely to be +able to fail somewhere [K1 "Seeds"]. + +**Corpus and replay.** Everything a round wrote (probes, rows, claims, registry, seeds) is tracked under +`tools/probe/corpus/`, and `prober replay` re-runs it with no model after each commit, "only for the +probes whose coverage footprint the commit changed", opening a finding when a probe that passed starts +failing [K3 preamble] [K2 "What runs where"]. The footprint is the set of `src/` files a probe executed in +its own process, measured by code coverage, plus declared `watched` paths [K2 "Files"]. The corpus exists +so that exploration is paid for once and the check is free afterwards. + +**Round, budget, lock, gate.** A round is a bounded exploration: "two claims or ten minutes of work after +the first build, whichever comes first", enforced by the script, not the model [K3 §6.7] [K4]. A lock +makes two rounds on one branch impossible [K2 "Files"]. The gate is the release check: it refuses while a +claim has uncovered pairs, unless a reviewed waiver says so [K2 "What runs where"]. These exist to keep +exploration from running away and to make "done" a word a script says [K3 §8 last paragraph]. + +**Fix trailers.** Every fix commit carries `Found-By:` and `Defect-Class:` trailers, so that six months +later the question "which finder found what" is answered by `git log` alone [K7 header] [K1]. + +--- + +## 2. What TypeKit's tooling does, and what BLite did with each function + +TypeKit's probing is about 6,600 lines of tool and harness [K6 Appendix A: "Harness size: `wc -l`, 6,617 +lines"]: `tools/Prober` (about 3,900 lines plus schemas), the `BuildVerdict` tool, `tools/probe/corpus/` +with its harness and generators, `enumerate.cs`, the agent, two skills and a hook [V105 inventory] [K8]. +BLite's whole framework is 188 lines: `Candidates.cs` (52), `OracleSeedsTests.cs` (58) and +`tools/oracle-seeds.cs` (78) [F2] [F3] [F4]. The table says, function by function, what happened to +each. + +| TypeKit function | What it does mechanically | BLite | How, and evidence | +|---|---|---|---| +| `.claude/probe-axes.md` | Prose for the writer plus a `json probe-axes` block: hand axes, ignored settings, refused values, surfaces, replay configuration, harness rules [K1] | Dropped; replaced by the test's own source | Each test's `

` and `` state the contract and the ways it runs; the axes are the nested loops that build its `TheoryData` [F5 lines 1–30] [F6 lines 9–50]. A fork-only `.claude/oracle-catalogue.md` (27 lines) lists written and not-yet-written oracles with what each compares and catches [F13] | +| `tools/probe/enumerate.cs`, generated axes | Reflection over every public settable property of each options type at the pinned versions; unassigned settings reported [K1 "Axes"] | Dropped | BLite's "options" are the engine's modes (indexed or plain, sync or async, crash mode, backend, publish variant), which the writer listed by hand from the API and the README section [T2 brief] [T5 brief] [T7 report] | +| Type-shape generators (`tools/probe/generators/*.cs`) | File-based apps that emit the declarations of the shape catalogue as `*.g.cs`, held to their output by a conventions test [K1 "Harness"] | Dropped; replaced by one hand-written document type per test | `DiffDoc` with string, int, long, double, decimal, DateTime, bool, nullable and enum properties; the value lists carry the boundary cases (`-0.0`, `5m` beside `5.00m`, `"p7"` beside `"p70"`, int and long extremes, nulls) [F5 `Generate`] | +| `claims.json` with `surfaces`, `narrow`, `exclude`, `obligations`, `not-promised`, `inapplicable` | The claim catalogue the universe is built from; quotes checked verbatim against the source file; a schema refuses unknown fields [K1 "Axes"] [K2 "Files"] | Dropped | The README section *is* the claim. The brief names the sections to read and the recent fix commits whose shapes the test must cover [T2 brief]; the test's `` quotes the contract ("README 'Transactions & ACID', the write methods' docs") [F6 lines 18–27]; the issue body quotes the README line it contradicts [I260] | +| `prober universe` | Builds each claim's axes and pairs from the surfaces; prints narrowings and exclusions for a reviewer [K2] [K3 §6.2] | Dropped | Not needed without a claims file | +| `prober rows` (pairwise covering array) | The smallest row set covering every pair not yet current [K3 §6.3] | Replaced by the full product, kept small by hand | 331 shapes × 2 collections × 2 modes [F5]; 9 key generators × up to 33 checks [T5 report]; 6 seeds × 60 steps × 3 crash modes × every write [T7 report]; 49 scenarios × 3 backends × 4 publish variants [T13 report] | +| `prober new`, `prober try` | Scaffold a probe with one throwing check per obligation; run one probe's rows in the replay's worktree [K2] | Dropped | The writer copies the previous oracle's shape; the brief says "following the pattern of the two oracle tests in PR #250" and "read docs/ORACLE_TESTS.md and tests/BLite.Tests/Oracles/*.cs on that branch before writing anything" [T5 brief] | +| Harness (`Row`, `Rec.Run`, `NoteKeys`, `Cite`, `Rows.Data`, 639-line `Row.cs`) | Outcome categories from compile-checked note keys; skips tied to open findings; citations to facts and records [K1 "Harness"] [K8] | Replaced by a 52-line `Candidates` helper | Three outcomes: unlisted disagreement fails; listed with an empty issue fails labelled; listed with an issue passes and logs `SKIPPED`; a listed row that agrees fails with `candidate fixed` [F2] | +| `BuildVerdict` tool | One-line build and test verdict, pinned English output, failing tests by name [K1 "Harness"] | Dropped | `dotnet test --filter FullyQualifiedName~BLite.Tests.Oracles` and a TRX log read with a short script when the skip lines were needed [T8 07:41] [F4] | +| `prober register`, `publish`, staging | A probe is staged in the agent's memory and published into the corpus by a command; a registry entry per probe [K3 §6.5] | Dropped | The test file is committed like any test; the "registry" is the `oracles.json`-free folder `tests/BLite.Tests/Oracles/`, discovered by `OracleSeedsTests` as `*Tests.cs` [F3] | +| Replay by coverage footprint, `watched` paths | Re-run only the probes a commit touched; a footprint per probe measured by coverage [K2 "Files"] | Dropped | Every oracle test runs on every `dotnet test` and in CI; each class is kept to about a minute [T5 report "about 55 s"] [T7 report "in about a minute"] | +| Ledger (`universe.json`, `ledger.jsonl`, `findings.jsonl`, `footprints.jsonl`, `runs.jsonl`, `dismissed.jsonl`) | Per-branch state; pairs covered, findings opened and closed, dismissals as decisions [K3 §8] | Dropped; replaced by the registry, the drafts file and the issues | `Candidates` in the test, `Seeds/candidate-issues.md` with the drafts and a table mapping each family to its draft, and the GitHub issue [F9] [T4 22:26] | +| `prober round`, lock, budget | A round under caps the script enforces; one round per branch [K4] | Replaced by one agent session per oracle | Each test was one session with a written brief and a worktree; the parent held the sequencing ("hold the PRs until #250 has had its first review") [T1 06:19] | +| `prober gate`, waivers | Refuses a release while a claim has uncovered pairs [K2] | Replaced by a rule in the epic | "A section counts as stable in the README's stability matrix once its oracle is merged and its registry is empty" [I288 "Oracle coverage by library section"] | +| `prober seeds` (decisions 100, 104) | Applies each seed in a scratch worktree, builds, replays, requires a violation the unseeded probe lacks; one per obligation [K1 "Seeds"] | Kept, reduced | `OracleSeedsTests` (CI, no build: a seed exists per test, touches only `src/`, still applies) and `tools/oracle-seeds.cs` (local: scratch worktree, build, run the class, require a failure not labelled `[known candidate:`) [F3] [F4]. One seed per test, not per obligation; a second seed where the first proves only part (two in the dynamic tests, two in the AOT tool) [T9 report] [T11 report] | +| `prober status`, `gaps` | Worklist and what the corpus leaves out, with reasons [K2] | Replaced by a table in the epic | The epic's "Oracle coverage by library section": written and in review, not pushed, next, and "not covered by any oracle, stays with review" [I288] | +| `prober dismiss` | A dismissal is a decision recorded in the corpus [K3 §8] | Dropped | A row registered as intended cites the README line in the registry comment; none was needed in the end ("No README-intended rows") [T9 report] | +| `probe-triage` skill, four steps with pinned facts | Harness error; fact at the pinned version; not-promised; finding for the analyst [K5] | Kept, rewritten for the domain, fork-only | `oracle-triage`: the oracle or model is wrong; both paths agree but the reference differs (a semantics question for the README); only the indexed path diverges (an `IDX` bug); hand off to the code-analyst [F13 SKILL.md] | +| `prober` agent, `/probe` skill | A headless explorer under caps; modes replay, explore, full, status, gate, triage [K3] [K4] | Dropped | The parent session wrote a brief per oracle and started an ordinary session [T1 throughout] | +| `code-analyst` agent | Confirms a finding and lists siblings [K5 §4] | Kept as is | "The code-analyst agent is already global in your home directory and runs here today" [T1 21:11]; confirmation in practice was throwaway repro `Fact`s read against the code [T2 22:03] [T9 08:17] | +| Fix trailers | `Found-By:`, `Defect-Class:` [K7] | Kept, with BLite's classes | `RV OT IS US CA`; `IDX WAL TXN BSON LINQ MP PLT`; a `git log` one-liner counts them [F12 "Commit trailers"] | +| Checkout-guard hook on the agent | Refuses edits to the caller's checkout [K3 frontmatter] | Dropped | Worktrees per session instead [T2 header] | + +The one thing BLite added that TypeKit does not have in this form: the registry row carries the issue +number and the rule is self-cleaning. TypeKit's `r.Skip("F7", why)` ties a skip to an open finding in the +ledger [K1 "Harness"]; BLite ties it to the GitHub issue and makes the fix PR delete the row [F2] [F1 +"Candidate registry"]. + +--- + +## 3. The lightweight process, step by step, as BLite ran it + +### 3.1 The decision, and the order of entry + +The question was put on 2026-10-16 at 21:09: "I want to try out the bug-detection machinery that is +already implemented in CodoMetis.TypeKit and adopt it to an up-to-date fork of the upstream repo" [T1 +21:09]. Two minutes later the answer was: "your instinct is right about the approach, but wrong about +which part to port" [T1 21:11]. The reasoning was TypeKit's own record: + +| Finder | Fixes of 133 | Cost recorded | +|---|---|---| +| Review sessions and outside reviews | 89 | hours per session, 44 sessions | +| code-analyst agent | 10 | three runs on one day | +| prober | 3 real, 1 false | about a week, 6,617 lines of tool and harness | + +[T1 21:11, from K7 "Class by found-by": RS 69 + OR 20, CA 10, PR 3]. "Harness traps outnumbered product +defects found by probes roughly ten to one" [T1 21:11]. And TypeKit's concept note had already written +the transfer rule for exactly this case: "Two ordinary tests in the existing test project first, no +tooling: the model-based or differential test for the product's central obligation, and a round-trip or +refusal test for its second, each with a seed that shows it can fail. If they find nothing in a week, +stop; the rest is not worth installing there. If they find what the profile predicts, they have earned +the obligations file, the seeds and the corpus" [K6 §12]. The session quoted this and added why BLite +would do better than TypeKit: "BLite has exact ones where TypeKit had to construct a twin": LINQ-to-Objects +for the query provider, a dictionary for the store, round trips for BSON and encryption [T1 21:11]. + +What to keep was decided the same evening, after the first results: "exact oracles with a seed each, the +rule that every guard is shown to fail, fix trailers that make finders countable from git log, a fixed +triage order, and the code-analyst agent. What did not pay off at its cost: pairwise covering arrays, +axes enumerated by reflection, generated type-shape harnesses, the ledger with locks and rounds, the +obligations schema and the gate. The first list is a few hundred lines. The second is six thousand, and it +is TypeKit-shaped" [T1 21:55]. The split by location followed from the repository being someone else's: +"only tests and plain conventions go there. Agents and skills stay in your fork and your home directory" +[T1 21:55]. + +### 3.2 Who derives the axes, from what, and where they are written + +Nobody enumerated settings by reflection. The axes of each test were chosen by the session that wrote +it, from three inputs the brief named: the README sections that make the promise, the public API's modes, +and the recent fix commits whose defect shapes the test must reach [T2 brief: "Read README.md 'Advanced +Indexing', 'Powerful Query Engine (LINQ)' and the BLQL sections for what is promised; look at the recent +upstream fix commits (7dbe21d, f8f437b, 33c6bbf, ffdaaba, 7e37f33, 70eb3c3) for the defect shapes this +must cover"]. The axes are written nowhere but in the test file, as the loops that build the theory data, +and in the row's name, which carries its coordinates. The documentation then describes them in prose +[F1 "What it is"]. + +What the axes were, per oracle: + +| Oracle | Axis 1: the case | Axis 2: the ways | Axis 3 | Reference | Source | +|---|---|---|---|---|---| +| LINQ differential | 331 hand-listed query shapes, grouped by property type and operator (`count == 5`, `count < 9.5 (double bound)`, `count two lower bounds`, `orderby maybe take 20 (nulls)`) | indexed collection, plain collection | sync, async | LINQ-to-Objects over the seeded list | [F5 `BuildCatalogue`] [T2 report] | +| Store model | 8 seeds of 300 random steps over 13 operation kinds | single-process, multi-process | plaintext, encrypted (added later) | dictionary with committed and pending views | [F6] [T2 report] | +| B-tree model | 9 key generators (int, long and double extremes, prefix strings, control bytes, keys up to the size limit, unique, multi-process) | up to 33 checks (each query primitive, cursor walks, structure, page accounting) | | sorted dictionary in the index's own key order, plus "reachable pages plus free list plus queued frees equals pages allocated" | [T5 report] [F1] | +| WAL crash | the store model's sequences, 6 seeds of 60 steps | 3 crash modes (process crash, torn write, power loss) | 8 checks; every write a crash point | the store model's committed view after reopen, plus the page invariant | [T7 report] [F8] | +| DynamicCollection | the same 331 shapes | dynamic indexed, dynamic plain | API, BLQL | LINQ-to-Objects **and** the typed collection over the same documents | [T9 brief and report] [F14] | +| Native AOT | the consumer's sections (store sequence, encryption, DynamicCollection with CDC and BLQL, key-value store, BLite.Caching) | JIT, Native AOT | the publish log | the JIT dump, byte for byte; the publish log against the document's promise | [T11 report] [F10] | +| Browser | 49 scenarios (later 54) | OPFS, IndexedDB, in-memory | 4 publish variants; later 3 browsers and 2 context modes | the scenario's own contract (a committed write survives reload; two tabs keep both writes) | [T13 report] [T15 report] | + +Two details show how the axes were kept honest without a script. First, the generators were guarded: the +B-tree test fails if a key generator did not force what it was meant to force ("Every generator gave pages +back at least 4 times, and the longest duplicate runs held 133–388 entries per key"; "generator guards" +counted among the seed's failures) [T5 report]. Second, where a way cannot express a case, the row says so +rather than passing: "Six query shapes BLQL can't express are listed with their reason and print +`INAPPLICABLE`", and a new one fails a test until it is listed [T9 report] [F14 lines 43–44]. + +The value lists were chosen for boundaries, not breadth: counts `[-7, -1, 0, 0, 1, 2, 3, 5, 5, 5, …, +100]`, ratios including `-0.0`, `0.0`, `1e300`, `double.Epsilon`, prices with `5m` and `5.00m`, nicks with +two nulls and an empty string, and exactly one document at each integer extreme "for Single and for bound +tests" [F5 `Generate`]. Documents are inserted in a shuffled order "so that no index order is the scan +order" [F5 `Fixture`]. + +### 3.3 How claims were collected and tagged + +There is no claims file. The unit of a claim is a README section, and the unit of coverage is "one +oracle for every library section the documentation makes a promise about" [I288]. The tagging that +TypeKit does with `surfaces` happened in three places: + +1. **In the brief.** The parent session named the sections and the API the oracle must read before + writing ("Read README.md 'Transactions & ACID', 'Hot Backup', 'Multi-Process WAL' and docs on + checkpointing for what is promised") [T7 brief]. +2. **In the test's remarks.** The store model's `` begins "The contract the model encodes (README + 'Transactions & ACID', the write methods' docs):" and then states it as rules: a write without a + transaction commits on its own; a `BLiteValidationException` leaves the transaction usable while any + other failure rolls it back; a checkpoint, a backup and a reopen change nothing [F6 lines 18–27]. +3. **In the issue.** The body quotes the sentence the behaviour contradicts, with its line: `README + "Transactions & ACID" (README.md:394): "Durable: WAL ensures data safety even in power loss."` [I260]. + +The parent session named this as one of the three reasons it worked: "Claims as the test list. Every +test started from a sentence the repository makes: durable in power loss, the index is used +transparently, this API is AOT-safe, BLQL supports these operators. The oracle checks the sentence, which +is why the findings read as broken promises rather than opinions" [T1 08:48]. + +### 3.4 How a probe was written + +**File layout.** `tests/BLite.Tests/Oracles/Tests.cs` in namespace `BLite.Tests.Oracles`; entity +contexts in `tests/BLite.Shared/`; seeds in `Oracles/Seeds/Tests..patch`; drafts in +`Oracles/Seeds/candidate-issues.md`; a two-line `Oracles/README.md` pointing at `docs/ORACLE_TESTS.md` +[F1 "How to use it"] [T3 report step 1]. Tools that cannot be unit tests (a Native AOT publish, a +browser) live under `tools/aot-smoke/` and `tests/BLite.Wasm.BrowserTests/` with the same conventions: +a `candidates.md` table the runner reads, a `seeds/` directory, a `--prove-seeds` mode [F10] [F11]. + +**Test framework.** xunit 2.9.3, as the repository already had; a `[Theory]` over +`[MemberData(nameof(Cases))]` where `Cases` is a `TheoryData` of row names, and the test looks the +case up by name [F5 lines 561–580]. The model tests use `TheoryData` built by nested +loops over seeds, process mode and encryption [F6 lines 40–50]. Nothing was added to the test project's +references. + +**Shape of the theory body.** For each way the case runs, run it, capture the value *or the exception* +as an `Outcome` ("both are compared"), compare against the reference with the case's own rule (sets by id +for unordered queries, key sequences for ordered ones, values or the exception type for terminals), +collect one line per disagreement naming the way (`indexed/async: …`), and hand the lines to the registry: + +```csharp +s_candidates.Verify(name, errors.Count == 0 ? null + : $"seed {_f.Seed}: query '{name}' disagrees with LINQ-to-Objects:\n " + string.Join("\n ", errors), _output); +``` + +[F5 lines 561–586]. The failure text carries the seed and the row name, so a run can be repeated +(`BLITE_DIFF_SEED=`) [F1 "How to use it"]. + +**Shared pieces.** The store model's generator and model were moved into `StoreModel.cs` when the WAL +test needed the same sequences; the dynamic tests reused the differential catalogue by extracting it; the +AOT consumer copied the generator [T7 report] [T9 08:00] [T11 08:17]. The page-accounting walk of the WAL +test was made `internal` for the dynamic model test "rather than copied" [T9 08:07]. + +**Local tools follow the same shape.** The AOT tool's `candidates.md` has a machine-readable table at the +top (`Id | Kind | Match | Issue | Summary`) and the drafts below it; `run.sh` prints `KNOWN … [known +candidate: A]` for a matching warning or dump line and fails on anything else [F10]. The browser +harness's `Candidates.cs` reads the same kind of table, with a `Kind` of `always` or `sometimes`, because +"a pass proves nothing" for a row that fails only in some runs [F11 Candidates.cs]. + +**Naming and endings.** Row names are human sentences with the operand in them (`count < 9.5 (double +bound)`), family labels state the hypothesis (`null equality on an indexed property finds nothing`) [F5 +lines 136–155]. Upstream uses CRLF, the user's global git has `autocrlf=input`, so every new file was +converted and staged with `git -c core.autocrlf=false add`; this cost time in every session and is in +every brief [T4 report] [T5 brief]. + +### 3.5 How the covering array, or its substitute, was chosen + +BLite ran the full product every time and kept it small by choosing values, not by choosing pairs. +The sizes: + +| Oracle | Rows per run | Run time | Source | +|---|---|---|---| +| LINQ differential | 331 cases, each 4 ways | not recorded on its own; the whole oracle filter of the first branch (both tests) ran inside the full suite's few minutes | [T2 report] [T4 report] | +| Store model | 16 rows (8 seeds × 2 modes), 300 steps each | about 3 s plaintext; longer encrypted | [T1 21:45] | +| B-tree model | 270 tests (9 generators × up to 33 checks) | about 55 s | [T5 report] | +| WAL crash | 24 rows (3 modes × 8 checks), 1,886 crash states | about a minute | [T7 report] | +| DynamicCollection | 3,335 tests | 1 min 16 s | [T16 11:02] | +| Encrypted, whole oracle set | 680 tests | 3 min 2 s | [T16 11:07] | +| AOT tool | 871 dump lines, two publishes | about 20 s; 37 s with seeds | [T11 report] | +| Browser | 53–54 rows | about 5 minutes per browser | [T15 brief] | + +Where the product would have been too large, it was sampled by a rule stated in the code, not by a +pairwise algorithm: the WAL test crashes recovery only at "sampled crash states" ("every third such step, +or every one when shrinking") [F8 line 252] [T7 report]; the browser harness publishes four variants but +runs the same 49 scenarios in each [T13 report]. The parent session's position on pairwise arrays was +explicit: "If the catalogue of query shapes grows past what a hand-written list holds, a pairwise +generator is a fifty-line addition to the test, decided then" [T1 21:55]. It was not needed in the two +days. + +### 3.6 How a run was budgeted + +There was no round budget in the TypeKit sense. The units of budget were: + +- **One session per oracle**, with a written brief that fixed the scope, the files to read first, the + base branch, the seed recipe and "done means" [T2 brief] [T5 brief] [T7 brief]. Sessions took 30 to 55 + minutes each (3.4 below). The brief forbade pushing until the maintainer had reviewed the first PR, so + the sequencing was the parent's, not the tool's [T5 brief last sentence]. +- **Per-class run time**, held to about a minute so the class can stay in the normal suite [T5 report] + [T7 report]. +- **Shrink budgets** inside a failing row: 45 s for the store model, 20 s then 10 s for the WAL test, and a + 60 s watchdog around the B-tree walk after a seeded run hung "for over 10 minutes inside an engine + traversal" [F6 line 88] [F8 line 100] [T5 report]. +- **Drafts before the session ends.** Every writing session ended with drafts for what it found, so that a + filing session could run without re-deriving anything [T5 report] [T7 report] [T9 report]. + +### 3.7 How a failing row was judged and shrunk + +**The oracle judges.** A row fails only when its reference disagrees; the writer writes no expectation. +The three outcomes are the registry's, quoted in full from the helper: "Passes a row that agrees and is +not listed, or that disagrees and is listed with an issue; fails every other row" [F2 `Verify` doc +comment]. A listed row that agrees fails with `candidate fixed: remove the entry for ''`, "so the +list never goes stale" [F2 class comment]. + +**Shrinking.** The model tests shrink a failing sequence by removing chunks "half the sequence, then +quarters, … down to single steps", replaying after each removal, within the budget, and print the +shortest sequence that still fails, one operation per line, with the seed and the step [F6 lines 164–192] +[F1 "Read a failure"]. The WAL test shrinks to "the shortest sequence that still fails at a crash in its +last step" and prints the crash point as "crash after FreePage(24): page 24 written during step 3" [F9 +WalCrashTests seed header]. A shrunk sequence is the repro that goes into the draft and the issue [I260 +"Repro"]. + +**Confirmation before filing.** Every family was reproduced outside the oracle, as "throwaway repro Facts +for D1–D9 (not to be committed) so every draft's repro is actually run, not just reasoned" [T9 08:17], or +"standalone repros for the drafts, including what each defect does at the collection level" [T5 23:13], +and the mechanism was read in the code with `file:line` [I249 "Mechanism (verified on 0450b97)"]. + +**The triage order**, as BLite's skill states it, in the order the differential test makes natural [F13 +SKILL.md]: + +1. Is the oracle or the model wrong? Cases that stopped here: the store model expected + `InvalidOperationException` for a duplicate key where BLite throws `BLiteValidationException` and + leaves the transaction active [T2 report]; the AOT consumer's CDC count "was my arithmetic (50 inserts, + not 51)" [T11 08:22]; the dynamic model's rule that "an explicit null counts as a value for the unique + index" [T9 report]. +2. Do both engine paths agree with each other but differ from the reference? Then it is a semantics + question to settle against the README before anything is filed. The `!=` family was this: both paths + drop null-valued documents, LINQ keeps them; the issue "should ask rather than assert" [T1 22:09]. +3. Does only the indexed path diverge? "That is a bug with no further question: the index serves a + different answer than a scan of the same documents" [F1 "Triage"]. +4. Hand the family to the code-analyst for confirmation and siblings; file only when confirmed. + +**A correction that shows why the order matters.** The parent session first reported that all fourteen +rows diverged on the indexed path only; a rerun "with a complete filter" showed its own filter had +dropped the plain collection's lines, and two rows were in fact wrong on both paths [T1 22:09]. The +lesson it drew is TypeKit's first triage rule in different words: read every way's line before deciding +which step applies. + +### 3.8 How a finding became an issue + +The route, in the order it was run every time: + +1. **Family label, issue empty.** The rows go under one label in the test's registry with an empty issue, + so they keep failing, "so nothing is quarantined before it is filed" [F1 "Candidate registry"]. +2. **Draft.** `Seeds/candidate-issues.md` gets a section per family: rows, repro (three or four documents, + the plain collection as control), expected, actual, hypothesis with code references, and at the top a + table mapping family label to draft number [F9 candidate-issues.md head]. +3. **Cross-check.** Open issues first; after the WAL round, closed issues and PRs too, with `git log -S` + for the commit that introduced the behaviour: "Extend step 1 before filing: cross-check W1–W3 against + CLOSED issues and PRs too, and reference them in the bodies" [T8 cross-session message]. That check + rewrote W1's history (the fsync went missing in three steps between v1.0.0 and v4.0.0, not in the commit + the draft had named) and reframed half of W2 as a trade-off two merged PRs had already accepted [T8 + 07:51]. +4. **Group by root cause.** D6 and D7 became one issue "since both live in `BsonValueToIndexKey` / + `CreateIndexKeyFromObject`"; A3, A4, A6 and A7 became one issue, the pragma pattern; BW2, BW3, BW4 and + BW7 became one, the blocking wait [T10 08:45] [T12 report] [F11 candidates.md]. Families 1 and 4 of the + differential shared a cause and a decision, so one issue [T1 22:12]. +5. **File**, with the labels the repository has (`bug`, `package/core`, `area/indexing` or `area/linq`, + `severity/*` where it is clear; `encryption`, `package/generator`, `package/client` where they apply) + [T6 report] [T12 report]. +6. **Number into the registry**, commit, run the oracle filter and confirm every family's rows print + `SKIPPED` at the draft's row counts and nothing says `candidate fixed` [T10 08:50]. +7. **Comment on siblings** (#237, #234, #254 got one line each saying the test reproduces them and carries + them as known) and link the test PR from each issue [T6 report] [T10 report]. +8. **Fix PR removes the entry** and carries `Found-By: OT` and `Defect-Class:` [F12]. + +**The issue body**, as the filed issues have it [I249] [I260] [I273]: + +- First paragraph: what fails in one sentence, then "Found by (, ), which carries the + affected rows as a known candidate until this is fixed." +- The README or document sentence contradicted, quoted with its line. +- `## Repro`: the shrunk sequence or three documents and the call, with expected and actual as comments; + the `dotnet test` command and how to make the row fail again ("With the issue number removed from the + family in `s_candidates`, the row fails and prints the seed, the crash point and the shortest sequence"). +- `**Expected:**` / `**Actual:**`. +- `## Mechanism (verified on )`: the code path with `file:line` for every step. +- `## History` where a decision is behind the behaviour: commits, dates, versions, the design note that + intended something else. +- `**Affected versions:**` from `git describe --contains` of the introducing commit; "Not reproduced on + hardware; the test models a power loss as …" where that is true. +- `## Fix` or `## Fix considerations`: options, framed as the maintainer's choice where it is a trade-off + ("This is a durability/latency trade-off for the maintainers to decide"), with the per-commit cost + stated; "Happy to send the fix if you want it." +- `## Workaround` where one exists. +- `Related:` the sibling issues and PRs, and the other findings of the same test. + +### 3.9 How candidates that are not fixed yet are kept + +The registry lives in the test that owns the rows, not in one central file, and it is the only +quarantine. Its rules, verbatim from the documentation [F1 "Candidate registry"]: + +- "it still disagrees and its issue is **filed**: the row passes and writes `SKIPPED [known candidate: + , ] ` to the test output (xunit 2 has no dynamic skip, so this line is the + trace a run shows);" +- "it still disagrees and its issue is **empty**: the row keeps failing with `[known candidate: + ]`, so nothing is quarantined before it is filed;" +- "it **agrees**: the row fails with `candidate fixed: remove the entry for ''`, so the fix's PR + removes the entry and the registry cannot outlive the defect." + +A `Fact` per test fails on an entry that names no row, "for example after a row is renamed" [F1] [F2 +`AssertAllIn`]. For tools outside xunit the same table lives in `candidates.md`, with a `Kind` column so a +row that fails only sometimes is never reported as fixed [F11 Candidates.cs]. Already-known issues go in +with their number from the start, even in a session told to leave issues empty: "That departs from your +'empty Issue' instruction, because the registry's own rule is that a filed issue goes in the entry" [T5 +report]. The registry is the thing the maintainer sees: "a quarantine that is visible, classified, and +self-cleaning, which is the one piece of the replay machinery worth keeping" [T1 21:55]. + +### 3.10 How the corpus is kept alive without per-commit replay + +- **It is the test suite.** The oracle tests are ordinary tests in `tests/BLite.Tests`; CI runs them on + every push with no extra job. The cost of "replay everything" is the per-class minute of 3.5 [F1 "Run + the oracle tests"]. +- **Seeds stay applicable by a CI test without a build**, and are re-proven locally before a release by + the tool [F3] [F4] [F1 "Seeds"]. "A refactor that strands a seed fails CI instead of leaving the oracle + unproven" [F1]. +- **The registry cleans itself** through the `candidate fixed` rule, and the fix PR is the one that removes + the row [F1]. +- **Growth is by catalogue, not by test.** The documentation states the rule per oracle: "A new LINQ + operator or terminal is one more `QueryCase` in the `BuildCatalogue` method"; "A new index key type or + key shape is one more key generator"; "A new collection operation or storage feature is one more + `StoreOpKind`" and "once it is a `StoreOpKind`, one more step the injector of `WalCrashTests.cs` + interrupts"; "A new public API is one more call in `tools/aot-smoke/Consumer`" [F1 "How detection grows + with the feature set"]. +- **What is written and what is next** is a table in the epic, with the rule that makes it a gate by + convention: stable in the stability matrix only when the oracle is merged and its registry is empty + [I288]. The fork keeps the same list as `.claude/oracle-catalogue.md`, with the instruction "Add a row + to 'Written' in the same commit that adds the test and its seed" [F13]. +- **Stacked branches until merged.** Because every oracle depends on the machinery of the first PR, the + test PRs were stacked (#250 ← #259 ← #263 ← {#272, #277} ← #294) and the parent held them until the + maintainer's first review [T1 06:19] [T1 11:22]. A late commit on the bottom branch then had to be + merged up through every branch by hand [T16 report]; see 4.3. + +--- + +## 4. What it produced in BLite + +### 4.1 The issues + +28 issues, 27 of them straight from failing rows and one found while fixing another; plus one draft not +filed. Labels as filed; every one also carries the repository's default `triage` label [G1]. + +| Issue | One line | Found by | Labels beyond `bug`, `package/core` | +|---|---|---|---| +| #247 | `== null` and `OrderBy(nullable).Take(n)` served from a typed index return no null-keyed documents | LINQ differential, families 1 and 4 | area/indexing, area/linq | +| #248 | `x.Prop != constant` does not match documents whose property is null; the evaluator has no branch for a stored null | LINQ differential, family 2 | area/linq | +| #249 | Indexed double equality misses `-0.0`: the key encoding gives the two zeros different keys | LINQ differential, family 3 | area/indexing | +| #254 | DynamicCollection range filters and sorts on an indexed double are wrong: keys are little-endian `BitConverter` bytes | noticed in #249's body, filed while fixing it | area/indexing | +| #255 | `BTreeIndex.Equal/In/GreaterThan/LessThan/Between/StartsWith` miss duplicate keys and fail past the last key | B-tree model, B1 | area/indexing | +| #256 | Equal keys come back in reverse insertion order on a forward scan | B-tree model, B2 | area/indexing | +| #257 | A NaN with the sign bit clear sorts above `+Infinity` in an index on a double | B-tree model, B3 | area/indexing | +| #258 | Every reopen strands the unwritten rest of the last page growth block | B-tree model, B4 (page invariant) | area/indexing | +| #260 | WAL commits are never flushed to disk: a power loss drops acknowledged commits and can leave documents pointing at freed pages | WAL crash, W1 | severity/high | +| #261 | A crash leaks pages freed or allocated outside the WAL | WAL crash, W2 | area/indexing | +| #262 | A crash between the two page writes of `FreePage`/`AllocatePage` leaves a Free page off the free list | WAL crash, W3 | area/indexing | +| #264 | BLQL filters and sorts cannot reach a nested field, though an index can be built on one | Dynamic differential, D1 | area/linq | +| #265 | BLQL orders and compares ObjectIds by their little-endian bytes | Dynamic differential, D2 | area/linq | +| #266 | BLQL `First`/`FirstOrDefault` ignore Sort and Skip | Dynamic differential, D3 | area/linq | +| #267 | BLQL range operators match fields of another BSON type: null under `$lt`, strings under `$gt` | Dynamic differential, D4 | area/linq | +| #268 | BLQL compares two Int64 values through double | Dynamic differential, D5 | area/linq | +| #269 | A dynamic index misses keys for a numeric bound of another BSON type and keys no Decimal128 or Boolean value | Dynamic differential, D6 + D7 | area/indexing | +| #270 | `QueryIndexAsync` rejects a DateTime bound | Dynamic differential, D8 | area/indexing | +| #271 | A failed unique `CreateIndexAsync` leaves the half-filled index registered | Dynamic store model, D9 | area/indexing | +| #273 | Typed collections cannot write under Native AOT: the generated mapper registers its BSON keys by reflection that trimming removes | AOT twin, A1 | severity/critical, package/generator | +| #274 | Encrypted database: a page allocated by a transaction that never commits cannot be decrypted afterwards | AOT twin, A2 (JIT too) | severity/high, encryption | +| #275 | `#pragma warning disable IL…` hides reflection from BLite's build but not from the consumer's publish | AOT twin, A3 + A4 + A6 + A7 | | +| #276 | BLite.Caching uses reflection-based System.Text.Json: throws in any trimmed or AOT app | AOT twin, A5 | package/client | +| #289 | BLite.Wasm: a committed write is lost on a page reload unless a checkpoint ran | Browser, BW1 | severity/critical | +| #290 | BLite.Wasm: synchronous waits block or kill the browser runtime | Browser, BW2 + BW3 + BW4 + BW7 | severity/high | +| #291 | BLite.Wasm: two tabs on one database lose a committed write (IndexedDB) or cannot open it (OPFS) | Browser, BW5 | area/multi-process | +| #292 | BLite.Wasm: a 50 MB database cannot be reopened; `CheckpointAsync` returns at once while a background checkpoint runs | Browser, BW6 | severity/medium | +| #293 | BLite.Wasm: the package lacks its JS files and offers no typed-context factory | Browser, BW8 + BW9 | | +| BW10 | WebKit without persistent storage has no OPFS and `Auto` picks it anyway; drafted, not filed | Browser on WebKit | | + +[G1] [T1 22:37, 06:08, 08:07, 08:55, 09:05, 11:22] [T15 report]. Two things the tally should not hide: the +store model found nothing on upstream HEAD in its first run, "which is also information: the +collection-level contract holds in one process without crashes" [T1 06:18]; and three of the issues are +design questions as much as bugs, filed as questions (#247, the `!=` half of #248's framing, #260's sync +mode) [T1 06:18] [T1 22:12]. + +Alongside the issues: 7 test PRs (#250, #259, #263, #272, #277, #294, #295), 12 fix PRs (#251–#253, +#278–#286), Discussion #287 and epic #288 with the coverage table [G2] [T1 11:22]. + +### 4.2 The effort + +Wall clock from the first question to the last PR of the stack: 2026-10-16 21:09 to 2026-10-17 11:22, +about 14 hours including a night with no activity between 23:48 and 06:00 [T1 timestamps]. The sessions, +each a brief from the parent and a worktree: + +| Session | Start | End | Result | +|---|---|---|---| +| Decision and orchestration (parent) | 16th 21:09 | 17th 12:00 | briefs, filing of the first three issues and PR #250, relays | +| First two tests [T2] | 21:14 | 21:44 | differential (14 rows in four families) and store model, two seeds | +| Triage of the four families | 21:54 | 22:03 | standalone repros, drafts | +| Machinery [T3] | 21:58 | 22:08 | Oracles folder, seeds test, registry, CONTRIBUTING, seeds tool, fork-only skill and catalogue | +| Assemble and document [T4] | 22:15 | 22:26 | skip-while-open rule, CRLF, `docs/ORACLE_TESTS.md` | +| Fix PRs #251–#253 | 22:40 | 23:16 | three PRs, #254 filed | +| B-tree model [T5] | 22:53 | 23:47 | 73 rows in four new families, two known reproduced | +| B-tree filing [T6] | 06:00 | 06:08 | #255–#258, PR #259 | +| WAL crash [T7] | 06:22 | 07:16 | three families, 1,886 crash states | +| WAL filing [T8] | 07:37 | 07:51 | #260–#262, PR #263, history cross-check | +| DynamicCollection [T9] | 07:47 | 08:32 | nine drafts, 252 candidate rows | +| Native AOT [T11] | 08:12 | 08:51 | seven candidates, two seeds | +| Dynamic filing [T10] | 08:43 | 08:54 | #264–#271, PR #272 | +| AOT filing [T12] | 08:58 | 09:05 | #273–#276, PR #277 | +| Fix round 2 | 09:08 | 09:59 | PRs #278–#286 | +| Browser harness [T13], in parallel | about 08:57 | 10:23 | 49 scenarios, 20 failing, nine findings | +| Restack [T16] | 10:58 | 11:19 | five merge commits, PR #294 | +| Browser into the frame [T14] | about 11:00 | 11:22 | registry, seed, #289–#293, PR #295 | +| Firefox and WebKit [T15] | 11:22 | 12:17 | BW10 drafted, persistent-context mode | + +Code written on the top of the stack, excluding the dynamic, AOT and browser branches: `BTreeModelTests` +1,419 lines, `WalCrashTests` 1,325, `DifferentialQueryTests` 839, `StoreModel.cs` 428, `StoreModelTests` +193, `candidate-issues.md` 744, `docs/ORACLE_TESTS.md` 243, the four seed patches 231, the three framework +files 188 [F-wc]. The dynamic tests add 1,684 [F14 wc]. The browser harness is two projects [T13 report]. + +### 4.3 What went wrong, what it missed, and the lessons the transcripts record + +**Mistakes made and caught.** + +- The parent misreported the `!=` family as indexed-only because its own output filter dropped the plain + collection's lines; corrected after a rerun with a complete filter [T1 22:09]. Lesson: read every way's + line before classifying. +- A seeded B-tree run hung for over ten minutes inside an engine traversal of a corrupted tree. The test + gained its own cycle-safe walk before any engine traversal and a 60 s watchdog, "so a broken tree is + reported as a `Structure` failure instead of a hang" [T5 report]. +- Two tests backed up into the same temp folder because the backup manifest has a fixed name; the fix + landed as a late commit on the bottom branch and broke the mergeability of the four stacked PRs above + it, which then needed a merge commit each, one with a conflict [T9 08:25] [T1 10:56] [T16 report]. + Lesson: a stack is cheap to build and expensive to move; land the base before stacking, or expect one + restack. +- A `sed` whose delimiter clashed with `#277` emptied four issue bodies for a minute; restored, visible in + the edit history [T12 report]. +- The bottom branch had been rewritten upstream with new commit ids and the same tree; the B-tree branch + was replayed onto it and force-pushed with lease before its PR existed [T6 report]. +- zsh did not word-split a label variable, so an issue was created without labels on the first try [T6 + 06:01]; zsh also mangled a browser configuration string [T15 11:36]. +- The registry's "skip" is a logged pass in xunit 2.9.3, "so the quarantine is invisible in CI counts" + [T1 22:27] [T1 06:18]. +- CRLF: every file needed converting and staging with `core.autocrlf=false`; the seed patches had to stay + LF because a CRLF copy no longer applied [T4 report]. +- Pre-existing concurrency tests flake under the load of the full suite; every full run was followed by + reruns in isolation [T16 11:12] [T5 brief]. +- The AOT publish needed `LIBRARY_PATH` for Homebrew's OpenSSL and the `xcrun` clang shim; both are handled + in `run.sh` [T11 08:26, 08:37]. + +**What the method cannot see**, as its own documentation states: targets other than `net10.0`, real +multi-process races, crashes in multi-process mode and inside `Backup`, torn page writes, performance, +"and anything whose correct answer has no exact reference, such as API design and documentation drift. +Those stay with review" [F1 last paragraph]. Two concrete misses: #212 (keys not prefix-free) "does not +show at this level: a raw `BTreeIndex` key has no document id appended", so the oracle sat below the +defect [T5 report]; and the first-run claim that the store contract held was limited to one process with +no crashes, which the WAL test then contradicted under power loss [T1 06:18] [T7 report]. + +**Lessons the transcripts record**, in their words where possible: + +1. "Exact oracles. LINQ-to-Objects, a sorted dictionary, the committed view of a model, the JIT build of + the same program. None of them is an approximation, so no finding needed an argument about what the + right answer was" [T1 08:48]. "That is what the principle 'model budget goes where the oracle is + weakest' predicts: an exact oracle plus deep state is where reading finds least and programs find + most" [T1 21:11]. +2. "Claims as the test list" [T1 08:48] (3.3 above). +3. "Seeds. Each test was shown to fail before it was trusted, so none of the twenty-six is a test that + happens to pass or fail for reasons unrelated to the product" [T1 08:48]. The seeds tool had to count + only unlabelled failures, because a test with registered candidates "would always pass" the naive + check [T3 report step 5]. +4. "Findings make the case for the machinery" and "agents and workflows never go into someone else's + repository on the strength of an invitation" [T1 21:11] [T1 22:12 "What not to send yet"]. +5. Hold the test PRs until the maintainer has seen the pattern once, "so the maintainer sees the pattern + once before it multiplies" [T1 22:50]; but file a durability finding at once, because "the issue stands + on its own without the test PR" [T1 07:18]. +6. Cross-check closed issues and PRs and the commit that introduced the behaviour; "an issue that cites + that history and frames the fix as a trade-off lands better with the maintainer than one that reads as + a discovery" [T1 07:40]. +7. One issue per root cause, and ask rather than assert where the README is silent [T1 22:12] [T10 + 08:45]. +8. The registry must be self-cleaning and visible; a deleted row loses the guard [T1 22:12 step 2]. +9. "The method finds what has an exact reference. API misuse that compiles, documentation drift and + unclear error messages are invisible to it and stay with review. The TypeKit fix history showed exactly + that split, and it holds here too" [T1 08:48]. +10. A side effect worth naming: several of the later issues were found by review or by running the + README samples for the v6 documentation work, not by oracles (#299 "while compiling and running the + README samples for #298"; #301 "while checking the secure-erase behaviour for #299"; #304, #305 and + #307 "while reviewing … for #296"), and two of them (#304, #305) propose the oracle row that would + hold them [G1 bodies]. The catalogue tests created a habit of asking "which oracle would hold this". + +--- + +## 5. Adoption guide for any repository + +### 5.1 Prerequisites + +- A test project the repository already runs in CI, in whatever framework it uses. Nothing here needs a + particular one; the templates below are given for xunit (BLite) and MSTest (ValueRanges). +- At least one **exact reference** for a central promise: a simpler implementation of the same + computation (LINQ-to-Objects, a dictionary, set theory over a small universe, PostgreSQL itself), an + invariant (pages reachable plus free equals allocated), a twin build (JIT against AOT), or an identity + (round trip). If the only reference you can think of is "what the code did last week", stop: that is a + snapshot test, and this method will produce noise [K6 §12 "Which oracle a domain gets"]. +- Documents that make promises: a README, a docs folder, a limitations file. The promise is the claim. +- Issues with labels, so a finding has somewhere to go that is not a list in a file. +- `git apply` and a scratch worktree, for seeds. + +### 5.2 The artefacts, with templates + +Create them in this order. Each one is small; none depends on a tool. + +**1. The first catalogue test**, with its seed, before anything else. Pick the section with the densest +recent bug history and an exact reference ("the last 35 upstream commits are almost all index/LINQ/WAL +correctness fixes found one at a time" [T2 brief]). Write it in the consumer's position. + +xunit skeleton, the shape of `DifferentialQueryTests` [F5]: + +```csharp +public class DifferentialTests : IClassFixture<DifferentialTests.Fixture> +{ + // Fixture: seeded data (override with an environment variable), one instance per "way". + public sealed class Fixture : IDisposable { /* seed, data, the ways */ } + + // Known candidates: family label (the hypothesis), issue (empty until filed), the row names. + private static readonly Candidates s_candidates = new() + { + { "", "", "", "" }, + }; + + // The catalogue: one named case per shape. The name carries the operand. + private static readonly Dictionary s_cases = BuildCatalogue(); + public static TheoryData Cases => new(s_cases.Keys); + + [Fact] public void Candidates_NameExistingRows() => s_candidates.AssertAllIn(s_cases.Keys); + [Fact] public void Fixture_ReachesWhatTheAxesPromise() { /* guards: indexes exist, splits happened, … */ } + + [Theory, MemberData(nameof(Cases))] + public async Task Case_AgreesWithTheReference(string name) + { + var c = s_cases[name]; + var errors = new List(); + foreach (var (way, run) in Ways()) // the product of the hand-listed axes + { + var outcome = await Guard(() => run(c)); // value or exception, both compared + var error = c.Compare(Reference(c), outcome); // the case's own rule: set, sequence, scalar + if (error != null) errors.Add($"{way}: {error}"); + } + s_candidates.Verify(name, errors.Count == 0 ? null + : $"seed {Seed}: '{name}' disagrees with the reference:\n " + string.Join("\n ", errors), _output); + } +} +``` + +MSTest skeleton, for a repository that already has a `Candidates.Verdict` over a list of failure lines +(as ValueRanges does [V-cand]): + +```csharp +[TestClass] +public class ProbeTests +{ + public static IEnumerable Rows() + { + foreach (var type in DiscoveredTypes()) + foreach (var declaredAs in new[] { "union", "variant", "interface", "object" }) + foreach (var options in OptionsCatalogue()) + yield return [type, declaredAs, options.Name]; + } + + [DataTestMethod, DynamicData(nameof(Rows), DynamicDataSourceType.Method)] + public void Row_AgreesWithTheReference(Type type, string declaredAs, string optionsName) { /* one row */ } + + // Or one sweep method that collects every disagreement and ends in the verdict, + // which is how the existing small-model sweeps are written: + [TestMethod] + public void Sweep() + { + var failures = new List(); + foreach (var row in Rows()) { var line = Check(row); if (line != null) failures.Add(line); } + Candidates.Verdict(oracle: nameof(ProbeTests), failures, header: "", method: nameof(Sweep)); + } +} +``` + +The row's failure line must start with a stable prefix (the operands and the operation, never the actual +value), because the registry matches by that prefix [V-cand remarks]. + +**2. The seed**, in the same commit. Revert a known fix restricted to `src/`, or plant a defect by hand; +run the test; confirm it fails *for the defect*; save the diff with a header [F1 "Add a new oracle test"] +[F9 seed header]: + +```text +Seeded defect for tests//Oracles/Tests.cs + + (#)>: . + +With it applied, rows that pass on fail, e.g. , ( of ). shrinks to: + 1. + 2. + + +Reproduce: git apply && dotnet test --filter +Restore: git checkout -- src + +Never commit the seeded state. + +diff --git a/src/... b/src/... +``` + +**3. The registry helper** (52 lines in BLite [F2]); if the repository already has one, reuse it. The +three outcomes are the contract; the implementation is incidental. For a framework with a real dynamic +skip, use it; otherwise log the skip and say so in CONTRIBUTING. + +**4. The seeds test** (58 lines [F3]): every `*Tests.cs` under the oracle folder has a seed; every seed +touches only `src/` (`git apply --numstat`); every seed still applies (`git apply --check`). No build. + +**5. The seeds prover** (78 lines [F4]): for each seed, a detached scratch worktree of HEAD, apply, build, +run the class, read the results file, require at least one failure whose message does not contain the +known-candidate label; print `CAUGHT`, `MISSED` or `ERROR`; exit 1 unless all caught; remove the +worktree. Local only. + +**6. The drafts file**, `Seeds/candidate-issues.md`: a status line at the top, a table from family label +to draft number and row count, then one section per draft [F9]: + +````markdown +## . + +**Rows:** `<row>`, `<row>`, … + +**Repro:** +```<language> +<three or four inputs; the control path; expected and actual as comments> +``` + +**Expected:** <what the reference and the control path say>. +**Actual:** <what the path under test says>. + +**Hypothesis** (checked against the code): <mechanism with file:line>. +**Affected versions:** <from git describe --contains of the introducing commit>. +**Existing issues checked:** <numbers, and why none covers it>. +```` + +**7. The issue body template**, the structure of 3.8 reduced to headings: + +```markdown +<One sentence: what fails.> Found by <test> (`<path>`, <PR>), which carries the affected rows as a known +candidate until this is fixed. + +<Document> "<section>" (`<file>:<line>`): "<the promise, quoted>." + +## Repro +<shrunk sequence or inputs and call; expected and actual as comments; the test command; how to make the +row fail again> + +**Expected:** … +**Actual:** … + +## Mechanism (verified on <sha>) +- <step, file:line> + +## History <!-- only where a decision is behind the behaviour --> +- <commit, date, version: what it changed and why, quoting the design note> + +**Affected versions:** <range>. <What was not reproduced on real hardware, if anything.> + +## Fix considerations +<options; the trade-off named as the maintainer's call; cost stated; "happy to send the fix"> + +## Workaround <!-- where one exists --> + +Related: <siblings, closed fixes it neighbours, the other findings of the same test> +``` + +**8. The registry row**, as a C# entry or a table row: + +```csharp +{ "<family label stating the hypothesis>", "#<issue or empty>", "<row>", "<row>" }, +``` + +```markdown +| <Id> | always or sometimes | `<test method or row pattern>` | `<substring of the failure>` | #<issue or empty> | <summary> | +``` + +**9. CONTRIBUTING section and trailers** (about 22 lines in BLite [T3 report step 4]): what an oracle test +is, that every one ships a seed, how the registry behaves, and the two trailers with the repository's own +defect classes. + +**10. The method document**, `docs/ORACLE_TESTS.md` or its equivalent, in four parts: why it exists (the +bug history it answers), what it is (each mechanism in one paragraph), how to use it (run, read a +failure, triage order, register, retire, add a test), how detection grows with the feature set (one +sentence per catalogue), and what it does not see [F1 headings] [T1 22:21]. + +**11. The catalogue of oracles**, written and not yet written, each with what it compares, what it would +catch, and a seed idea (27 lines in BLite's fork; a table in the epic upstream) [F13] [I288]. This is the +axes file reduced to what a human needs: where the next oracle goes. + +**12. The triage skill or checklist**, four questions in the order the oracle makes natural, each with +"applies when" and "then", and a table of what each step leaves behind in the registry, the issue and the +commit [F13 SKILL.md]. In a repository without agents, this is a section of the method document. + +### 5.3 Fitting it to an existing suite and an existing claim catalogue + +- **If the repository already has claims** (a `claims.json` that quotes document sentences and names the + test holding each, as ValueRanges has [V-claims]), do not write a second list. A catalogue test names + the claim ids it holds in their `heldBy`; the axes it varies go in a short hand-written file beside the + claims (`test/oracles/axes.md`), one section per surface family, each axis with its values, the test + that already varies it, and the pairs no test varies [V105 comment]. The unheld claims and the uncovered + pairs are the worklist that TypeKit's `status` and `gaps` would print. +- **If it already has a registry and a seed convention** (`candidates.json`, `seeds/*.patch`, a prover), + register the new class where the others are registered and give it a seed; do not add a second + mechanism [V-oracles]. +- **If it has sweeps that already vary one axis each** (every culture, every type, every shape pair), the + catalogue test's job is the *combinations* those sweeps do not reach: declared type × serializer + option, entry point × culture × type, one literal across every parser against the external system's + own answer [V105 comment]. List the existing sweeps as the first row of each axis so the new test does + not repeat them. +- **Put each class in the project that already has its harness**: the conventions project for type + discovery, the web test project for `TestServer`, the integration project for a live server [V105 + comment]; BLite put the AOT and browser tools beside the tests because a publish takes minutes and needs + a toolchain [F1 "Native AOT smoke test"]. + +### 5.4 Keeping it cheap + +- A class runs in about a minute; above that, sample by a rule written in the code (every third crash + state), or split by mode so one slow axis does not multiply the rest [F8 line 252]. +- Shrink under a budget and with a watchdog; print the shortest sequence, the seed and the step, so the + repro is the failure message [F6 lines 164–192] [T5 report]. +- Shared generators and models: one `StoreModel.cs` fed four oracles and a tool [T7 report] [T9 08:07] + [T11 08:17]. +- One session, one oracle, one brief; drafts before the session ends; a separate short session files [4.2]. +- No tooling until the hand-written list hurts; then "a pairwise generator is a fifty-line addition to the + test" [T1 21:55]. Replay by footprint is worth building only when the suite's minute becomes an hour. +- Do not send agents, hooks or skills into a repository you do not own; send tests, a document and a + CONTRIBUTING section [T1 21:55] [T1 22:12]. + +### 5.5 Failure modes to avoid + +1. **An inexact reference.** "A harness that compares unlike things produces the most convincing bugs + there are" [K5] [F13]. Step 1 of triage exists for this; three of BLite's first disagreements were the + model's [3.7]. +2. **Reading one way's output and deciding.** The parent's filter error [T1 22:09]. +3. **A seed that proves the wrong thing.** A test with registered candidates always "fails", so the prover + must ignore labelled failures [T3 report]; a seed that breaks some assertion shows the test can fail, + not that the claim is checked [K1 "Seeds", decision 104]; a seed whose build fails proves nothing [K1]. +4. **A registry that cannot go stale, going stale.** Without the `candidate fixed` rule the quarantine + outlives the defect; without the `AssertAllIn` check a renamed row leaves a ghost entry [F2]. +5. **Quarantine by deletion.** "Quarantining by deleting rows loses the guard" [T1 22:12]. +6. **Filing before confirming, or before checking closed issues.** Both were caught in BLite by an explicit + step [T8 cross-session message] [T9 08:17]; TypeKit's record of a false prober finding is why [K7]. +7. **Assertions in the issue where the maintainer owns the decision.** Frame semantics and trade-offs as + questions with options and costs [I260 "Fix considerations"]. +8. **A catalogue that grows as new tests instead of new rows.** The growth rules in the method document + are what keep one new case checking against everything old [F1 "How detection grows"]. +9. **Oracles at the wrong level.** A defect in the layer above the oracle's seam does not show (#212 at the + raw B-tree) [T5 report]; a tool that steps aside (the WAL recorder during `Backup`) leaves a documented + hole [T7 report]. Say what each oracle does not see. +10. **Mechanical traps that cost hours:** line endings, shell word splitting, a `sed` delimiter, a stack + that moves, flaky neighbours under load [4.3]. Put each in the brief of the next session. + +--- + +## 6. Appendix: sources + +### 6.1 Source key + +Transcripts are JSON Lines files under `~/.claude/projects/`; a position is the assistant message's +timestamp on 2026-10-16 or 2026-10-17 as printed in the file. The `local_…` id is the desktop app's +session id, for `search_session_transcripts`. + +| Tag | What | Where | +|---|---|---| +| T1 | The adoption and orchestration session ("TypeKit bug-detection adoption", `local_5f13179c-c4e6-4a17-a5a5-76adc4a039ee`) | `-Users-ricardo-gross-RiderProjects-BLite/2fff4cfa-ef17-42b6-9be3-0c572e0b2aa1.jsonl` | +| T2 | First two oracle tests (`local_0ff03db5-0549-47d3-8b13-d3468efee6ba`) | `-Users-ricardo-gross-RiderProjects-BLite--claude-worktrees-awesome-nobel-c18118/da25b03b-e4b5-4e8a-a3ef-cd38f4ac2917.jsonl` | +| T3 | Minimal machinery (`local_7e91d830-d86b-45b6-b7e1-410856125ace`) | `…--claude-worktrees-amazing-shirley-d6a17b/29e567f2-7168-4b21-9853-2daf71f9c86a.jsonl` | +| T4 | Assemble the upstream branch and write the doc (`local_f348875c-7895-417b-b6a7-f41aebb94b03`) | `…--claude-worktrees-bold-jemison-ab4112/d11cfd8c-ecac-4e34-b91d-72826be4677b.jsonl` | +| T5 | B-tree model test (`local_59590bd2-73d2-4b18-aadc-c29124f62c46`) | `…--claude-worktrees-gallant-dhawan-099ff2/d7e134a2-0062-4103-bb54-b0fc3aefb69d.jsonl` | +| T6 | B-tree issues and PR (`local_0bcf26c6-c767-4fea-ad0d-c9dc8f7a3d96`) | `-Users-ricardo-gross-RiderProjects-BLite/aaa634d7-5790-4cab-8f8c-6d0e8df33e80.jsonl` | +| T7 | WAL crash test (`local_8c7a6120-02cd-4ae6-8d90-6c70111de988`) | `…--claude-worktrees-nice-kepler-e43635/237a51b1-e924-4cae-be95-87c7d8571796.jsonl` | +| T8 | WAL issues and PR (`local_299441f5-a412-408c-b780-e74a8d2da9d2`) | `-Users-ricardo-gross-RiderProjects-BLite/d93cd37d-94a9-4a59-a781-9b8b97ba6700.jsonl` | +| T9 | DynamicCollection tests (`local_1662d46c-7300-4b1b-957d-92b109d821ba`) | `-Users-ricardo-gross-RiderProjects-BLite/f8230ab4-f7f2-4dff-983a-638cddf35031.jsonl` | +| T10 | DynamicCollection issues and PR (`local_cd76b880-e49d-4abe-9c07-6f01b45b3b77`) | `-Users-ricardo-gross-RiderProjects-BLite/e548a07a-2716-4d2f-96ca-a86923239b18.jsonl` | +| T11 | Native AOT smoke test (`local_86a39a6c-aa47-4858-85a8-7fd9077afc1d`) | `…--claude-worktrees-compassionate-swirles-586c00/7c9352d3-753f-430b-91c1-0f9e3551203e.jsonl` | +| T12 | AOT issues and PR (`local_01a36223-675a-40ae-bacc-518cd341b617`) | `-Users-ricardo-gross-RiderProjects-BLite/e4803599-52d8-4f4c-9944-c64b4537f29e.jsonl` | +| T13 | Browser harness built (`local_43b916c1-3f2a-4db3-abd1-9c0ac45ca64a`) | `-Users-ricardo-gross-RiderProjects-BLite/1dbc59f3-97f4-4d6b-b2a9-ff8956ed7b19.jsonl` | +| T14 | Browser harness into the oracle frame (`local_9f981451-221d-4943-9140-ec0280ac0083`) | `-Users-ricardo-gross-RiderProjects-BLite/be98704b-fd11-4efa-8eed-54621ef1d1d0.jsonl` | +| T15 | Firefox and WebKit runs (`local_a2d52883-225a-4309-b283-95dc2dad45b8`) | `…--claude-worktrees-festive-villani-1d94c1/d956d69f-3ff8-4422-a5d0-7a557bd9c862.jsonl` | +| T16 | Restack of the PR stack (`local_0edf32a2-08d7-488b-bad6-eb13fd916412`) | `-Users-ricardo-gross-RiderProjects-BLite/c02d43e2-c1b2-4108-8c2a-9cae41be9363.jsonl` | +| F1 | `docs/ORACLE_TESTS.md`, 243 lines | EntglDb/BLite branch `test/oracle-encrypted` at `0de4379` (local worktree `BLite/.claude/worktrees/eloquent-mcclintock-d7bc82`) | +| F2 | `tests/BLite.Tests/Oracles/Candidates.cs`, 52 lines | same | +| F3 | `tests/BLite.Tests/Oracles/OracleSeedsTests.cs`, 58 lines | same | +| F4 | `tools/oracle-seeds.cs`, 78 lines | same | +| F5 | `tests/BLite.Tests/Oracles/DifferentialQueryTests.cs`, 839 lines | same | +| F6 | `tests/BLite.Tests/Oracles/StoreModelTests.cs` (193) and `StoreModel.cs` (428) | same | +| F7 | `tests/BLite.Tests/Oracles/BTreeModelTests.cs`, 1,419 lines | same | +| F8 | `tests/BLite.Tests/Oracles/WalCrashTests.cs`, 1,325 lines | same | +| F9 | `tests/BLite.Tests/Oracles/Seeds/*.patch` (four) and `candidate-issues.md` (744 lines) | same | +| F10 | `tools/aot-smoke/candidates.md`, `run.sh`, `README.md` | same branch (merged up from `test/oracle-aot`) | +| F11 | `tests/BLite.Wasm.BrowserTests/Candidates.cs`, `candidates.md`, `FINDINGS.md` | branch `test/wasm-browser-harness` at `f95a63e` (worktree `wasm-browser-harness`) | +| F12 | `CONTRIBUTING.md`, "Oracle tests" and "Commit trailers" | branch `test/oracle-encrypted` | +| F13 | `.claude/oracle-catalogue.md` and `.claude/skills/oracle-triage/SKILL.md` | fork-only commit `23f7182` on branch `claude/amazing-shirley-d6a17b` (worktree `amazing-shirley-d6a17b`) | +| F14 | `tests/BLite.Tests/Oracles/DynamicDifferentialTests.cs` (887), `DynamicStoreModelTests.cs` (797) | branch `test/oracle-dynamic` (worktree `oracle-dynamic`) | +| F-wc | Line counts | `wc -l` in the `eloquent-mcclintock-d7bc82` worktree on 2026-10-18 | +| K1 | TypeKit `.claude/probe-axes.md`, 751 lines | `/Users/ricardo.gross/RiderProjects/CodoMetis.TypeKit` | +| K2 | TypeKit `tools/Prober/README.md` | same | +| K3 | The `prober` agent definition | `~/.claude/agents/prober.md` | +| K4 | The `probe` skill | `~/.claude/skills/probe/SKILL.md` | +| K5 | TypeKit `.claude/skills/probe-triage/SKILL.md`, 261 lines | TypeKit checkout | +| K6 | TypeKit concept note, section 12 and Appendix A | `.claude/agent-memory-local/concept-2026-10-16-agentic-issue-finding.md` | +| K7 | TypeKit `design/fix-history.md`, "Class by found-by" | TypeKit checkout | +| K8 | Sizes of `tools/Prober/*.cs`, `tools/probe/enumerate.cs`, `tools/probe/corpus/harness/Row.cs` | `wc -l` in the TypeKit checkout on 2026-10-18 | +| I<n> | Issue n on EntglDb/BLite, read with `gh issue view` | GitHub | +| G1 | `gh issue list --repo EntglDb/BLite --state all` and the bodies of the user's issues since 2026-10-16 | run 2026-10-18 | +| G2 | `gh pr list --repo EntglDb/BLite --state all --author CaffeinatedCoder` | run 2026-10-18 | +| V105 | CodoMetis.ValueRanges issue #105, body and the 2026-10-18 comment | GitHub | +| V-claims, V-oracles, V-cand | `test/oracles/claims.json`, `oracles.json`, `Candidates.cs` in ValueRanges | this checkout | + +### 6.2 Statement index + +Section 1: every definition is from K1 ("Harness", "Seeds", "Claim sources", "Axes"), K2 ("What runs +where", "Files"), K3 (preamble, §3, §4, §6, §8) and K5 (preamble, headings), as tagged inline. The pairwise +size ("about three hundred rows") is K3 §6.3. The 56-line enumerator and 639-line `Row.cs` are K8. + +Section 2: the 6,617 figure is K6 Appendix A; the 188 figure is F2 + F3 + F4; every table row carries its +tags. The three-outcome rule is F2; the stability-matrix rule is I288; the BLQL `INAPPLICABLE` list is T9 +report and F14 lines 43–44. + +Section 3.1: T1 at 21:09, 21:11, 21:55; K6 §12; K7 table. +Section 3.2: T2 brief and report; T5 brief and report; T7 report; T9 brief and report; T11 report; T13 and +T15 reports; F5 `Generate` and `Fixture`; F14. +Section 3.3: T7 brief; F6 remarks; I260; I288; T1 08:48. +Section 3.4: F1 "How to use it"; T3 report; F5 lines 561–586; F6 lines 40–50; T7, T9, T11 as tagged; F10; +F11; T4 report; T5 brief. +Section 3.5: T2 report; T1 21:45; T5 report; T7 report; T16 11:02 and 11:07; T11 report; T15 brief; F8 +line 252; T13 report; T1 21:55. +Section 3.6: T2, T5, T7 briefs; F6 line 88; F8 line 100; T5 report. +Section 3.7: F2; F6 lines 164–192; F1 "Read a failure" and "Triage"; F9 WAL seed header; I260; T9 08:17; +T5 23:13; I249; F13; T2 report; T11 08:22; T9 report; T1 22:09. +Section 3.8: F1 "Candidate registry"; F9; T8 cross-session message and 07:51; T10 08:45; T12 report; F11; +T1 22:12; T6 report; T10 08:50 and report; F12; I249, I260, I273. +Section 3.9: F1 "Candidate registry"; F2; F11; T5 report; T1 21:55. +Section 3.10: F1 as tagged; F3; F4; I288; F13; T1 06:19 and 11:22; T16 report. +Section 4.1: G1 (titles and labels); T1 at 22:37, 06:08, 08:07, 08:55, 09:05, 11:22; T15 report; T1 06:18 +and 22:12; G2. +Section 4.2: the timestamps of the first and last user or assistant message of each transcript as listed +in the key; F-wc; F14; T13 report. +Section 4.3: T1 22:09; T5 report; T9 08:25; T1 10:56; T16 report; T12 report; T6 report and 06:01; T15 +11:36; T1 22:27 and 06:18; T4 report; T16 11:12; T11 08:26 and 08:37; F1 last paragraph; T7 report; T1 +08:48, 21:11, 22:12, 22:50, 07:18, 07:40; T10 08:45; T3 report; G1 bodies of #299, #301, #304, #305, #307. +Section 5: templates are reduced from F5, F2, F3, F4, F9, I249/I260/I273, F11, F12, F1, F13; the +ValueRanges fit is from V105's comment and V-claims, V-oracles, V-cand; the failure modes cite the +incidents of 4.3 and K1, K5, K7. diff --git a/design/roadmap.md b/design/roadmap.md index 959602ea..716f8ba0 100644 --- a/design/roadmap.md +++ b/design/roadmap.md @@ -5,9 +5,22 @@ item names the decision that scoped it, or the review that found it. A Warning m refusal may relax in any minor version; an Error that reports more waits for a major ([decision 34](adr/0034-errors-fixed-within-major-version-warnings.md)). -## 2.2.0, additive +## 2.2.0, consolidation -Decided, and moved here when 2.1.0 shipped without them ([decision 88](adr/0088-2-1-0-ships-without-its-open-roadmap-items.md)). +2.2.0 adds no public API. The probe gate binds from this release +([decision 97](adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md)). Any gap below that is a +defect, a cost or a missing guard may land here; one that adds surface waits for 2.3.0. + +- The probing harness (WS2 of the agentic issue-finding concept) is done + ([decision 108](adr/0108-a-probe-check-earns-credit-against-a-baseline-and-ws2-closes.md)). + WS3 (beyond pairs), WS4 (the mutation nightly), the second half of WS5 (the finder benchmark at equal budgets) and + WS6 (PostgreSQL in CI) were dropped on 2026-10-17 by the maintainer's decision, and are not scheduled. +- The generated-surface snapshot under "Generators" below. + +## 2.3.0, additive + +Decided, moved to 2.2 when 2.1.0 shipped without them ([decision 88](adr/0088-2-1-0-ships-without-its-open-roadmap-items.md)), +and then to 2.3 when 2.2.0 became a consolidation release ([decision 97](adr/0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md)). - An optional `Cause` on `Fault<TFault>`, an exception kept beside the reason and the detail for the log and the fault feature, never written to the problem; `Fault.Of(reason, detail, cause)`, and a @@ -17,10 +30,12 @@ Decided, and moved here when 2.1.0 shipped without them ([decision 88](adr/0088- ([decision 64](adr/0064-fault-name-reaches-client-causes-not-told-apart-one-fault.md)). - A default value-object key over `ulong` generated as a plain one is, which needs a per-key value generator a compiled model can write ([decision 63](adr/0063-char-keys-application-assigned-ulong-documented.md)). +- Testing: async assertions. +- `CatchAsync` over a `ValueTask`, and `TraverseAsync` on `Option` and `Patch`. ## Gaps, in any 2.x -Small and additive, each closable in a minor version. Found by the reviews named. +Small, each closable in a minor version. Found by the reviews named. **Analyzers** - CMTK0009, a Warning, reports `FirstOrNone`, `SingleOrNone` and `FirstOrError` on an `IQueryable<T>` @@ -42,11 +57,9 @@ Small and additive, each closable in a minor version. Found by the reviews named **Run time** - The Serilog policy ignores `ToMaximumStringLength`. -- Testing: an exception error prints its whole `ToString()` in a message, and there are no async - assertions. +- Testing: an exception error prints its whole `ToString()` in a message. - EF Core: a closed set's `.Name` in a query is not translated (it throws). - A closed set's JSON read allocates a string per token. -- `CatchAsync` over a `ValueTask`, and `TraverseAsync` on `Option` and `Patch`. ## 2.x, with a spike first @@ -73,6 +86,9 @@ Small and additive, each closable in a minor version. Found by the reviews named - CMTK0010 checks `[Abstract]`, `[Sealed]`, `[Record]` and `[Concrete]` at a method the compiler binds by pattern too, as it checks the kinds 2.1.0 added ([decision 89](adr/0089-cmtk0010-at-methods-bound-by-pattern.md)). +- CMTK0010 refuses `Enum`, `Delegate`, `MulticastDelegate`, `ValueType` and `Array` under `[Unsealed]`: abstract + classes nothing can derive from, which 2.x accepts since refusing them reports more under an Error + ([decision 34](adr/0034-errors-fixed-within-major-version-warnings.md); found by `TypeArgumentKinds`, 2026-10-15). - An endpoint whose fault types write one name for two members stops the host, where 2.x logs a warning ([decision 90](adr/0090-faults-alike-are-a-warning-until-3-0.md)). - CMTK1023, a base record's equality that the generated one does not call, becomes an Error diff --git a/design/test-strategy.md b/design/test-strategy.md index 9b40b891..7e1da4d4 100644 --- a/design/test-strategy.md +++ b/design/test-strategy.md @@ -47,3 +47,44 @@ pin behind. Seeded: a pin behind read as current, `Microsoft.AspNetCore.OpenApi` raised to 10.0.13 alone (KB-001 and KB-002 reported), a TypeKit project referenced from the facts, and KB-003's calls in the other order each fail their test. +9. **Variant catalogues** (2026-10-15, [decision 95](adr/0095-a-fix-is-tested-across-its-input-class.md)). + A fix is tested across the class of input its defect lives in, not only on the input a review named. + For a rule over type arguments the class is `TypeArgumentSites` + (`test/CodoMetis.TypeKit.Analyzers.Tests/Variants/`): 62 shapes through which an argument reaches a + parameter, written and inferred, method groups, null-conditional calls, extension blocks, generic + attributes, implementations reached through another declaration, methods bound by pattern, crefs and + `nameof`, and generated code, each run with an argument that breaks the rule's constraint and one that + meets it. CMTK0010 runs it under three constraints (`TypeArgumentConstraintVariantTests`). For a + generated type the class is the `shape.*` axes of `.claude/probe-axes.md`, for a framework option the + settings axes. Measured: at the origin of CMTK0010's chain of fourteen fixes the catalogue fails on 20 + sites, each fixed by a later member of the chain; seeded, without the rule's method-group and property + registrations, 15 cases fail. + What the argument is, the second axis, is `TypeArgumentKinds` beside it: 79 kinds (classes, records and + structs in each modifier, enums, delegates, arrays, `dynamic`, interfaces and the value-object marker, + the abstract bases, types built from a parameter, a parameter under each constraint, bound and + attribute) and 37 pairs. `TypeArgumentConstraintKindTests` runs every CMTK0010 attribute against each, + 864 cases, with an oracle per attribute written from `docs/type-constraints.md`'s tables. Measured + 2026-10-15 at each of chain A's commits: six of its seven fixes about what an argument is (`0d6ba1a`, + `2f8a854`, `7792801`, `d27cf2d`, `75fb3aa`, `6368c1c`) turn 35 cases green that fail at their parent; + `1a9d7f2` fixed the library's own declarations, which no analyzer test reads. It found one gap still + open, `[Unsealed]` taking the abstract bases nothing derives from, which waits for 3.0.0 (decision 34). + For the EF satellite, whose translation defects escaped most (7 of 10 in the fix history, all silent), + the catalogue is `KeyShapes`, `QueryShapes` and `WrappedKinds` in + `test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/`, each case a differential against the same + entity over the wrapped type. `KeyGenerationVariantTests` builds 13 key shapes (alone, the root of each + mapping strategy, abstract or not, three dependents whose key is a foreign key, composite, configured) + keyed by 13 wrapped types on SQLite, Npgsql and SQL Server, 507 cases, and has SQLite fill the keys in. + `QueryTranslationVariantTests` runs 5,200 queries over 22 wrapped types (three with their own + `CompareTo`) by accessor, site (column, nullable column, collection element, captured list, navigation, + JSON, query filter, named or not), converter (TypeKit's, named, and three of the application's own) and + operation, on SQLite against the wrapped type's rows and SQL and against memory, and translated for + PostgreSQL. The README's departures from parity are its oracle: a `char` or `Guid` key and a TPC root + without a sequence are not generated, `.Value` on a foreign converter's column and an own `CompareTo` + in an ordering are refused. Measured 2026-10-15 at each fix of chains H and I, the catalogue replayed at + the fix and at its parent: all nine turn cases green (`7732936` 272, `63471e7` 579, `717de54` 111, + `719ee82` 12, `8f6af8b` 12, `92dfe16` 12, `e644a8a` 67, `3bddba7` 13, `84e9776` 17), and three of them + turn cases red that the chain's next fix repaired: `92dfe16` the abstract TPC root on Npgsql and SQL + Server (6), `e644a8a` and `3bddba7` the `char` key (16 and 1). Seeded: without the TPC convention 16 key + cases fail, without the foreign-converter check 579 queries, with element columns re-typed 272. It + found one divergence still open: a nullable record-class value object compared with `<` keeps its null + rows in memory, where the generated operators sort null first, and SQL drops them. diff --git a/docs/ef-core.md b/docs/ef-core.md index 79fc714a..a2f5159c 100644 --- a/docs/ef-core.md +++ b/docs/ef-core.md @@ -165,10 +165,16 @@ cast. **Good to know** -- **A property with your own converter has no `.Value` in a query.** `.Value` on it fails with "could - not be translated". Comparing the value object itself still works. +- **A property with your own converter has `.Value` only in the query's last `Select`.** `.Value` on + it in a filter, a sort, a grouping or an aggregate fails with "could not be translated". Selecting + it as the last step works, since EF takes it in memory. Comparing the value object itself still + works. - **Ordering follows the column's collation in SQL.** The generated comparison in memory is ordinal for a string, as for any string column. +- **A null passes no `<`, `<=`, `>` or `>=` in SQL.** A record-class value object puts null first in + memory. So for a nullable `Rank` record class, `i.Rank < rank` keeps an invoice without a rank in a + list, and leaves it out of the query. Write `i.Rank == null || i.Rank < rank` to keep it in the + query too. - **Your own `CompareTo(TSelf)` can't run in the database.** The database orders the column by the underlying value. So a query that compares or orders such a value object, or takes its `Min` or `Max`, is rejected, and the error names the value object. Write `.Value` to say the underlying diff --git a/docs/json.md b/docs/json.md index a976104b..44e9c24b 100644 --- a/docs/json.md +++ b/docs/json.md @@ -84,6 +84,9 @@ of options. `InvalidOperationException`, because the attribute is valid only on a number. Set the number handling on the options instead, or in a source-generated context's `[JsonSourceGenerationOptions(NumberHandling = ...)]`. Both reach the value object. +- **An array of value objects over `byte` is a JSON array of numbers.** Each element is written as its byte, + so you get `[7,255]`, not the base64 string `"B/8="` that a `byte[]` becomes. Keep a `byte[]` where a client + expects base64. ## When a value is rejected diff --git a/docs/type-constraints.md b/docs/type-constraints.md index f9336a89..d5b81b8b 100644 --- a/docs/type-constraints.md +++ b/docs/type-constraints.md @@ -47,7 +47,7 @@ Each attribute accepts some kinds of type and rejects the rest: | `[Record]` | a record class or record struct | a class or a struct | | `[Concrete]` | a class, record or struct that can be instantiated, an enum, a delegate | an abstract or static type, an interface, and the abstract bases `Enum`, `Delegate` and `ValueType`. Unlike `new()`, it demands no constructor | | `[Interface]` | an interface | a class, even one that implements the interface | -| `[Unsealed]` | a class or record class that can be derived from, abstract included, and `dynamic`, which is `object` | a sealed or static class, an interface, a struct | +| `[Unsealed]` | a class or record class that can be derived from, abstract included, and `dynamic`, which is `object`. Until 3.0.0 it also accepts `Enum`, `Delegate`, `MulticastDelegate`, `ValueType` and `Array`, which nothing can derive from | a sealed or static class, an interface, a struct | | `[ValueObject]` | a struct or class that implements `IValue<T>` for any `T`, which no `where` can say | any other type, a closed set included, and an interface, even one that derives from `IValue<T>` | | `[ClosedSet]` | a type that implements `IClosedSet<TSelf>`, a fault set included | any other type, a value object or an enum included | | `[FlagsEnum]` | an enum marked `[Flags]` | a plain enum, which `where T : Enum` admits | diff --git a/spikes/MutationSpike/.gitignore b/spikes/MutationSpike/.gitignore new file mode 100644 index 00000000..063487f0 --- /dev/null +++ b/spikes/MutationSpike/.gitignore @@ -0,0 +1 @@ +StrykerOutput/ diff --git a/spikes/MutationSpike/README.md b/spikes/MutationSpike/README.md new file mode 100644 index 00000000..5ad100c2 --- /dev/null +++ b/spikes/MutationSpike/README.md @@ -0,0 +1,98 @@ +# Spike: does Stryker.NET run against this repository's tests, and what does it find? + +**Status: run 2026-10-16, answered.** The bounded first trial of WS4 (mutation as the measure of +detection gaps) in the agentic issue-finding programme, on one satellite: `CodoMetis.TypeKit.Serilog` +with `test/CodoMetis.TypeKit.Serilog.Tests`. It decides nothing yet; it tells WS4 whether planning on +Stryker is possible. The survivors and their reading are in +[design/mutation-2026-10-16.md](../../design/mutation-2026-10-16.md). + +**Question.** Stryker.NET's own integration test for its Microsoft Testing Platform runner pins +xunit.v3 3.2.2 with Microsoft.NET.Test.Sdk. This repository pins xunit.v3 4.0.2, which is Testing +Platform v2 with no Test.Sdk, and treats warnings as errors. Does Stryker's current release run here +with its `mtp` runner, and if it does, what are the counts and what do the survivors say about the +tests? + +**Answer: yes, it runs, and its score undercounts.** Stryker.NET 5.0.0 (`dotnet-stryker`, the +current release on nuget.org) with `"test-runner": "mtp"` built the test project, found its 10 tests, +captured per-test coverage and tested every placeable mutant against xunit.v3 4.0.2 on +Microsoft.Testing.Platform 2.5.1, with no change to `src/` or `test/`. Of 15 mutants: 3 killed, +3 survived, 0 timed out, 0 without coverage, 7 compile errors, 2 ignored; score 50.00 %. The run took +17.7 s cold and 8.4 s warm. Two limits matter more than the score: + +1. **A declaration pattern costs its whole expression.** Stryker places every mutant in one + compilation by wrapping the mutated expression in a ternary over the original (mutant schemata). + Both arms then declare the same pattern variable (`is { } valueObject`), which is CS0128, and + Stryker rolls back every mutant whose span holds the error. 4 of the 7 compile errors are mutants + that cannot compile anyway (`is { } v || v.X` reads an unassigned `v`, CS0165). The other 3 are + valid mutants Stryker lost: `==` to `!=` on each attribute's type check and the outer `||` to `&&`. + Seeded by hand into `src/` and run through the ordinary MSBuild build, each was killed (4, 3 and 7 + of the 10 tests failed). They are the policy's core logic, and the score never saw them. Lines + with a declaration pattern outside comments, a rough count: base package 48, Analyzers 128, + AspNetCore 78, EntityFrameworkCore 22, Testing 1, Serilog 2. How many mutants each costs is + measured only by a run. +2. **The `mtp` runner is preview.** Stryker says so on every run ("The Microsoft Test Platform + testrunner is currently in preview. Results should be verified"). Here they were: each of the 3 + survivors, seeded by hand, also passes all 10 tests under the ordinary build, and each of the 3 + lost mutants fails it, so Stryker's verdicts agreed with MSBuild on all 6 checked. + +Of the 3 survivors, 1 is a missing assertion (the null guard of `WithTypeKit`) and 2 are equivalent +mutants (`inherit: false` to `true` on attributes declared `Inherited = false`). None is a product +defect. + +## Evidence + +| What | Value | +|---|---| +| Stryker.NET | `dotnet-stryker` 5.0.0, local tool in [dotnet-tools.json](dotnet-tools.json) (`rollForward: false`) | +| Test framework | xunit.v3 4.0.2 (`xunit.v3.core.mtp-v2` 4.0.2), Microsoft.Testing.Platform 2.5.1, Shouldly 4.3.0, no Microsoft.NET.Test.Sdk | +| Library under test | Serilog 4.0.0, the satellite's floor | +| SDK | 10.0.402 (`global.json`), macOS 27.0.1 arm64, Apple M4 Max, 16 cores; Stryker's default concurrency 8 | +| Mutation level | `Complete`; coverage analysis `perTest` | +| Tests found | 10, all passing before the run (the verdict tool: `TESTS OK: 10 passed`) | +| Mutants | 15: 3 killed, 3 survived, 0 timeout, 0 no coverage, 7 compile error, 2 ignored ("block already covered") | +| Score | 50.00 % (killed / (killed + survived)) | +| Run time | 17.7 s first run, 8.4 s second, same counts both times | + +Every mutant, its status and the reading of each is in the report under `design/`. + +The compile errors, from the trace log (`-L`), with the code the German-locale message names: + +| Attempt | Errors | Rolled back | +|---|---|---| +| 1 | CS0411 `MutantContext.TrackValue<T>` cannot infer `T`; CS0128 `valueObject` and `set` already defined (3 each); CS0165 unassigned `valueObject`, `set` | mutants 7, 8, 11, 12 | +| 2 | CS0128 `valueObject`, `set`; CS0165 `valueObject`, `set` | mutants 6, 10, 14 | +| 3 | none | | + +The test project's `bin/Debug/.../CodoMetis.TypeKit.Serilog.dll` held no Stryker code after either +run (no `Stryker` string in it, the size of the ordinary build), and the next ordinary test run +passed, so a run leaves the build as it found it. + +## Running it again + +Stryker runs as a local tool from this folder; the manifest pins the version measured. Nothing runs +in CI ([decision 92](../../design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md): CI +needs no model, and WS4 is local and nightly). + +```bash +cd spikes/MutationSpike +dotnet tool restore +dotnet tool run dotnet-stryker -- --skip-version-check +``` + +Add `-L` for the trace log, which names each compile error and each rollback. The report is written +to `spikes/MutationSpike/StrykerOutput/<yyyy-MM-dd.HH-mm-ss>/reports/mutation-report.html` and +`mutation-report.json` beside it, with the logs under `logs/`; the folder is git-ignored, and the +2026-10-16 runs were `2026-10-16.19-51-01` and `2026-10-16.19-53-13`. +[stryker-config.json](stryker-config.json) names the source project and the test project; another +satellite is a copy of it with its own `project` and `test-projects`. + +## What it leaves for WS4 + +- Stryker is usable here as it ships; no Test.Sdk, no VSTest, no pinned-down xunit. +- Its score is not the measure on this code: a mutant lost to a compile error is invisible in the + score, and this repository writes declaration patterns throughout. A WS4 run reports the + compile-error count beside the score, per package, and reads the trace log for valid mutants that + were rolled back. +- The base package, EntityFrameworkCore, AspNetCore, Testing and the Analyzers are the next + candidates; the Generators package stays with decision 100's seeds, since Stryker compiles the + source project with Roslyn and Metalama does not run in that compilation. diff --git a/spikes/MutationSpike/dotnet-tools.json b/spikes/MutationSpike/dotnet-tools.json new file mode 100644 index 00000000..bd866140 --- /dev/null +++ b/spikes/MutationSpike/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-stryker": { + "version": "5.0.0", + "commands": [ + "dotnet-stryker" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/spikes/MutationSpike/stryker-config.json b/spikes/MutationSpike/stryker-config.json new file mode 100644 index 00000000..19b7122c --- /dev/null +++ b/spikes/MutationSpike/stryker-config.json @@ -0,0 +1,13 @@ +{ + "stryker-config": { + "test-runner": "mtp", + "project": "CodoMetis.TypeKit.Serilog.csproj", + "test-projects": [ + "../../test/CodoMetis.TypeKit.Serilog.Tests/CodoMetis.TypeKit.Serilog.Tests.csproj" + ], + "mutation-level": "Complete", + "coverage-analysis": "perTest", + "reporters": [ "progress", "cleartext", "html", "json" ], + "thresholds": { "high": 80, "low": 60, "break": 0 } + } +} diff --git a/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md b/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md index 270412c6..7721d6b8 100644 --- a/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Shipped.md @@ -61,3 +61,5 @@ CMTK0003 | Usage | Error | Usage | Warning | IgnoredOutcomeAnalyzer, raised in a Rule ID | Category | Severity | Notes --------|----------|----------|------------------------------------------------------------- CMTK0014 | Usage | Warning | UndefinedEnumValueAnalyzer, a FaultKind or ConstructorAccessibility that names no member + +## Release 2.2.0 diff --git a/src/CodoMetis.TypeKit.Analyzers/README.md b/src/CodoMetis.TypeKit.Analyzers/README.md index 9e5427b3..88816f9c 100644 --- a/src/CodoMetis.TypeKit.Analyzers/README.md +++ b/src/CodoMetis.TypeKit.Analyzers/README.md @@ -434,7 +434,7 @@ public abstract class Sound<[Abstract] TCommand> : Decider<TCommand, OrderPlaced | `[Record]` | a record class or record struct | it carries `[Record]`, or is bound to a record or to a parameter that proves `[Record]`: only records derive from records | | `[Concrete]` | a class, record or struct that is not abstract, static or an interface, an enum, a delegate, an array; no constructor is demanded, unlike `new()` | it carries `[Concrete]`, is constrained to `new()`, or proves `[Sealed]`: a sealed type is never abstract | | `[Interface]` | an interface; not a class implementing one | it carries `[Interface]`; `where T : IContract` admits every implementer | -| `[Unsealed]` | a class or record class that is neither sealed nor static, abstract included; not an interface, which is implemented rather than derived from, nor a struct | it carries `[Unsealed]`; `class` and a class bound admit a sealed subclass | +| `[Unsealed]` | a class or record class that is neither sealed nor static, abstract included; not an interface, which is implemented rather than derived from, nor a struct. Until 3.0.0 also `Enum`, `Delegate`, `MulticastDelegate`, `ValueType` and `Array`, which nothing can derive from | it carries `[Unsealed]`; `class` and a class bound admit a sealed subclass | | `[ValueObject]` | a type implementing `IValue<T>` or `IValidatedValue<TSelf, T, TFault>` for any `T`, struct or class, through any interface; not an interface, even one deriving from a marker | it carries `[ValueObject]`, or is bound to a marker, to a type implementing one, or to a parameter so bound, beside `struct`, `unmanaged`, `new()` or a class bound, since `where T : IValue<int>`, and `class` beside it, admit `IValue<int>` itself | | `[ClosedSet]` | a type implementing `IClosedSet<TSelf>`, a fault set (`IFaultSet<TSelf>`) included | it carries `[ClosedSet]`, or is bound to `IClosedSet<TSelf>`, to a closed set, or to a parameter so bound | | `[FlagsEnum]` | an enum marked `[Flags]` | it carries `[FlagsEnum]`; `Enum` admits a plain enum, and no bound names an enum | diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs index 3456e346..5c1fd7e5 100644 --- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs @@ -806,7 +806,8 @@ private static bool CouldBe(CSharpCompilation compilation, ITypeParameterSymbol TypeArgumentConstraint.Sealed => argument.TypeKind is TypeKind.Struct or TypeKind.Enum or TypeKind.Array || argument is INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Delegate, IsSealed: true, IsStatic: false }, TypeArgumentConstraint.Record => argument is INamedTypeSymbol { IsRecord: true }, TypeArgumentConstraint.Interface => argument.TypeKind is TypeKind.Interface, - // dynamic is object, a class that can be derived from, as [Concrete] takes it. + // dynamic is object, a class that can be derived from, as [Concrete] takes it. The abstract bases nothing + // derives from (Enum, Delegate, MulticastDelegate, ValueType, Array) are taken too until 3.0.0 (decision 34). TypeArgumentConstraint.Unsealed => argument.TypeKind is TypeKind.Dynamic || argument is INamedTypeSymbol { TypeKind: TypeKind.Class, IsSealed: false, IsStatic: false }, TypeArgumentConstraint.ValueObject => argument.TypeKind is not TypeKind.Interface && symbols.FindMarker(argument, out _) is not null, TypeArgumentConstraint.ClosedSet => symbols.FindClosedSetMarker(argument) is not null, diff --git a/src/CodoMetis.TypeKit.AspNetCore/ClosedSetSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ClosedSetSchemaTransformer.cs index b6f00b30..fef86686 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/ClosedSetSchemaTransformer.cs +++ b/src/CodoMetis.TypeKit.AspNetCore/ClosedSetSchemaTransformer.cs @@ -30,6 +30,9 @@ internal sealed class ClosedSetSchemaTransformer : IOpenApiSchemaTransformer public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { + // A non-abstract polymorphic base's own branch, which ASP.NET hands to no transformer. + await PolymorphicBaseBranch.WalkAsync(schema, context, this, cancellationToken); + var info = context.JsonTypeInfo; if (Describe(info.Type) is { } set) @@ -57,9 +60,9 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC // Only what ASP.NET left out: a schema it did build is its own description of the elements. if (info.Kind == JsonTypeInfoKind.Enumerable && schema.Items is null) - schema.Items = await context.GetOrCreateSchemaAsync(element, parameterDescription: null, cancellationToken); + schema.Items = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, element, parameterDescription: null, cancellationToken); else if (info.Kind == JsonTypeInfoKind.Dictionary && schema.AdditionalProperties is null) - schema.AdditionalProperties = await context.GetOrCreateSchemaAsync(element, parameterDescription: null, cancellationToken); + schema.AdditionalProperties = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, element, parameterDescription: null, cancellationToken); } private static void Fill(OpenApiSchema schema, GeneratedClosedSetAttribute set, bool admitsNull) diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs index d873ba65..072f8a9e 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs +++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Schema; @@ -37,7 +38,8 @@ namespace CodoMetis.TypeKit.AspNetCore; /// A route, query or header parameter binds through <c>TryParse</c>, not JSON, so a described converter /// says nothing of it. ASP.NET describes a parameter of a primitive, a date, a <see cref="decimal"/> or a /// <see cref="Guid"/> by the type's JSON schema, which the converter emptied; the parameter gets back the -/// schema the type has without converters of one's own, beside the <c>format</c> ASP.NET kept. +/// schema the type has without converters of one's own, beside the <c>format</c> ASP.NET kept. A query or header +/// array of it, whose <c>items</c> ASP.NET dropped for the converter, gets them back the same way. /// </para> /// </remarks> internal sealed class DescribedSchemaTransformer : IOpenApiSchemaTransformer @@ -45,8 +47,20 @@ internal sealed class DescribedSchemaTransformer : IOpenApiSchemaTransformer /// <summary>The types described as another on this call path, to refuse a description that never ends.</summary> private static readonly AsyncLocal<ImmutableStack<Type>?> SameAsPath = new(); + /// <summary> + /// The elements put back into a container ASP.NET left without one, with their type. ASP.NET goes on to visit each + /// as that type, where a nullable one of a type with a component reads as the component, which admits no null. + /// </summary> + private static readonly ConditionalWeakTable<OpenApiSchema, Type> PutBack = new(); + public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { + // A non-abstract polymorphic base's own branch, which ASP.NET hands to no transformer. + await PolymorphicBaseBranch.WalkAsync(schema, context, this, cancellationToken); + + // Already complete: visited again as a nullable type with a component, it would lose its null. + if (PutBack.TryGetValue(schema, out _)) return; + var info = context.JsonTypeInfo; var options = info.Options; @@ -54,7 +68,22 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC // route, the query, a header or a form field is text TryParse reads. if (context is { JsonPropertyInfo: null, ParameterDescription: { } parameter } && parameter.Source != BindingSource.Body) { - if (Description(info.Type, options, property: null) is not null) Parameter(schema, info.Type, options, Version(context)); + if (Description(info.Type, options, property: null) is not null) + { + Parameter(schema, info.Type, options, itself: ValueObjectSchemaTransformer.IsParameterItself(parameter, info.Type)); + return; + } + + // A query or header array of a described type: ASP.NET dropped the items for the converter, as it does in JSON. + // The element is ASP.NET's own, asked for as a parameter's element, which this transformer then describes as above. + if (info.ElementType is not { } item || info.Kind is not (JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary)) return; + if (Description(item, options, property: null) is null) return; + + if (info.Kind == JsonTypeInfoKind.Enumerable && schema.Items is null) + schema.Items = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, item, ValueObjectSchemaTransformer.Detached(parameter), cancellationToken); + else if (info.Kind == JsonTypeInfoKind.Dictionary && schema.AdditionalProperties is null) + schema.AdditionalProperties = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, item, ValueObjectSchemaTransformer.Detached(parameter), cancellationToken); + return; } @@ -101,6 +130,7 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC var elementDescribed = await SchemaAsync(elementDescription, element, context, cancellationToken); if (Nullable.GetUnderlyingType(element) is not null) PatchSchemaTransformer.AdmitNull(elementSchema, elementDescribed); else elementSchema.FillFrom(elementDescribed); + PutBack.AddOrUpdate(elementSchema, element); if (info.Kind == JsonTypeInfoKind.Enumerable) schema.Items = elementSchema; else schema.AdditionalProperties = elementSchema; @@ -174,7 +204,7 @@ internal static async Task<OpenApiSchema> SchemaAsync(JsonSchemaDescription desc SameAsPath.Value = path.Push(sameAs); try { - var other = await context.GetOrCreateSchemaAsync(sameAs, parameterDescription: null, cancellationToken); + var other = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, sameAs, parameterDescription: null, cancellationToken); var copy = new OpenApiSchema(); copy.FillFrom(other is OpenApiSchema concrete ? concrete : new OpenApiSchema()); return copy; @@ -195,12 +225,7 @@ internal static async Task<OpenApiSchema> SchemaAsync(JsonSchemaDescription desc /// </summary> internal static OpenApiSchema Parse(JsonObject schema, Type type, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_1) { - var parsed = OpenApiModelFactory.Parse<OpenApiSchema>(schema.ToJsonString(), OpenApiSpecVersion.OpenApi3_1, new OpenApiDocument(), out var diagnostic, "json"); - - if (parsed is null || diagnostic is { Errors.Count: > 0 }) - throw new InvalidOperationException( - $"The JSON schema described for '{type}' is not one an OpenAPI document can hold: " + - string.Join("; ", diagnostic?.Errors.Select(error => error.Message) ?? ["it did not parse"]) + "."); + var parsed = Read(schema, type); if (FirstChanged(schema, Written(parsed, OpenApiSpecVersion.OpenApi3_1), "") is { } changed) throw new InvalidOperationException( @@ -215,6 +240,19 @@ internal static OpenApiSchema Parse(JsonObject schema, Type type, OpenApiSpecVer return parsed; } + /// <summary>A JSON Schema as Microsoft.OpenApi reads one, refused only where it does not read.</summary> + private static OpenApiSchema Read(JsonObject schema, Type type) + { + var parsed = OpenApiModelFactory.Parse<OpenApiSchema>(schema.ToJsonString(), OpenApiSpecVersion.OpenApi3_1, new OpenApiDocument(), out var diagnostic, "json"); + + if (parsed is null || diagnostic is { Errors.Count: > 0 }) + throw new InvalidOperationException( + $"The JSON schema described for '{type}' is not one an OpenAPI document can hold: " + + string.Join("; ", diagnostic?.Errors.Select(error => error.Message) ?? ["it did not parse"]) + "."); + + return parsed; + } + private static string Name(OpenApiSpecVersion version) => version switch { OpenApiSpecVersion.OpenApi2_0 => "2.0", @@ -356,21 +394,48 @@ private static bool Unwritten(string keyword, JsonNode? value) => /// <summary> /// A parameter of <paramref name="type"/> as ASP.NET describes it without converters of one's own: the - /// schema the options' resolver gives the type with none registered, beside the format ASP.NET kept. + /// schema the options' resolver gives the type with none registered, beside the format ASP.NET kept. A + /// nullable parameter is its underlying type, as ASP.NET writes it: an absent parameter is its null. A + /// nullable element of a parameter array (<c>decimal?[]</c>), and anything else below the parameter, keeps + /// its null, as ASP.NET writes it there. The + /// schema is ASP.NET's, not a description, so it is not held to what a description must survive into: a + /// 2.0 document writes a decimal read from a string, <c>["number","string"]</c>, as it writes ASP.NET's own. /// </summary> - private static void Parameter(OpenApiSchema schema, Type type, JsonSerializerOptions options, OpenApiSpecVersion version) + private static void Parameter(OpenApiSchema schema, Type type, JsonSerializerOptions options, bool itself) { - var plain = new JsonSerializerOptions(options); - plain.Converters.Clear(); + var plain = Plain(options); - if (!plain.TryGetTypeInfo(type, out var info)) return; + if (!plain.TryGetTypeInfo(itself ? Nullable.GetUnderlyingType(type) ?? type : type, out var info)) return; if (info.GetJsonSchemaAsNode() is not JsonObject native) return; var format = schema.Format; var description = schema.Description; Reshape(schema); - schema.FillFrom(Parse(native, type, version)); + schema.FillFrom(Read(native, type)); schema.Format ??= format; schema.Description ??= description; + ParameterDefault(schema, type, options); + } + + /// <summary>The options without converters of one's own: what ASP.NET writes a parameter with where there are none.</summary> + private static JsonSerializerOptions Plain(JsonSerializerOptions options) + { + var plain = new JsonSerializerOptions(options); + plain.Converters.Clear(); + return plain; + } + + /// <summary> + /// A parameter's default as ASP.NET writes it without converters of one's own. ASP.NET writes the default of a + /// parameter of <paramref name="type"/> with the JSON options, a described converter included (a decimal's + /// <c>"1.5"</c> under <c>DecimalStringJsonConverter</c>), where the parameter binds through <c>TryParse</c> and its + /// schema is the type's without the converter (<c>1.5</c>). Read back as the options wrote it, the value is written + /// again without them; for a value object, its generated converter writes the type it wraps the same way. + /// </summary> + internal static void ParameterDefault(OpenApiSchema schema, Type type, JsonSerializerOptions options) + { + if (schema.Default is not { } written || !Plain(options).TryGetTypeInfo(type, out var plain)) return; + + schema.Default = JsonSerializer.SerializeToNode(JsonSerializer.Deserialize(written, options.GetTypeInfo(type)), plain); } } diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs index 475e1216..8e936ee6 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs @@ -116,11 +116,11 @@ public async Task TransformAsync(OpenApiOperation operation, OpenApiOperationTra // nobody wrote gets the problem types. Skipped whole, a host declaring every status described // neither, though the problems carried fault (reported 2026-10-14). if (declared is OpenApiResponse { Content: { } content } response - && content.TryGetValue("application/problem+json", out var media)) + && DeclaredProblems(context, status, content) is { } schemas) { problem ??= await ProblemSchemaAsync(context, cancellationToken); touched = true; - if (media.Schema is OpenApiSchema inline) WithFault(inline, Qualified(context)); + foreach (var schema in schemas) WithFault(schema, Qualified(context)); if (string.IsNullOrEmpty(response.Description) || response.Description == ReasonPhrases.GetReasonPhrase(status)) response.Description = Description(status, types); } @@ -162,6 +162,39 @@ private static (int Status, int Range) Rank(string key) => : string.Equals(key, "default", StringComparison.OrdinalIgnoreCase) ? (int.MaxValue, 1) : (int.MaxValue, 0); + /// <summary> + /// The problem schemas of a response the endpoint declares itself: its <c>application/problem+json</c> + /// content's, and every content's whose type the endpoint declares as <see cref="ProblemDetails"/> or one + /// derived from it, under the media type it declares. <c>Produces<ProblemDetails>(404)</c> and + /// <c>[ProducesResponseType<ProblemDetails>(404)]</c> declare <c>application/json</c>, and read for + /// <c>application/problem+json</c> alone their 404 described neither <c>fault</c> nor the problem types (reported + /// 2026-10-16). A schema that refers to a component is that component, so a derived type's own one gets them; + /// <see cref="ProblemDetails"/>'s gets them from <see cref="ProblemSchemaAsync"/> as well. A <c>$ref</c> under + /// <c>application/problem+json</c> to a type that is no problem is left alone. Null where no content is a problem. + /// </summary> + private static List<IOpenApiSchema>? DeclaredProblems(OpenApiOperationTransformerContext context, int status, IDictionary<string, OpenApiMediaType> content) + { + var problems = context.Description.SupportedResponseTypes + .Where(declared => declared.StatusCode == status && declared.Type is { } type && typeof(ProblemDetails).IsAssignableFrom(type)) + .ToList(); + List<IOpenApiSchema>? schemas = null; + + foreach (var (mediaType, media) in content) + { + var declaresProblem = problems.Any(declared => declared.ApiResponseFormats.Count == 0 + || declared.ApiResponseFormats.Any(format => string.Equals(format.MediaType, mediaType, StringComparison.OrdinalIgnoreCase))); + if (!declaresProblem && mediaType != "application/problem+json") continue; + + schemas ??= []; + if (media.Schema is OpenApiSchema inline) schemas.Add(inline); + else if (declaresProblem && media.Schema is OpenApiSchemaReference { Reference.Id: { } id } + && context.Document?.Components?.Schemas?.TryGetValue(id, out var component) == true && component is not null) + schemas.Add(component); + } + + return schemas; + } + private static string Description(int status, List<string> types) => types.Count == 0 ? ReasonPhrases.GetReasonPhrase(status) diff --git a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs index 850e25a4..17865cc1 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +++ b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs @@ -73,6 +73,9 @@ internal static void AdmitNull(OpenApiSchema schema, OpenApiSchema inner) public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { + // A non-abstract polymorphic base's own branch, which ASP.NET hands to no transformer. + await PolymorphicBaseBranch.WalkAsync(schema, context, this, cancellationToken); + var info = context.JsonTypeInfo; // The object holding patch properties: none of them is required, since absent is a state, unless @@ -99,7 +102,7 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC ? await DescribedSchemaTransformer.SchemaAsync(description, argument, context, cancellationToken) : DescribedSchemaTransformer.Replaced(argument, info.Options) ? new OpenApiSchema() - : await context.GetOrCreateSchemaAsync(ValueObjectTypes.WrappedType(argument) ?? argument, parameterDescription: null, cancellationToken); + : await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, ValueObjectTypes.WrappedType(argument) ?? argument, parameterDescription: null, cancellationToken); schema.Properties?.Clear(); AdmitNull(schema, inner); diff --git a/src/CodoMetis.TypeKit.AspNetCore/PolymorphicBaseBranch.cs b/src/CodoMetis.TypeKit.AspNetCore/PolymorphicBaseBranch.cs new file mode 100644 index 00000000..13550242 --- /dev/null +++ b/src/CodoMetis.TypeKit.AspNetCore/PolymorphicBaseBranch.cs @@ -0,0 +1,105 @@ +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.AspNetCore; + +/// <summary> +/// The branch of a non-abstract <c>[JsonPolymorphic]</c> base that describes the base itself, which ASP.NET never hands to +/// a schema transformer: each of TypeKit's transformers walks it from the polymorphic schema, as ASP.NET walks the rest. +/// </summary> +/// <remarks> +/// <para> +/// A base that is not abstract and does not list itself as a derived type is serialized as itself too, so its schema is +/// an <c>anyOf</c> of each derived type's branch and, last, the base's own (its <c>x-schema-id</c> is <c>Base</c>, the +/// component <c>{Base}Base</c>). ASP.NET visits the derived types' branches by <c>PolymorphismOptions.DerivedTypes</c>, +/// one per index, and stops there (measured with Microsoft.AspNetCore.OpenApi 10.0, and read in its +/// <c>OpenApiSchemaService.InnerApplySchemaTransformersAsync</c>): a value object, a patch, a closed set or a described +/// type among the base's properties was left as ASP.NET writes a converter of one's own, <c>{}</c>, and a list of them +/// lost its <c>items</c>. +/// </para> +/// <para> +/// A transformer that meets a polymorphic schema hands the branches past the derived types to <see cref="WalkAsync"/>, +/// which visits them, and everything below them, in ASP.NET's order and with the type and property ASP.NET would have +/// given. ASP.NET's context cannot be pointed at another type from outside, so each visit gets a context of its own; a +/// schema it asks for (<see cref="GetOrCreateSchemaAsync"/>) is asked of ASP.NET's context, whose transformers a context +/// made here does not have. +/// </para> +/// </remarks> +internal static class PolymorphicBaseBranch +{ + /// <summary>The context ASP.NET made, for each context made here.</summary> + private static readonly ConditionalWeakTable<OpenApiSchemaTransformerContext, OpenApiSchemaTransformerContext> Origins = new(); + + /// <summary> + /// <see cref="OpenApiSchemaTransformerContext.GetOrCreateSchemaAsync"/> through the context ASP.NET made: the schema of + /// <paramref name="type"/>, every transformer of the document applied. + /// </summary> + public static Task<OpenApiSchema> GetOrCreateSchemaAsync( + OpenApiSchemaTransformerContext context, Type type, ApiParameterDescription? parameterDescription, CancellationToken cancellationToken) => + Origin(context).GetOrCreateSchemaAsync(type, parameterDescription, cancellationToken); + + /// <summary>Visits, with <paramref name="transformer"/>, the branches of a polymorphic schema ASP.NET does not.</summary> + public static async Task WalkAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, IOpenApiSchemaTransformer transformer, CancellationToken cancellationToken) + { + var info = context.JsonTypeInfo; + if (info.PolymorphismOptions is not { } polymorphism || schema.AnyOf is not { } branches) return; + + for (var i = polymorphism.DerivedTypes.Count; i < branches.Count; i++) + await VisitAsync(branches[i], info, property: null, Origin(context), transformer, cancellationToken); + } + + private static OpenApiSchemaTransformerContext Origin(OpenApiSchemaTransformerContext context) => + Origins.TryGetValue(context, out var origin) ? origin : context; + + /// <summary>A schema and those below it, as <c>InnerApplySchemaTransformersAsync</c> visits them.</summary> + private static async Task VisitAsync( + IOpenApiSchema input, + JsonTypeInfo info, + JsonPropertyInfo? property, + OpenApiSchemaTransformerContext origin, + IOpenApiSchemaTransformer transformer, + CancellationToken cancellationToken) + { + if ((input as OpenApiSchema ?? (input as OpenApiSchemaReference)?.Target as OpenApiSchema) is not { } schema) return; + + var context = new OpenApiSchemaTransformerContext + { + DocumentName = origin.DocumentName, + ParameterDescription = origin.ParameterDescription, + JsonTypeInfo = info, + JsonPropertyInfo = property, + ApplicationServices = origin.ApplicationServices, + Document = origin.Document, + }; + Origins.AddOrUpdate(context, origin); + + // A polymorphic schema below walks its own base branch from here, as at the top. + await transformer.TransformAsync(schema, context, cancellationToken); + + var options = info.Options; + + if (schema.AnyOf is { Count: > 0 } branches && info.PolymorphismOptions is { } polymorphism) + { + for (var i = 0; i < polymorphism.DerivedTypes.Count && i < branches.Count; i++) + await VisitAsync(branches[i], options.GetTypeInfo(polymorphism.DerivedTypes[i].DerivedType), property: null, origin, transformer, cancellationToken); + } + + if (schema.Items is { } items && info.ElementType is { } element) + await VisitAsync(items, options.GetTypeInfo(element), property: null, origin, transformer, cancellationToken); + + if (schema.Properties is { Count: > 0 } properties) + { + foreach (var member in info.Properties) + { + if (properties.TryGetValue(member.Name, out var memberSchema)) + await VisitAsync(memberSchema, options.GetTypeInfo(member.PropertyType), member, origin, transformer, cancellationToken); + } + } + + if (schema is { AdditionalPropertiesAllowed: true, AdditionalProperties: { } values } && info.ElementType is { } valueType) + await VisitAsync(values, options.GetTypeInfo(valueType), property: null, origin, transformer, cancellationToken); + } +} diff --git a/src/CodoMetis.TypeKit.AspNetCore/README.md b/src/CodoMetis.TypeKit.AspNetCore/README.md index 6bc9904a..0428cc4a 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/README.md +++ b/src/CodoMetis.TypeKit.AspNetCore/README.md @@ -116,6 +116,12 @@ element schema, so `Quantity` gets no component at all, and both parameters are referenced one allows it around its reference. A closed set's property is then the enum of its names plus null. + OpenAPI 2.0 can't put null around a reference. ASP.NET writes a nullable property of any component as + `{"allOf":[{"x-nullable":true}]}` there, without the reference. So under 2.0 a nullable value-object + property loses its type, where the underlying type's nullable property keeps it. A nullable element of + a list or a dictionary loses its type the same way, since it's the component or null too. Inline value + objects if your 2.0 document needs it. + - **Put rules in `Create`, not on the property.** A validation attribute on a value-object property (`[MaxLength]`, `[Range]`) ends up in the value object's shared component, as ASP.NET does for any referenced schema. From there it applies to every other use too. `Create` is where every way of @@ -155,7 +161,8 @@ source-generated JSON can do, because the patch's converter hides the value obje **A described type.** ASP.NET can't see through a JSON converter you write yourself. It writes the type as `{}`, or with only the `format` its CLR type maps to. It also drops a list's `items` and a -dictionary's `additionalProperties` of that type. +dictionary's `additionalProperties` of that type. The same goes for a converter a library registers for +its own type, such as NodaTime's for `Instant`. Two things fix that, and the document then describes the type as they say: @@ -171,8 +178,11 @@ value, and inside a value object or a patch. which then writes it instead of the generated converter. Both hold in a patch of it too. - **The converter asked is the one the serializer runs.** For a factory, that's the converter it makes. For the nullable positions, it's a converter registered for `Nullable<T>`. -- **Parameters aren't JSON.** A route, query or header parameter binds through `TryParse`, not JSON. - It keeps what ASP.NET writes for the type without custom converters. +- **Parameters aren't JSON.** A route, query, header or form parameter binds through `TryParse`, not + JSON. It keeps what ASP.NET writes for the type without custom converters, and so do the items of a + query or header array (`decimal[]`). The same goes for a value object over the type, such as + `Money : IValue<decimal>`. Where value objects are components, such a parameter is written in place + rather than as a reference to `Money`, because the `Money` component says what its JSON is. - **Register the same on both option sets.** ASP.NET builds the document from the minimal-API JSON options (`ConfigureHttpJsonOptions`), while controllers write with MVC's (`AddJsonOptions`). A host with controllers registers the same converters and descriptions on both, in the same order. @@ -240,7 +250,10 @@ app.MapDelete("/orders/{id}", (OrderId id) => orders.Cancel(id).ToHttpResult()); - **The document shows it too.** The endpoint's return type is ASP.NET's own `Results<Ok<OrderDto>, FaultProblem<OrderFault>>`, so the operation describes the value. `AddTypeKit()` adds a problem response for every status the error type is mapped to, as - `ProducesProblem` would. An MVC action returning the same type is described the same way. + `ProducesProblem` would. An MVC action returning the same type is described the same way. A + response you declare yourself at one of those statuses, with `Produces(404)` or + `[ProducesResponseType(404)]`, is kept as you declared it: the document doesn't add the problem + schema there, though the wire still answers a problem. - **It's not a wire format.** The `Result` is matched to a response, not serialized. Returning the `Result` itself from a handler is still rejected, and the document would promise a `Result`. - **It changes every error response of the host, not only these.** `AddFaultResponses` calls diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs index fdc6ea96..6078f794 100644 --- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs @@ -1,7 +1,9 @@ using System.Collections.Immutable; using System.Runtime.CompilerServices; +using System.Text.Json.Nodes; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -43,6 +45,11 @@ namespace CodoMetis.TypeKit.AspNetCore; /// generated one, is not the wrapped type's in a JSON position: it is what that converter says it writes /// (<see cref="DescribedSchemaTransformer"/>), or, where it says nothing, what ASP.NET writes for a /// converter of one's own. A parameter binds through <c>TryParse</c> and is unchanged.</item> +/// <item>Outside the body, a value object binds through <c>TryParse</c>, as a parameter and as an element of a query or +/// header array: the type it wraps is asked for as the parameter's, so a converter that describes that type, such as +/// <c>DecimalStringJsonConverter</c>, says nothing of it there. Such a parameter is written inline where value objects +/// are components, since the component is what its JSON positions say +/// ([decision 106](design/adr/0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md)).</item> /// </list> /// </remarks> internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer @@ -62,6 +69,9 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC // its oneOf, and a null then no longer matched them. if (NullableElements.TryGetValue(schema, out _)) return; + // A non-abstract polymorphic base's own branch, which ASP.NET hands to no transformer. + await PolymorphicBaseBranch.WalkAsync(schema, context, this, cancellationToken); + var info = context.JsonTypeInfo; if (ValueObjectTypes.WrappedType(info.Type) is { } wrapped) @@ -69,14 +79,41 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC // Written by a converter the options put in charge, not the generated one: not the wrapped type's. if (DescribedSchemaTransformer.Replaced(info.Type, info.Options)) return; - // Where ASP.NET makes the value object a component, it adds the null around the reference itself. - // Inlined, the schema is this position's own, and the null goes in it, as ASP.NET's exporter puts - // it into the wrapped type's schema for a nullable property of that type. - var admitsNull = (Nullable.GetUnderlyingType(info.Type) is not null - || context.JsonPropertyInfo is { IsGetNullable: true } or { IsSetNullable: true }) - && SchemaReferenceId(info, context) is null; + // Where ASP.NET makes the value object a component, it adds the null around the reference itself (marked below where + // it would not). + // Inlined, the schema is this position's own, and the null goes in it where ASP.NET's exporter puts + // it into the wrapped type's schema in the same position: for a nullable property of that type, and + // elsewhere for the Nullable<T> of a wrapped value type, a parameter outside the body included. Not at + // the root of a wrapped reference type, which has none (a string? body is a string, inside ASP.NET's null-or). + // + // Outside the body, as a parameter or an element of one, the value object binds through TryParse: the type it + // wraps is asked for as that parameter's, so a converter that describes it says nothing here, as it says + // nothing of a parameter of that type. Then the schema is not its JSON's, and it is written inline, never as + // the component its JSON positions share (decision 106). + var outside = ParameterOutsideTheBody(context) is { } given ? Detached(given) : null; + var inline = outside is not null && WrapsDescribed(info.Type, info.Options); + if (inline) + { + Inline(schema); + DescribedSchemaTransformer.ParameterDefault(schema, info.Type, info.Options); + } + + var component = !inline && SchemaReferenceId(info, context) is not null; + var nullableProperty = context.JsonPropertyInfo is { IsGetNullable: true } or { IsSetNullable: true }; - await DescribeAsync(schema, info.Type, wrapped, parameter: null, admitsNull, context, cancellationToken); + // ASP.NET puts its null around the reference only for a property it reads as nullable through NullabilityInfoContext, + // a settable one annotated: not for a get-only property, a field or an oblivious reference type, whose JSON admits null + // as the serializer's contract says, and whose wrapped type's property keeps its null. So the property is marked as ASP.NET + // marks the others, under its own key (OpenApiConstants.NullableProperty, internal, as decision 106 reads x-schema-id). + if (component && nullableProperty) MarkNullableProperty(schema); + + var position = component ? NullablePosition.None + : nullableProperty ? NullablePosition.Property + : Nullable.GetUnderlyingType(info.Type) is null || !wrapped.IsValueType ? NullablePosition.None + : IsParameterOutsideTheBody(context) ? NullablePosition.ParameterOutsideTheBody + : NullablePosition.Root; + + await DescribeAsync(schema, info.Type, wrapped, outside, position, context, cancellationToken); return; } @@ -89,9 +126,16 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC // A parameter of a wrapped type ASP.NET does not describe gets the same placeholder: it stays. if (!DescribesParameterAsItself(parameterWrapped)) return; + // MVC hands a value object bound from the body here too, as the string it would parse. There + // ASP.NET's exporter admits null in the schema of the Nullable<T> of a wrapped value type, inside + // the null-or ASP.NET puts around a nullable body, and outside the body among its values alone. + var position = Nullable.GetUnderlyingType(valueObject) is null || !parameterWrapped.IsValueType ? NullablePosition.None + : parameter.Source == BindingSource.Body ? NullablePosition.Root + : NullablePosition.ParameterOutsideTheBody; + // ASP.NET's placeholder for any TryParse-bound parameter; the wrapped type says what it is. schema.Type = null; - await DescribeAsync(schema, valueObject, parameterWrapped, parameter, admitsNull: false, context, cancellationToken); + await DescribeAsync(schema, valueObject, parameterWrapped, parameter, position, context, cancellationToken); return; } @@ -112,17 +156,38 @@ public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerC /// <remarks> /// Asking ASP.NET for the <see cref="Nullable{T}"/> itself returned the bare value object where it is a /// component: the component is shared with every non-nullable use, so it cannot admit null, and ASP.NET - /// adds the null only for a property, a body or a response, never for an element it did not build. + /// adds the null only for a property, a body or a response, never for an element it did not build. The + /// same holds for the component of the type an inlined value object wraps (an enum's), whose content is + /// then the element's, with the null put in. /// </remarks> private static async Task<IOpenApiSchema> ElementSchemaAsync(Type element, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { + // An element of a parameter outside the body binds through TryParse, as the parameter does: asked for as the + // parameter's element, it is described as such and written inline where that is not its JSON (decision 106). + if (ParameterOutsideTheBody(context) is { } parameter && WrapsDescribed(element, context.JsonTypeInfo.Options)) + return await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, element, Detached(parameter), cancellationToken); + var valueObject = Nullable.GetUnderlyingType(element); + if (valueObject is null) return await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, element, parameterDescription: null, cancellationToken); + + var options = context.JsonTypeInfo.Options; + if (SchemaReferenceId(options.GetTypeInfo(element), context) is null) + { + // Inlined over an inlined wrapped type, the nullable value object's own schema admits null (above). + if (SchemaReferenceId(options.GetTypeInfo(ValueObjectTypes.WrappedType(valueObject)!), context) is null) + return await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, element, parameterDescription: null, cancellationToken); - // Inlined, the nullable value object's own schema admits null (above). - if (valueObject is null || SchemaReferenceId(context.JsonTypeInfo.Options.GetTypeInfo(element), context) is null) - return await context.GetOrCreateSchemaAsync(element, parameterDescription: null, cancellationToken); + // Over the wrapped type's component, the content admits null as it does over an inlined wrapped type: a + // null-or around it is what OpenAPI 2.0 writes as a bare x-nullable, without the type. + var content = new OpenApiSchema(); + content.FillFrom(await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, valueObject, parameterDescription: null, cancellationToken)); + AdmitNull(content, inType: true); + NullableElements.AddOrUpdate(content, element); - var schema = await context.GetOrCreateSchemaAsync(valueObject, parameterDescription: null, cancellationToken); + return content; + } + + var schema = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, valueObject, parameterDescription: null, cancellationToken); var valueObjectOrNull = new OpenApiSchema { OneOf = [new OpenApiSchema { Type = JsonSchemaType.Null }, schema] }; NullableElements.AddOrUpdate(valueObjectOrNull, element); @@ -130,6 +195,17 @@ private static async Task<IOpenApiSchema> ElementSchemaAsync(Type element, OpenA return valueObjectOrNull; } + /// <summary> + /// Puts the null into an inlined schema: into its type, as ASP.NET's exporter writes a nullable property of a + /// number, a string or a Guid, unless <paramref name="inType"/> says not, and among its values, since an enum + /// lists every value a validator accepts. A new list: the old one may be the wrapped type's. + /// </summary> + private static void AdmitNull(OpenApiSchema schema, bool inType) + { + if (inType && schema.Type is { } type) schema.Type = type | JsonSchemaType.Null; + if (schema.Enum is { Count: > 0 } values && !values.Contains(null!)) schema.Enum = [.. values, null!]; + } + /// <summary> /// The component a type gets in this document, or <see langword="null"/> where the host inlines it: the /// document's own <see cref="OpenApiOptions.CreateSchemaReferenceId"/>, which ASP.NET asks too. @@ -164,6 +240,100 @@ private static bool DescribesParameterAsItself(Type type) || type == typeof(Uri); } + /// <summary> + /// Whether the schema is a parameter's own, bound from anywhere but the body (the route, the query string, + /// a header, a form field), rather than a body's, a response's, or an element's or property's below one. + /// </summary> + private static bool IsParameterOutsideTheBody(OpenApiSchemaTransformerContext context) => + ParameterOutsideTheBody(context) is { } parameter && IsParameterItself(parameter, context.JsonTypeInfo.Type); + + /// <summary> + /// The parameter bound from anywhere but the body whose schema this is, its own or an element's or a wrapped type's + /// below it; <see langword="null"/> in a JSON position: a body, a response, or a property below either. + /// </summary> + private static ApiParameterDescription? ParameterOutsideTheBody(OpenApiSchemaTransformerContext context) => + context is { JsonPropertyInfo: null, ParameterDescription: { } parameter } && parameter.Source != BindingSource.Body ? parameter : null; + + /// <summary> + /// Whether the value object wraps a type the options describe, itself or through the value objects it wraps: as + /// a parameter it is then described otherwise than in JSON. + /// </summary> + private static bool WrapsDescribed(Type valueObject, System.Text.Json.JsonSerializerOptions options) + { + var seen = new HashSet<Type>(); + for (var type = Nullable.GetUnderlyingType(valueObject) ?? valueObject; seen.Add(type) && ValueObjectTypes.WrappedType(type) is { } wrapped; type = wrapped) + { + if (DescribedSchemaTransformer.Description(wrapped, options, property: null) is not null) return true; + } + + return false; + } + + /// <summary> + /// Writes the schema where it is used rather than as the component ASP.NET would make of it. ASP.NET reads the + /// component's name from the schema's metadata, and puts a parameter's default there to write it on the reference + /// instead of the shared schema, each under a key of its own (<c>OpenApiConstants.SchemaId</c> and + /// <c>RefDefaultAnnotation</c>, internal; measured with Microsoft.AspNetCore.OpenApi 10.0). Without the name it writes + /// the schema inline, so the default goes back on the schema. + /// </summary> + private static void Inline(OpenApiSchema schema) + { + if (schema.Metadata is not { } metadata || !metadata.Remove("x-schema-id")) return; + + if (metadata.TryGetValue("x-ref-default", out var value) && metadata.Remove("x-ref-default") && value is JsonNode defaultValue) schema.Default ??= defaultValue; + } + + /// <summary> + /// Has ASP.NET write the property as null or the reference to the value object's component, as it writes a property it + /// reads as nullable itself. It reads the mark from the schema's metadata, under a key of its own + /// (<c>OpenApiConstants.NullableProperty</c>, internal, set by <c>ApplyNullabilityContextInfo</c> and read by + /// <c>ResolveReferenceForSchema</c>; measured with Microsoft.AspNetCore.OpenApi 10.0.12). OpenAPI 2.0 writes it as its + /// <c>allOf: [{x-nullable: true}]</c>, without the reference, as for every nullable property of a component (KB-026). + /// </summary> + private static void MarkNullableProperty(OpenApiSchema schema) => + (schema.Metadata ??= new Dictionary<string, object>())["x-is-nullable-property"] = true; + + /// <summary> + /// Whether a schema of <paramref name="type"/> visited with <paramref name="parameter"/> is the parameter's own, + /// rather than an element's below it (<c>decimal</c> under <c>decimal[]</c>) or the type a value object wraps: + /// ASP.NET hands each of those to the transformers with the parameter's description too. + /// </summary> + internal static bool IsParameterItself(ApiParameterDescription parameter, Type type) => + (Nullable.GetUnderlyingType(parameter.Type) ?? parameter.Type) == (Nullable.GetUnderlyingType(type) ?? type); + + /// <summary> + /// The description a schema below a parameter is asked for with, an element's or the type a value object wraps: + /// where the parameter binds from, and its type, so the schema reads as below it (<see cref="IsParameterItself"/>). + /// Not the parameter's own: ASP.NET applies a parameter's default and its validation attributes to the schema it is + /// asked for, and they are the parameter's, already on its schema. Asked for again, they would be the array's on each + /// element, and a value object's default would be written as the wrapped type, which throws. + /// </summary> + internal static ApiParameterDescription Detached(ApiParameterDescription parameter) => + new() { Name = parameter.Name, Source = parameter.Source, Type = parameter.Type }; + + /// <summary>Where a schema of a value object stands, which decides where its null goes.</summary> + private enum NullablePosition + { + /// <summary>Not nullable, or a component, around whose reference ASP.NET puts the null itself.</summary> + None, + + /// <summary>A nullable property: ASP.NET's null-or-reference where the wrapped type is a component.</summary> + Property, + + /// <summary> + /// The <see cref="Nullable{T}"/> of a value type in a body, a response or a container's element: inside the + /// null-or ASP.NET puts around a nullable body or response, or the element's own. + /// </summary> + Root, + + /// <summary> + /// The <see cref="Nullable{T}"/> of a value type as a route, query, header or form parameter, which ASP.NET + /// describes without the null in its type (a query <c>int?</c> is an integer) and with it among its values + /// (a query <c>DayOfWeek?</c> written by name lists null). + /// </summary> + ParameterOutsideTheBody, + } + private static Type? ParameterValueObject(ApiParameterDescription parameter) => ValueObjectTypes.IsValueObject(parameter.Type) ? parameter.Type : parameter.ModelMetadata?.ModelType is { } model && ValueObjectTypes.IsValueObject(model) ? model @@ -174,7 +344,7 @@ private static async Task DescribeAsync( Type valueObject, Type wrapped, ApiParameterDescription? parameter, - bool admitsNull, + NullablePosition position, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { @@ -191,14 +361,23 @@ private static async Task DescribeAsync( Describing.Value = describing.Push(self); try { - var wrappedSchema = await context.GetOrCreateSchemaAsync(wrapped, parameter, cancellationToken); + var wrappedSchema = await PolymorphicBaseBranch.GetOrCreateSchemaAsync(context, wrapped, parameter, cancellationToken); - if (!admitsNull) + if (position == NullablePosition.None) { schema.FillFrom(wrappedSchema); } else if (SchemaReferenceId(context.JsonTypeInfo.Options.GetTypeInfo(wrapped), context) is not null) { + if (position != NullablePosition.Property) + { + // The wrapped type's component holds no null, and ASP.NET puts none beside it but a property's: + // a body's or a response's null-or holds it as it is, a parameter is the reference alone, and an + // element gets its null from ElementSchemaAsync. So its content goes here as it is. + schema.FillFrom(wrappedSchema); + return; + } + // A nullable property of the wrapped type is ASP.NET's null-or-reference to the wrapped type's // component (an enum's): the same, with the component's content in place of the reference. // Visited again, it is rebuilt the same way; the wrapped keywords never reach its top level. @@ -208,13 +387,11 @@ private static async Task DescribeAsync( } else { - // An inlined wrapped type (a number, a string, a Guid) admits null in its type, as ASP.NET's - // exporter writes a nullable property of it; an inlined enum or closed set among its values - // too, since an enum lists every value a validator accepts. A new list: the wrapped one is - // the wrapped type's. + // An inlined wrapped type admits null in its type and among its values. Outside the body ASP.NET + // takes the null out of the type and leaves it among the values (a query DayOfWeek? written by + // name), so it goes there alone. schema.FillFrom(wrappedSchema); - if (schema.Type is { } type) schema.Type = type | JsonSchemaType.Null; - if (schema.Enum is { Count: > 0 } values && !values.Contains(null!)) schema.Enum = [.. values, null!]; + AdmitNull(schema, inType: position != NullablePosition.ParameterOutsideTheBody); } } catch (NotSupportedException unsupported) diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs index 5f1499f8..09a9e8cf 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs @@ -1,6 +1,8 @@ using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using CodoMetis.TypeKit.CompilerServices; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -65,7 +67,10 @@ public IModel ProcessModelFinalized(IModel model) } } -/// <summary>Finds a comparison, an ordering, or a minimum or maximum of a value object with a hand-written ordering, and throws.</summary> +/// <summary> +/// Finds a comparison, an ordering, or a minimum or maximum of a value object with a hand-written ordering, and throws: +/// in C#, or through the database's own <c>EF.Functions.Greatest</c>, <c>Least</c> and row-value comparisons. +/// </summary> internal static class HandWrittenOrdering { /// <summary>In a query's LINQ tree, which EF has already stripped of what it evaluates in memory.</summary> @@ -113,10 +118,41 @@ protected override Expression VisitMethodCall(MethodCallExpression node) // value object's CompareTo, while EF drops the conversion and orders by the column. if (KeySelector(node) is { } key) Refuse(Unconverted(key.Body).Type, $"ordered by {method.Name}"); } + else if (IsDbFunction(method) && !RunsInMemory(node)) + { + // EF.Functions.Greatest and Least (relational: GREATEST, LEAST, SQLite's max and min) pick by the + // column's order, as Max does. + if (method.Name is "Greatest" or "Least" && method.IsGenericMethod) + Refuse(method.GetGenericArguments()[0], $"compared by {method.Name}"); + + // A row-value comparison (Npgsql's EF.Functions.GreaterThan((a, b), (x, y)) and its siblings) compares + // each element by its column's order; its tuples arrive boxed to ITuple. + if (method.Name is "GreaterThan" or "GreaterThanOrEqual" or "LessThan" or "LessThanOrEqual") + foreach (var argument in node.Arguments) + if (argument.Type == typeof(ITuple)) + foreach (var element in TupleElements(Unconverted(argument).Type)) + Refuse(element, $"compared in a row value by {method.Name}"); + } return base.VisitMethodCall(node); } + /// <summary>A function of <c>EF.Functions</c>: an extension of <see cref="DbFunctions"/>, whichever provider declares it.</summary> + private static bool IsDbFunction(MethodInfo method) => + method.IsStatic && method.GetParameters() is [{ ParameterType: var functions }, ..] && functions == typeof(DbFunctions); + + /// <summary>The element types of a tuple type, a long tuple's rest included; none for a type that is no generic tuple.</summary> + private static IEnumerable<Type> TupleElements(Type tuple) + { + if (!tuple.IsGenericType || !typeof(ITuple).IsAssignableFrom(tuple)) yield break; + + foreach (var element in tuple.GetGenericArguments()) + { + yield return element; + foreach (var nested in TupleElements(element)) yield return nested; + } + } + /// <summary>The operator's key or value selector, quoted for <c>Queryable</c>, a lambda for <c>Enumerable</c>.</summary> private static LambdaExpression? KeySelector(MethodCallExpression node) => node.Arguments.Count > 1 ? StripQuotes(node.Arguments[1]) as LambdaExpression : null; diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md b/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md index 7b51a56a..1e56ef6f 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md @@ -104,8 +104,9 @@ column's type, not by the underlying type: A key over a `Guid` is yours to assign, `Id = OrderId.New()` (a version 7 Guid, which sorts by creation time), and EF inserts it as given. To have EF generate it instead, configure -`ValueGeneratedOnAdd()` on the property. EF then also treats an entity whose key is already set, -reached through a navigation, as an existing one. +`ValueGeneratedOnAdd()` on the property. It's then generated as a plain `Guid` key is: a version 7 +Guid on PostgreSQL, one that's sequential in SQL Server's order on SQL Server. EF then also treats +an entity whose key is already set, reached through a navigation, as an existing one. The root of an inheritance hierarchy follows the same rules under TPH and TPT. Under TPC, as for an `int` key, the key is generated only where the provider numbers the whole hierarchy from a sequence @@ -183,17 +184,27 @@ instead, which works everywhere and may add a cast: converter stores it: the one `UseTypeKit()` applies, or `ValueObjectConverter<,>` named in `HasConversion`. A property your application converts itself (`HasConversion(c => "C:" + c.Value, ...)`, a converter type, a conversion in - `ConfigureConventions`) doesn't hold `.Value`. So a query that reads `.Value` there fails with EF's - "could not be translated", rather than comparing against what the column really holds. Comparing + `ConfigureConventions`) doesn't hold `.Value`. So a filter, a sort, a grouping or an aggregate that + reads `.Value` there fails with EF's "could not be translated", rather than comparing against what + the column really holds. Selecting `.Value` as the query's last step still works, `Distinct` + included: EF reads the value object through your converter and takes `.Value` in memory. Comparing the value object itself (`o.Code == code`) goes through that converter and still works. - **Ordering in SQL follows the database's collation** for the column. The generated comparison in memory is ordinal for a string, as it is for any string column. +- **A null passes no `<`, `<=`, `>` or `>=` in the database.** SQL leaves out a row whose column is + NULL, as it does for an `int?` column. A value object that's a record class puts null first in + memory, so there `null < rank` is true. For a nullable `Rank` record class, `o.Rank < rank` keeps + an order without a rank in a list, and leaves it out of the query. Write + `o.Rank == null || o.Rank < rank` to keep it in the query too. A struct value object answers the + same both ways. - **Your own `CompareTo(TSelf)` can't run in the database.** A value object that declares one orders in memory by that method. The column orders by the underlying value, which could be the opposite of a descending `CompareTo`. So these queries throw an `InvalidOperationException` naming the value object: - a comparison (`<`, `>`, `<=`, `>=`, `CompareTo`); - an ordering (`OrderBy`, `ThenBy`, their descending forms, `Order`); + - the database's own orderings: `EF.Functions.Greatest` and `Least`, and a row-value comparison + such as Npgsql's `EF.Functions.GreaterThan((o.Priority, o.Id), (priority, id))`; - `Min` or `Max`. Compare or order by `.Value` where the underlying value's order is what you mean diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs index 220c82e6..f0ec2c3a 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs @@ -6,7 +6,9 @@ namespace CodoMetis.TypeKit.EntityFrameworkCore; /// <summary> /// Adds TypeKit's conventions to every model <c>UseTypeKit()</c> builds: the key generation of /// <see cref="ValueObjectKeyValueGenerationConvention"/> and <see cref="ValueObjectTpcKeyValueGenerationConvention"/>, -/// and the refusal of <see cref="HandWrittenOrderingQueryFilterConvention"/>. +/// the rewrite of <see cref="ValueThroughInterfaceQueryFilterConvention"/>, +/// the generator of <see cref="ValueObjectValueGeneratorConvention"/>, and the refusal of +/// <see cref="HandWrittenOrderingQueryFilterConvention"/>. /// </summary> internal sealed class TypeKitConventionSetPlugin : IConventionSetPlugin { @@ -22,6 +24,12 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet) // Last of all: a TPC key is generated only where the provider gave it a sequence. finalizing.Add(new ValueObjectTpcKeyValueGenerationConvention()); + // A query filter's .Value read through the value-object interface, before the model is final. + finalizing.Add(new ValueThroughInterfaceQueryFilterConvention()); + + // Last, once whether a property is generated is final: the provider's generator for the wrapped type. + finalizing.Add(new ValueObjectValueGeneratorConvention()); + // On the finished model, whose query filters are final. conventionSet.ModelFinalizedConventions.Add(new HandWrittenOrderingQueryFilterConvention()); diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitDbContextOptionsBuilderExtensions.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitDbContextOptionsBuilderExtensions.cs index a8bf79c1..99ad3b51 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitDbContextOptionsBuilderExtensions.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitDbContextOptionsBuilderExtensions.cs @@ -24,7 +24,7 @@ public static class TypeKitDbContextOptionsBuilderExtensions /// (<see cref="CodoMetis.TypeKit.ValueObjects.IValueObjectMaterializer{TSelf,T}"/>). /// </para> /// <para> - /// It only adds plugins, conventions and an interceptor, so it replaces nothing of EF's or of + /// It only adds plugins, conventions and interceptors, so it replaces nothing of EF's or of /// another library's, and the order against the database provider does not matter. /// </para> /// </remarks> diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitServiceCollectionExtensions.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitServiceCollectionExtensions.cs index dd659b61..d3d9e5dc 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitServiceCollectionExtensions.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitServiceCollectionExtensions.cs @@ -22,14 +22,15 @@ public static IServiceCollection AddEntityFrameworkTypeKit(this IServiceCollecti { ArgumentNullException.ThrowIfNull(serviceCollection); - // Plugins and an interceptor, which EF collects from every registration: nothing of EF's or of + // Plugins and interceptors, which EF collects from every registration: nothing of EF's or of // another library's is replaced, and an application's own interceptors still run. new EntityFrameworkRelationalServicesBuilder(serviceCollection) .TryAdd<IRelationalTypeMappingSourcePlugin, ValueObjectTypeMappingSourcePlugin>() .TryAdd<IMemberTranslatorPlugin, ValueObjectMemberTranslatorPlugin>() .TryAdd<IMethodCallTranslatorPlugin, ValueObjectMethodCallTranslatorPlugin>() .TryAdd<IConventionSetPlugin, TypeKitConventionSetPlugin>() - .TryAdd<IInterceptor, HandWrittenOrderingInterceptor>(); + .TryAdd<IInterceptor, HandWrittenOrderingInterceptor>() + .TryAdd<IInterceptor, ValueThroughInterfaceInterceptor>(); return serviceCollection; } diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs index 266a637d..e59f0638 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs @@ -1,3 +1,4 @@ +using CodoMetis.TypeKit.CompilerServices; using System.ComponentModel; using System.Linq.Expressions; using CodoMetis.TypeKit.ValueObjects; @@ -29,8 +30,12 @@ public sealed class ValueObjectConverter<TValueObject, T> : ValueConverter<TValu // (dotnet ef dbcontext optimize, which Native AOT requires) writes these expressions out as C# in // the application's own assembly, where an internal helper does not compile; and an expression // tree cannot call the static abstract Materialize itself (CS8927). + // CMTK0004 refuses Materialize everywhere but here: this converter's read path is the one caller + // the materializer exists for (AGENTS.md, "The materializer skips validation"). internal static readonly Expression<Func<TValueObject, T>> ToProvider = instance => ProviderValue(instance); +#pragma warning disable CMTK0004 internal static readonly Expression<Func<T, TValueObject>> FromProvider = value => Materialize(value); +#pragma warning restore CMTK0004 /// <summary>Creates the converter.</summary> public ValueObjectConverter() @@ -51,7 +56,12 @@ public ValueObjectConverter(ConverterMappingHints? mappingHints) /// <remarks>Public for the code EF's compiled model generates; nothing else needs to call it.</remarks> /// <param name="instance">The value object.</param> /// <returns>The value it wraps.</returns> + /// <remarks> + /// A query that reads <c>.Value</c> through <see cref="IValueObject{TSelf,T}"/>, as a generic method + /// does, is rewritten to call this, which is translated as the column it reads. + /// </remarks> [EditorBrowsable(EditorBrowsableState.Never)] + [TranslatedAsWrappedValue] public static T ProviderValue(TValueObject instance) => instance.Value; /// <summary> @@ -65,5 +75,7 @@ public ValueObjectConverter(ConverterMappingHints? mappingHints) /// <param name="value">A value the application itself stored.</param> /// <returns>The value object.</returns> [EditorBrowsable(EditorBrowsableState.Never)] +#pragma warning disable CMTK0004 // the read path, as above public static TValueObject Materialize(T value) => TValueObject.Materialize(value); +#pragma warning restore CMTK0004 } diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs index 934f8841..c64d73ee 100644 --- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs @@ -115,3 +115,40 @@ public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConven } } } + +/// <summary> +/// A value object over a <see cref="Guid"/> or a <see cref="string"/> that EF generates on add gets its values +/// from the generator the provider gives a property of that type (<see cref="WrappedTypeValueGeneratorFactory"/>). +/// </summary> +/// <remarks> +/// <para> +/// PostgreSQL's and SQL Server's providers choose a generated property's generator by its CLR type +/// (<c>FindForType</c> of <c>NpgsqlValueGeneratorSelector</c> and <c>SqlServerValueGeneratorSelector</c>), and +/// for any other type fall back to EF's, through the column's converter. So <c>ValueGeneratedOnAdd()</c> on a key +/// over a <see cref="Guid"/> gave the value object a random version 4 Guid where the plain key gets a version 7 +/// one on PostgreSQL and one sequential in SQL Server's order, and a key over a <see cref="string"/> a random +/// Guid's text where PostgreSQL gives the plain key a version 7 one. The other wrapped types' generators are +/// chosen through the converter's provider type, a sequence's (HiLo) included, as for the plain type. +/// </para> +/// <para> +/// A property with a default or computed SQL value is left alone: both keys then get EF's temporary value, +/// which the database replaces. It sets the convention's value only, so a generator or a factory configured +/// on the property still wins. +/// </para> +/// </remarks> +internal sealed class ValueObjectValueGeneratorConvention : IModelFinalizingConvention +{ + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + foreach (var property in entityType.GetDeclaredProperties()) + { + if (property.ValueGenerated == ValueGenerated.Never || property.GetValueGeneratorFactory() is not null) continue; + if (property.GetDefaultValueSql() is not null || property.GetComputedColumnSql() is not null) continue; + + if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(property.ClrType) ?? property.ClrType) is { WrappedType: var wrapped } + && WrappedTypeValueGenerator.Generates(wrapped)) + property.Builder.HasValueGeneratorFactory(typeof(WrappedTypeValueGeneratorFactory)); + } + } +} diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueThroughInterface.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueThroughInterface.cs new file mode 100644 index 00000000..865cab7e --- /dev/null +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueThroughInterface.cs @@ -0,0 +1,112 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using CodoMetis.TypeKit.CompilerServices; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; + +namespace CodoMetis.TypeKit.EntityFrameworkCore; + +/// <summary> +/// Rewrites <c>.Value</c> read through the value-object interface, as a generic method or base class reads it, to +/// the unwrap the translators know (<see cref="ValueThroughInterface"/>), before EF translates the query. +/// </summary> +/// <remarks> +/// <c>QueryCompilationStarting</c> sees the LINQ tree once per query shape, after EF has evaluated what reads no +/// database, so what is left to rewrite reads a column. It does not see the query filters EF adds later, which +/// <see cref="ValueThroughInterfaceQueryFilterConvention"/> rewrites. +/// </remarks> +internal sealed class ValueThroughInterfaceInterceptor : IQueryExpressionInterceptor +{ + public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData) => + ValueThroughInterface.Rewrite(queryExpression); +} + +/// <summary>Rewrites the same in the model's query filters, named ones included, when the model is built.</summary> +/// <remarks> +/// EF adds an entity type's query filters to a query after <c>QueryCompilationStarting</c>, so +/// <see cref="ValueThroughInterfaceInterceptor"/> never sees them. A compiled model writes the rewritten filter out, +/// which calls a public method. +/// </remarks> +internal sealed class ValueThroughInterfaceQueryFilterConvention : IModelFinalizingConvention +{ + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + foreach (var filter in entityType.GetDeclaredQueryFilters().ToList()) + { + if (filter.Expression is not { } expression || ValueThroughInterface.Rewrite(expression) is not LambdaExpression rewritten || rewritten == expression) + continue; + + if (filter.IsAnonymous) entityType.SetQueryFilter(rewritten); + else entityType.SetQueryFilter(filter.Key, rewritten); + } + } +} + +/// <summary> +/// <c>.Value</c> read through <see cref="IValueObject{TSelf,T}"/>, rewritten to +/// <see cref="ValueObjectConverter{TValueObject,T}.ProviderValue"/>, which the method translator turns into the +/// column, as it does <c>GetValue()</c>. +/// </summary> +/// <remarks> +/// <para> +/// In a generic repository or base class, <c>where T : IValueObject<T, string></c>, C# writes +/// <c>r.Name.Value</c> as <c>((IValueObject<Name, string>)r.Name).Value</c>: the interface's member, on a +/// conversion EF cannot translate, so the query failed with "could not be translated" before any translator saw +/// <c>.Value</c>, for a record class and a record struct alike (measured with EF Core 10.0.12 on SQLite). +/// </para> +/// <para> +/// Not to the value object's own <c>Value</c> property: finding it from a <see cref="Type"/> is a member lookup the +/// trimmer may break (IL2075). The unwrap is reached through <see cref="GeneratedValueObjectAttribute.Accept{TResult}"/> +/// with the value object as a type argument, from an expression whose method the compiler names, so nothing is +/// looked up by name and nothing is made with <c>MakeGenericType</c>. It is public because a compiled model writes +/// a query filter out as C# in the application's assembly. +/// </para> +/// <para> +/// Only the conversion of a value object to its own interface is rewritten: the interface's type argument is the +/// operand's type, which <see cref="ValueObjectTypes"/> knows as a value object wrapping what <c>Value</c> returns. +/// Anything else, an operand already typed as the interface for one, is left to EF. +/// </para> +/// </remarks> +internal static class ValueThroughInterface +{ + private static readonly ConcurrentDictionary<Type, MethodInfo> Unwraps = new(); + + public static Expression Rewrite(Expression expression) => Rewriter.Instance.Visit(expression); + + /// <summary><see cref="ValueObjectConverter{TValueObject,T}.ProviderValue"/> of <paramref name="valueObject"/>.</summary> + private static MethodInfo Unwrap(GeneratedValueObjectAttribute valueObject) => + Unwraps.GetOrAdd(valueObject.ValueObjectType, static (_, description) => description.Accept(UnwrapOf.Instance), valueObject); + + private sealed class Rewriter : ExpressionVisitor + { + public static readonly Rewriter Instance = new(); + + protected override Expression VisitMember(MemberExpression node) + { + if (node is { Member: PropertyInfo { Name: nameof(IValueObject<,>.Value) } property, Expression: UnaryExpression { NodeType: ExpressionType.Convert, Method: null } conversion } + && property.DeclaringType is { IsGenericType: true } contract + && contract.GetGenericTypeDefinition() == typeof(IValueObject<,>) + && conversion.Type == contract + && contract.GetGenericArguments()[0] == conversion.Operand.Type + && ValueObjectTypes.Describe(conversion.Operand.Type) is { } valueObject + && valueObject.WrappedType == property.PropertyType) + return Expression.Call(Unwrap(valueObject), Visit(conversion.Operand)); + + return base.VisitMember(node); + } + } + + private sealed class UnwrapOf : IValueObjectVisitor<MethodInfo> + { + public static readonly UnwrapOf Instance = new(); + + public MethodInfo Visit<TValueObject, T>() + where TValueObject : IValueObject<TValueObject, T>, IValueObjectMaterializer<TValueObject, T> + where T : notnull => + ((MethodCallExpression)ValueObjectConverter<TValueObject, T>.ToProvider.Body).Method; + } +} diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/WrappedTypeValueGeneratorFactory.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/WrappedTypeValueGeneratorFactory.cs new file mode 100644 index 00000000..c97f3bef --- /dev/null +++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/WrappedTypeValueGeneratorFactory.cs @@ -0,0 +1,98 @@ +using System.ComponentModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.ValueGeneration; + +namespace CodoMetis.TypeKit.EntityFrameworkCore; + +/// <summary> +/// Generates a value object's value with the generator the provider gives a property of the type it wraps, +/// configured the same way. +/// </summary> +/// <remarks> +/// <para> +/// <c>UseTypeKit()</c> sets it on a value object over a <see cref="Guid"/> or a <see cref="string"/> that +/// EF generates on add, so it only needs to be named by the code EF's compiled model generates. PostgreSQL's +/// and SQL Server's providers choose a key's generator by the property's CLR type, not by its column: a +/// <see cref="Guid"/> key gets a version 7 Guid on PostgreSQL and a Guid sequential in SQL Server's order, +/// while a value object over one got EF's random Guid on both (measured with Npgsql 10.0.3 and EF Core +/// 10.0.12). +/// </para> +/// <para> +/// A type, not a delegate, because a compiled model can only name a factory type. It has no services, so +/// the generator asks the provider at the first value, through the context of the entity being added. +/// </para> +/// </remarks> +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class WrappedTypeValueGeneratorFactory : ValueGeneratorFactory +{ + /// <summary>The generator of <paramref name="property"/>, a value object's.</summary> + /// <param name="property">A value-object property generated on add.</param> + /// <param name="typeBase">The type it is generated for.</param> + /// <returns>A generator that answers the provider's values for the wrapped type, as the value object.</returns> + /// <exception cref="InvalidOperationException">The property is no value object over a <see cref="Guid"/> or a <see cref="string"/>.</exception> + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) + { + ArgumentNullException.ThrowIfNull(property); + + return ValueObjectTypes.Describe(Nullable.GetUnderlyingType(property.ClrType) ?? property.ClrType) is { } valueObject + && WrappedTypeValueGenerator.Generates(valueObject.WrappedType) + ? new WrappedTypeValueGenerator(ValueObjectMapping.For(valueObject).Converter, valueObject.WrappedType) + : throw new InvalidOperationException( + $"{property.DeclaringType.DisplayName()}.{property.Name} is no value object over a Guid or a string, so it has no generator of its wrapped type's."); + } +} + +/// <summary> +/// The provider's generator for a property of the wrapped type, its values converted to the value object. +/// The provider is asked once, at the first value, about a key of the wrapped type in a model of its own, +/// built with the context's conventions. +/// </summary> +internal sealed class WrappedTypeValueGenerator(ValueConverter converter, Type wrapped) : ValueGenerator +{ + private ValueGenerator? _provider; + + /// <summary>The wrapped types whose generator the providers choose by the property's CLR type.</summary> + public static bool Generates(Type wrapped) => wrapped == typeof(Guid) || wrapped == typeof(string); + + /// <remarks>EF reads it after a value is generated (<c>ValueGenerationManager</c> in EF Core 10).</remarks> + public override bool GeneratesTemporaryValues => + _provider?.GeneratesTemporaryValues + ?? throw new InvalidOperationException("Whether a value object's generated value is temporary is known only once one is generated."); + + /// <remarks>None of EF's or the providers' generators of a Guid or a string makes stable values.</remarks> + public override bool GeneratesStableValues => false; + + protected override object? NextValue(EntityEntry entry) => + converter.ConvertFromProvider((_provider ??= ProviderGenerator(entry.Context, wrapped)).Next(entry)); + + private static ValueGenerator ProviderGenerator(DbContext context, Type wrapped) + { + var modelBuilder = new ModelBuilder(context.GetService<IConventionSetBuilder>().CreateConventionSet(), context.GetService<ModelDependencies>()); + if (wrapped == typeof(Guid)) + modelBuilder.Entity<GuidKey>().Property(key => key.Id).ValueGeneratedOnAdd(); + else + modelBuilder.Entity<StringKey>().Property(key => key.Id).ValueGeneratedOnAdd(); + + var model = context.GetService<IModelRuntimeInitializer>().Initialize(modelBuilder.FinalizeModel(), designTime: false, validationLogger: null); + var entityType = model.FindEntityType(wrapped == typeof(Guid) ? typeof(GuidKey) : typeof(StringKey))!; + + return context.GetService<IValueGeneratorSelector>().TrySelect(entityType.FindPrimaryKey()!.Properties[0], entityType, out var generator) && generator is not null + ? generator + : throw new InvalidOperationException($"The provider has no generator for a key of {wrapped.Name} generated on add."); + } + + private sealed class GuidKey + { + public Guid Id { get; set; } + } + + private sealed class StringKey + { + public string Id { get; set; } = ""; + } +} diff --git a/src/CodoMetis.TypeKit.Generators/README.md b/src/CodoMetis.TypeKit.Generators/README.md index 0af56139..7037b355 100644 --- a/src/CodoMetis.TypeKit.Generators/README.md +++ b/src/CodoMetis.TypeKit.Generators/README.md @@ -121,7 +121,8 @@ type of your own that does. | `TryFrom(T)`, `FromKnownGood(T)` | `IValidatedValue` | Both built from your `Create`. `FromKnownGood` throws with the caller's expression in the message, never the value. Declare either one yourself and it isn't generated (see "Write your own members" below). | | `Revalidate()` | `IValidatedValue` | Runs `Create` on the value an instance holds. It answers: which values read back without validation (a column, a stored document) would today's rules reject? Returns what `Create` returns, normalisation included. | | `[JsonConverter]` with a nested converter | every value object | Reads and writes the underlying value exactly as the serializer does, also as a dictionary key. A struct value object rejects a JSON `null` (see JSON below). | -| `IParsable`, `ISpanParsable`, `IUtf8SpanParsable` | when the underlying type can be parsed | A `Uri` parses relative or absolute, as JSON reads it. A NodaTime type parses through its type converter. | +| `IParsable` | when the underlying type can be parsed | A `Uri` parses relative or absolute, as JSON reads it. A NodaTime type parses through its type converter. | +| `ISpanParsable`, `IUtf8SpanParsable` | where the underlying type has them | Each one reads and rejects the same text as `Parse`. A `string` has `ISpanParsable` only. A `Uri`, an enum and a NodaTime type have neither. | | `IFormattable`, `ISpanFormattable`, `IUtf8SpanFormattable`, `ToString()` | every value object, unless you write `ToString()` | Invariant. A `ToString()` you write is kept, and then none of the formatting interfaces is generated, so interpolation and `string.Format` use yours too. | | `IEqualityOperators<TSelf, TSelf, bool>` | every value object | The generic-math form of `==`/`!=`, so a value object satisfies that constraint. | | `IComparable<TSelf>`, `IComparable`, `IComparisonOperators<TSelf, TSelf, bool>`, `<` `>` `<=` `>=` | when the underlying type is comparable | Strings compare ordinally, so ordering agrees with equality. | diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs index cb0cbae9..79a27700 100644 --- a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs +++ b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs @@ -220,7 +220,7 @@ public static IReadOnlyList<string> GeneratedMembersDeclaredByHand(INamedType ta found.AddRange(MembersNamed(target, $"{target.Name}TypeConverter")); generatedInterfaces.Add(TypeFactory.GetNamedType(typeof(IParsable<>))); - if (parse == ValueParseStrategy.SpanParsable) + if (ValueObjectParsableAspect.SupportsSpan(valueType)) { generatedInterfaces.Add(TypeFactory.GetNamedType(typeof(ISpanParsable<>))); diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.Templates.cs index 84acc052..93c0e436 100644 --- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.Templates.cs +++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.Templates.cs @@ -60,7 +60,7 @@ public static dynamic SpanParseTemplate(ReadOnlySpan<char> s, IFormatProvider? p { var tag = (ParsableImplementationArguments)meta.Tags.Source!; - return ExpressionFactory.Parse($"{meta.CompileTime(tag.FromText)}({meta.CompileTime(tag.InnerParse())})", tag.ValueObjectType, false).Value!; + return ExpressionFactory.Parse($"{meta.CompileTime(tag.FromText)}({meta.CompileTime(tag.InnerSpanParse())})", tag.ValueObjectType, false).Value!; } [Template] @@ -70,8 +70,8 @@ public static bool SpanTryParseTemplate(ReadOnlySpan<char> s, IFormatProvider? p result = meta.Default(tag.ValueObjectType); - // Only generated for the span-parsable strategy, which has a TryParse. - return (bool)ExpressionFactory.Parse(tag.InnerTryParse()!, TypeFactory.GetType(SpecialType.Boolean), false).Value!; + // Only generated over a span-parsable type, which has a TryParse. + return (bool)ExpressionFactory.Parse(tag.InnerSpanTryParse(), TypeFactory.GetType(SpecialType.Boolean), false).Value!; } [Template] diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs index c734b984..54a65edf 100644 --- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs @@ -61,7 +61,7 @@ public string InnerParse(string input = "s") { ValueParseStrategy.String => input, ValueParseStrategy.Parsable => $"({at}.TryParse<{valueType}>({input}, {Provider}, out var __parsed) ? __parsed : {unreadable})", - ValueParseStrategy.SpanParsable => $"({at}.TryParseSpan<{valueType}>({input}, {Provider}, out var __parsed) ? __parsed : {unreadable})", + ValueParseStrategy.SpanParsable => InnerSpanParse(input), // By name, as Enum.Parse does, but with the exception IParsable.Parse documents. ValueParseStrategy.Enum => $"(global::System.Enum.TryParse<{valueType}>({input}, out var __parsed) ? __parsed : {unreadable})", // Relative or absolute, as the serializer reads a Uri. The constructor took "/orders/7" for @@ -89,7 +89,7 @@ public string InnerParse(string input = "s") var parsed = Strategy switch { ValueParseStrategy.Parsable => $"{at}.TryParse<{valueType}>({input}, {Provider}, out var innerValue)", - ValueParseStrategy.SpanParsable => $"{at}.TryParseSpan<{valueType}>({input}, {Provider}, out var innerValue)", + ValueParseStrategy.SpanParsable => SpanTryParse(input, "innerValue"), // Enum.TryParse answers false for null, an unknown name and an empty string. ValueParseStrategy.Enum => $"global::System.Enum.TryParse<{valueType}>({input}, out var innerValue)", ValueParseStrategy.Uri => $"global::System.Uri.TryCreate({input}, global::System.UriKind.RelativeOrAbsolute, out var innerValue)", @@ -98,6 +98,22 @@ public string InnerParse(string input = "s") return parsed is null ? null : $"({parsed} && {TryFromText}(innerValue, out result))"; } + + /// <summary> + /// C# that turns the characters <c>s</c> into the wrapped type through its own + /// <c>ISpanParsable.TryParse</c>, or throws <c>GeneratedParsing.Unreadable</c>, as + /// <see cref="InnerParse"/> does for text. The span <c>Parse</c> of every value object over a + /// span-parsable type, whatever its strategy for text: a <see cref="string"/>'s parses its text as it + /// is, and its characters through <c>string</c>'s own span parsing. + /// </summary> + public string InnerSpanParse(string input = "s") => + $"({SpanTryParse(input, "__parsed")} ? __parsed : throw global::CodoMetis.TypeKit.CompilerServices.GeneratedParsing.Unreadable<{ValueObjectTypes.SourceName(ValueObjectType)}, {ValueObjectTypes.SourceName(ValueType)}>())"; + + /// <summary>C# for the span <c>TryParse</c>: the wrapped type's own, then <c>__TryFromText</c>, so nothing throws.</summary> + public string InnerSpanTryParse(string input = "s") => $"({SpanTryParse(input, "innerValue")} && {TryFromText}(innerValue, out result))"; + + private string SpanTryParse(string input, string parsed) => + $"global::CodoMetis.TypeKit.CompilerServices.GeneratedParsing.TryParseSpan<{ValueObjectTypes.SourceName(ValueType)}>({input}, {Provider}, out var {parsed})"; } /// <summary> @@ -126,6 +142,7 @@ public override void BuildAspect(IAspectBuilder<INamedType> builder) return; } + var supportsSpan = SupportsSpan(valueType); var supportsUtf8 = SupportsUtf8(valueType); builder.Tags = new ParsableImplementationArguments @@ -142,7 +159,9 @@ public override void BuildAspect(IAspectBuilder<INamedType> builder) IntroduceParse(builder, nameof(ParseTemplate), nameof(IParsable<>.Parse)); IntroduceTryParse(builder, nameof(TryParseTemplate), nameof(IParsable<>.TryParse)); - if (strategy == ValueParseStrategy.SpanParsable) + // By the wrapped type, not the strategy: the string strategy, picked first, dropped string's own + // ISpanParsable (found by the prober, claim C11, 2026-10-17). + if (supportsSpan) { builder.ImplementInterface(typeof(ISpanParsable<>).ToNamedType().MakeGenericInstance(builder.Target), OverrideStrategy.Ignore); IntroduceParse(builder, nameof(SpanParseTemplate), nameof(ISpanParsable<>.Parse)); @@ -183,6 +202,10 @@ private static void IntroduceTryParse(IAspectBuilder<INamedType> builder, string method.AddAttribute(CodeAnnotations.CompilerGenerated); }); + /// <summary>Whether <c>ISpanParsable</c> is generated: the wrapped type implements it, whatever its strategy for text.</summary> + internal static bool SupportsSpan(INamedType valueType) => + valueType.IsConvertibleTo(typeof(ISpanParsable<>).ToNamedType().MakeGenericInstance(valueType)); + /// <summary>Whether <c>IUtf8SpanParsable</c> is generated: the wrapped type implements it.</summary> internal static bool SupportsUtf8(INamedType valueType) => valueType.IsConvertibleTo(typeof(IUtf8SpanParsable<>).ToNamedType().MakeGenericInstance(valueType)); @@ -194,7 +217,7 @@ internal static ValueParseStrategy ResolveStrategy(INamedType valueType) return ValueParseStrategy.String; // ISpanParsable extends IParsable, so probe for it first. - if (valueType.IsConvertibleTo(typeof(ISpanParsable<>).ToNamedType().MakeGenericInstance(valueType))) + if (SupportsSpan(valueType)) return ValueParseStrategy.SpanParsable; if (valueType.IsConvertibleTo(typeof(IParsable<>).ToNamedType().MakeGenericInstance(valueType))) diff --git a/src/CodoMetis.TypeKit/CodoMetis.TypeKit.csproj b/src/CodoMetis.TypeKit/CodoMetis.TypeKit.csproj index f3069810..704500e4 100644 --- a/src/CodoMetis.TypeKit/CodoMetis.TypeKit.csproj +++ b/src/CodoMetis.TypeKit/CodoMetis.TypeKit.csproj @@ -25,10 +25,12 @@ ReferenceOutputAssembly="false", NuGet emits no dependency at all (measured 2026-09-27). Private="false" keeps the analyzer assembly out of this project's output, and nothing here compiles against it. PrivateAssets="none" keeps exclude="Build,Analyzers" off the dependency. + OutputItemType="Analyzer" also runs the analyzers on this project's own code, as + src/Directory.Build.targets does for the other packages. AnalyzerPackagingTests packs the packages and reads the nuspec to hold all of it in place. --> <ItemGroup> - <ProjectReference Include="..\CodoMetis.TypeKit.Analyzers\CodoMetis.TypeKit.Analyzers.csproj" PrivateAssets="none" Private="false" /> + <ProjectReference Include="..\CodoMetis.TypeKit.Analyzers\CodoMetis.TypeKit.Analyzers.csproj" PrivateAssets="none" Private="false" OutputItemType="Analyzer" /> </ItemGroup> </Project> diff --git a/src/CodoMetis.TypeKit/CompilerServices/BuiltInConverters.cs b/src/CodoMetis.TypeKit/CompilerServices/BuiltInConverters.cs index 02f09847..946424be 100644 --- a/src/CodoMetis.TypeKit/CompilerServices/BuiltInConverters.cs +++ b/src/CodoMetis.TypeKit/CompilerServices/BuiltInConverters.cs @@ -13,32 +13,40 @@ internal static class BuiltInConverters { /// <summary>The built-in converter for <typeparamref name="T"/>, or null where the serializer has none.</summary> /// <typeparam name="T">The type.</typeparam> - public static JsonConverter<T>? For<T>() => (JsonConverter<T>?)(object?)( - typeof(T) == typeof(string) ? JsonMetadataServices.StringConverter - : typeof(T) == typeof(int) ? JsonMetadataServices.Int32Converter - : typeof(T) == typeof(long) ? JsonMetadataServices.Int64Converter - : typeof(T) == typeof(bool) ? JsonMetadataServices.BooleanConverter - : typeof(T) == typeof(decimal) ? JsonMetadataServices.DecimalConverter - : typeof(T) == typeof(double) ? JsonMetadataServices.DoubleConverter - : typeof(T) == typeof(float) ? JsonMetadataServices.SingleConverter - : typeof(T) == typeof(Guid) ? JsonMetadataServices.GuidConverter - : typeof(T) == typeof(DateTime) ? JsonMetadataServices.DateTimeConverter - : typeof(T) == typeof(DateTimeOffset) ? JsonMetadataServices.DateTimeOffsetConverter - : typeof(T) == typeof(DateOnly) ? JsonMetadataServices.DateOnlyConverter - : typeof(T) == typeof(TimeOnly) ? JsonMetadataServices.TimeOnlyConverter - : typeof(T) == typeof(TimeSpan) ? JsonMetadataServices.TimeSpanConverter - : typeof(T) == typeof(short) ? JsonMetadataServices.Int16Converter - : typeof(T) == typeof(ushort) ? JsonMetadataServices.UInt16Converter - : typeof(T) == typeof(uint) ? JsonMetadataServices.UInt32Converter - : typeof(T) == typeof(ulong) ? JsonMetadataServices.UInt64Converter - : typeof(T) == typeof(byte) ? JsonMetadataServices.ByteConverter - : typeof(T) == typeof(sbyte) ? JsonMetadataServices.SByteConverter - : typeof(T) == typeof(char) ? JsonMetadataServices.CharConverter - : typeof(T) == typeof(Half) ? JsonMetadataServices.HalfConverter - : typeof(T) == typeof(Int128) ? JsonMetadataServices.Int128Converter - : typeof(T) == typeof(UInt128) ? JsonMetadataServices.UInt128Converter - : typeof(T) == typeof(Uri) ? JsonMetadataServices.UriConverter - : typeof(T) == typeof(Version) ? JsonMetadataServices.VersionConverter - : typeof(T) == typeof(byte[]) ? JsonMetadataServices.ByteArrayConverter - : (JsonConverter?)null); + public static JsonConverter<T>? For<T>() => (JsonConverter<T>?)Untyped<T>(); + + // A chain of returns, not one conditional expression: Rider's type inference walks a nested + // conditional again at every level, and 26 levels froze its backend until it crashed. Each + // typeof(T) test stays in a generic method, so the JIT and Native AOT still drop every arm but one. + private static JsonConverter? Untyped<T>() + { + if (typeof(T) == typeof(string)) return JsonMetadataServices.StringConverter; + if (typeof(T) == typeof(int)) return JsonMetadataServices.Int32Converter; + if (typeof(T) == typeof(long)) return JsonMetadataServices.Int64Converter; + if (typeof(T) == typeof(bool)) return JsonMetadataServices.BooleanConverter; + if (typeof(T) == typeof(decimal)) return JsonMetadataServices.DecimalConverter; + if (typeof(T) == typeof(double)) return JsonMetadataServices.DoubleConverter; + if (typeof(T) == typeof(float)) return JsonMetadataServices.SingleConverter; + if (typeof(T) == typeof(Guid)) return JsonMetadataServices.GuidConverter; + if (typeof(T) == typeof(DateTime)) return JsonMetadataServices.DateTimeConverter; + if (typeof(T) == typeof(DateTimeOffset)) return JsonMetadataServices.DateTimeOffsetConverter; + if (typeof(T) == typeof(DateOnly)) return JsonMetadataServices.DateOnlyConverter; + if (typeof(T) == typeof(TimeOnly)) return JsonMetadataServices.TimeOnlyConverter; + if (typeof(T) == typeof(TimeSpan)) return JsonMetadataServices.TimeSpanConverter; + if (typeof(T) == typeof(short)) return JsonMetadataServices.Int16Converter; + if (typeof(T) == typeof(ushort)) return JsonMetadataServices.UInt16Converter; + if (typeof(T) == typeof(uint)) return JsonMetadataServices.UInt32Converter; + if (typeof(T) == typeof(ulong)) return JsonMetadataServices.UInt64Converter; + if (typeof(T) == typeof(byte)) return JsonMetadataServices.ByteConverter; + if (typeof(T) == typeof(sbyte)) return JsonMetadataServices.SByteConverter; + if (typeof(T) == typeof(char)) return JsonMetadataServices.CharConverter; + if (typeof(T) == typeof(Half)) return JsonMetadataServices.HalfConverter; + if (typeof(T) == typeof(Int128)) return JsonMetadataServices.Int128Converter; + if (typeof(T) == typeof(UInt128)) return JsonMetadataServices.UInt128Converter; + if (typeof(T) == typeof(Uri)) return JsonMetadataServices.UriConverter; + if (typeof(T) == typeof(Version)) return JsonMetadataServices.VersionConverter; + if (typeof(T) == typeof(byte[])) return JsonMetadataServices.ByteArrayConverter; + + return null; + } } diff --git a/src/CodoMetis.TypeKit/CompilerServices/TranslatedAsWrappedValueAttribute.cs b/src/CodoMetis.TypeKit/CompilerServices/TranslatedAsWrappedValueAttribute.cs index a47f37dc..95d443a7 100644 --- a/src/CodoMetis.TypeKit/CompilerServices/TranslatedAsWrappedValueAttribute.cs +++ b/src/CodoMetis.TypeKit/CompilerServices/TranslatedAsWrappedValueAttribute.cs @@ -11,7 +11,8 @@ namespace CodoMetis.TypeKit.CompilerServices; /// attribute promises that the query and the in-memory call agree, and a method that does anything /// more breaks that promise only on the database side, where nothing checks it. /// CodoMetis.TypeKit.Generators puts it on the <c>GetValue</c>/<c>ValueOrNull</c> companions it -/// generates, and CodoMetis.TypeKit.EntityFrameworkCore translates it, on a static method whose one +/// generates, CodoMetis.TypeKit.EntityFrameworkCore on its converter's <c>ProviderValue</c>, and the +/// latter translates it, on a static method whose one /// parameter is a value object (or its <c>Nullable</c>) and which returns what that value object /// wraps. On any other method it is ignored, and EF refuses the call as it would without it. /// </remarks> diff --git a/src/CodoMetis.TypeKit/OptionExtensions.cs b/src/CodoMetis.TypeKit/OptionExtensions.cs index 525afb87..369d57a6 100644 --- a/src/CodoMetis.TypeKit/OptionExtensions.cs +++ b/src/CodoMetis.TypeKit/OptionExtensions.cs @@ -430,7 +430,7 @@ public Option<T> FirstOrNone(Func<T, bool> predicate) => source.Where(predicate) .Select(Some) .DefaultIfEmpty(None<T>()) - .FirstOrDefault(); + .First(); /// <summary>The last element that matches, or <c>None</c>.</summary> /// <param name="predicate">The condition to match.</param> @@ -439,7 +439,7 @@ public Option<T> LastOrNone(Func<T, bool> predicate) => source.Where(predicate) .Select(Some) .DefaultIfEmpty(None<T>()) - .LastOrDefault(); + .Last(); /// <summary> /// The only element, or <c>None</c> for an empty sequence. More than one is not absence: it diff --git a/src/CodoMetis.TypeKit/TypeArgumentConstraints.cs b/src/CodoMetis.TypeKit/TypeArgumentConstraints.cs index 706502d2..6fbf616d 100644 --- a/src/CodoMetis.TypeKit/TypeArgumentConstraints.cs +++ b/src/CodoMetis.TypeKit/TypeArgumentConstraints.cs @@ -118,7 +118,9 @@ public sealed class InterfaceAttribute : Attribute; /// </summary> /// <remarks> /// A type parameter given as the argument satisfies it only when it carries <c>[Unsealed]</c> itself: no -/// C# constraint excludes a sealed class. Reflection is out of the analyzer's reach. +/// C# constraint excludes a sealed class. Reflection is out of the analyzer's reach. Until 3.0.0 the analyzer +/// also accepts <c>Enum</c>, <c>Delegate</c>, <c>MulticastDelegate</c>, <c>ValueType</c> and <c>Array</c>, +/// abstract classes nothing can derive from, since refusing them reports more under an Error. /// </remarks> [AttributeUsage(AttributeTargets.GenericParameter)] public sealed class UnsealedAttribute : Attribute; diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 00000000..0ba5aac4 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,20 @@ +<Project> + <!-- + The CMTK analyzers run on the shipping code, as on a consumer's. A library generic that forwarded + its parameters into a constrained one without the attribute broke CMTK0010 in two packages and + reached a compiler that said so only when the analyzer was added by hand (1a9d7f2, 38 errors). A + project reference does not bring a project's analyzers along (dotnet/roslyn#79272), so each + project names the analyzer itself. + + Targets, not props: IsRoslynComponent and the project's own references are known only once the + project file is read. The analyzer projects are left out, and so is a project that references the + analyzer already: the base package, whose reference is the consumers' dependency and loads the + analyzer here through OutputItemType. ReferenceOutputAssembly="false" emits no package dependency, + and nothing compiles against the analyzer. ShippingProjectAnalyzerTests reads each project's + evaluation to hold this in place. + --> + <ItemGroup Condition="'$(IsRoslynComponent)' != 'true' and '@(ProjectReference->WithMetadataValue('Filename', 'CodoMetis.TypeKit.Analyzers'))' == ''"> + <ProjectReference Include="$(MSBuildThisFileDirectory)CodoMetis.TypeKit.Analyzers/CodoMetis.TypeKit.Analyzers.csproj" + OutputItemType="Analyzer" ReferenceOutputAssembly="false" PrivateAssets="all" /> + </ItemGroup> +</Project> diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs new file mode 100644 index 00000000..b295217c --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintKindTests.cs @@ -0,0 +1,225 @@ +using CodoMetis.TypeKit.Analyzers.Tests.Variants; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; + +namespace CodoMetis.TypeKit.Analyzers.Tests; + +/// <summary> +/// CMTK0010 across the variant catalogue of what a type argument is (<see cref="TypeArgumentKinds"/>): +/// every attribute against every kind, and both pair attributes against every pair, each verdict taken +/// from docs/type-constraints.md. +/// </summary> +/// <remarks> +/// The oracle is the page's tables, written as predicates: "Accepts" over what a type is, "Proved by" +/// over what a type parameter declares. A kind the oracle accepts is reported nowhere; one it rejects is +/// reported once, at the argument (a pair's at the later one). +/// </remarks> +public sealed class TypeArgumentConstraintKindTests +{ + /// <summary>One attribute that constrains a single parameter, read from the page's tables.</summary> + /// <param name="Attribute">The attribute's name, without the suffix.</param> + /// <param name="Accepts">The "Accepts" column, over what a type is.</param> + /// <param name="ProvedBy">The "Proved by" column, over what a type parameter declares; the attribute itself always proves it.</param> + private sealed record Oracle(string Attribute, Func<Is, bool> Accepts, Func<Parameter, bool> ProvedBy) + { + public bool Meets(TypeArgumentKind kind) => kind switch + { + KindOfType type => Accepts(type.Is), + KindOfParameter parameter => Proves(parameter.Facts), + _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null), + }; + + public bool Proves(Parameter parameter) => parameter.Carries.Contains(Attribute) || ProvedBy(parameter); + } + + private static readonly Oracle[] Oracles = + [ + new("Abstract", + // an abstract class or record | a concrete type, and an interface + Accepts: type => type.HasFlag(Is.Abstract) && !type.HasFlag(Is.Interface), + // nothing but [Abstract]: no C# constraint excludes a concrete type + ProvedBy: _ => false), + + new("Sealed", + // a sealed class or record, a struct, an enum | a type that can be derived from + Accepts: type => type.HasFlag(Is.Sealed) || type.HasFlag(Is.Struct) || type.HasFlag(Is.Enum), + // struct or unmanaged, [ReadOnlyStruct] or [FlagsEnum], or a bound where T : U with U proving [Sealed] + ProvedBy: parameter => (parameter.Declares & (Declares.Struct | Declares.Unmanaged)) != 0 + || parameter.Carries.Intersect(["ReadOnlyStruct", "FlagsEnum"]).Any() + || BoundBy(parameter, "Sealed")), + + new("Record", + // a record class or record struct | a class or a struct + Accepts: type => type.HasFlag(Is.Record), + // a bound that is a record, or a U proving [Record] + ProvedBy: parameter => parameter.Declares.HasFlag(Declares.RecordBound) || BoundBy(parameter, "Record")), + + new("Concrete", + // a class, record or struct that can be instantiated, an enum, a delegate | an abstract or static type, + // an interface, and the abstract bases Enum, Delegate and ValueType + Accepts: type => !type.HasFlag(Is.Abstract) && !type.HasFlag(Is.Interface), + // new(), or anything that proves [Sealed], [Sealed] itself included + ProvedBy: parameter => parameter.Declares.HasFlag(Declares.New) || Single("Sealed").Proves(parameter)), + + new("Interface", + // an interface | a class, even one that implements the interface + Accepts: type => type.HasFlag(Is.Interface), + // nothing but the attribute: an interface bound admits its implementers + ProvedBy: _ => false), + + new("Unsealed", + // a class or record class that can be derived from, abstract included, and dynamic, which is object | + // a sealed or static class, an interface, a struct + Accepts: type => type.HasFlag(Is.Class) && !type.HasFlag(Is.Sealed) && !type.HasFlag(Is.SpecialBase) || type.HasFlag(Is.Dynamic), + // nothing but the attribute: class admits a sealed class + ProvedBy: _ => false), + + new("ValueObject", + // a struct or class that implements IValue<T> for any T | any other type, a closed set included, and an + // interface, even one that derives from IValue<T> + Accepts: type => type.HasFlag(Is.ValueObjectMarker) && !type.HasFlag(Is.Interface), + // a bound that is or implements the marker, counted only beside struct, unmanaged, new() or a class + // bound, or a U proving it + ProvedBy: parameter => parameter.Declares.HasFlag(Declares.ValueObjectBound) + && (parameter.Declares & (Declares.Struct | Declares.Unmanaged | Declares.New | Declares.ClassBound)) != 0 + || BoundBy(parameter, "ValueObject")), + + new("ClosedSet", + // a type that implements IClosedSet<TSelf> | any other type, a value object or an enum included + Accepts: type => type.HasFlag(Is.ClosedSetMarker), + // a bound that is or implements the marker, or a U proving it + ProvedBy: parameter => parameter.Declares.HasFlag(Declares.ClosedSetBound) || BoundBy(parameter, "ClosedSet")), + + new("FlagsEnum", + // an enum marked [Flags] | a plain enum + Accepts: type => type.HasFlag(Is.Enum) && type.HasFlag(Is.Flags), + // nothing but the attribute: Enum admits a plain enum + ProvedBy: _ => false), + + new("ReadOnlyStruct", + // a readonly struct or readonly record struct, the primitives included | a mutable struct, an enum, a class + Accepts: type => type.HasFlag(Is.Struct) && type.HasFlag(Is.ReadOnly), + // nothing but the attribute: struct admits a mutable one + ProvedBy: _ => false), + ]; + + /// <summary> + /// Where CMTK0010 is silent although the oracle rejects, each with the decision. <c>[Unsealed]</c> shipped in + /// 2.1.0 taking the abstract bases nothing can derive from; refusing them reports more under an Error, which + /// waits for 3.0.0. The page says so beside the table. + /// </summary> + private static readonly Dictionary<(string Attribute, string Kind), string> SilentUntilThree = new() + { + [("Unsealed", "the abstract base Enum")] = "decision 34", + [("Unsealed", "the abstract base Delegate")] = "decision 34", + [("Unsealed", "the abstract base MulticastDelegate")] = "decision 34", + [("Unsealed", "the abstract base ValueType")] = "decision 34", + [("Unsealed", "the abstract base Array")] = "decision 34", + }; + + /// <summary>The two attributes that constrain a pair: [Unrelated] implies [Distinct].</summary> + private static readonly Dictionary<string, Apart> PairOracles = new() + { + ["Distinct"] = Apart.Distinct, + ["Unrelated"] = Apart.Unrelated, + }; + + private static Oracle Single(string attribute) => Oracles.Single(oracle => oracle.Attribute == attribute); + + private static bool BoundBy(Parameter parameter, string attribute) => + parameter.BoundBy is { } bound && Single(attribute).Proves(bound); + + /// <summary> + /// Whether the base package under test declares the attribute. On a current checkout every one does, + /// which <see cref="Every_constraint_attribute_has_an_oracle_and_every_oracle_an_attribute"/> holds; a + /// checkout of an earlier commit runs the kinds its attributes allow. + /// </summary> + private static bool Declared(string attribute) => RealTypeKit.Assembly.GetType($"CodoMetis.TypeKit.{attribute}Attribute") is not null; + + public static TheoryData<string, string> Kinds() + { + var cases = new TheoryData<string, string>(); + + foreach (var oracle in Oracles.Where(oracle => Declared(oracle.Attribute))) + foreach (var kind in TypeArgumentKinds.All.Where(kind => kind.Mentions.All(Declared))) + cases.Add(oracle.Attribute, kind.Name); + + return cases; + } + + public static TheoryData<string, string> Pairs() + { + var cases = new TheoryData<string, string>(); + + foreach (var attribute in PairOracles.Keys.Where(Declared)) + foreach (var pair in TypeArgumentKinds.Pairs.Where(pair => TypeArgumentKinds.Mentions(pair).All(Declared))) + cases.Add(attribute, pair.Name); + + return cases; + } + + [Theory] + [MemberData(nameof(Kinds))] + public Task Each_kind_is_reported_as_the_page_says(string attribute, string kindName) + { + var kind = TypeArgumentKinds.All.Single(candidate => candidate.Name == kindName); + var breaking = !Single(attribute).Meets(kind) && !SilentUntilThree.ContainsKey((attribute, kind.Name)); + var argument = breaking ? $"{{|#0:{kind.Argument}|}}" : kind.Argument; + + return Run( + $"public sealed class Subject<[{attribute}] T> {{ }}", + TypeArgumentKinds.Use(kind.Parameters, kind.Where, "Subject", argument), + breaking); + } + + [Theory] + [MemberData(nameof(Pairs))] + public Task Each_pair_is_reported_as_the_page_says(string attribute, string pairName) + { + var pair = TypeArgumentKinds.Pairs.Single(candidate => candidate.Name == pairName); + var breaking = pair.Proved < PairOracles[attribute]; + var second = breaking ? $"{{|#0:{pair.Second}|}}" : pair.Second; + + return Run( + $"public sealed class Subject<[{attribute}] T1, [{attribute}] T2> {{ }}", + TypeArgumentKinds.Use(pair.Parameters, pair.Where, "Subject", $"{pair.First}, {second}"), + breaking); + } + + [Fact] + public void Every_constraint_attribute_has_an_oracle_and_every_oracle_an_attribute() + { + var declared = RealTypeKit.Assembly.GetExportedTypes() + .Where(type => type.IsSubclassOf(typeof(Attribute))) + .Where(type => type.GetCustomAttributes(typeof(AttributeUsageAttribute), false) + .Cast<AttributeUsageAttribute>() + .Any(usage => usage.ValidOn == AttributeTargets.GenericParameter)) + .Select(type => type.Name[..^"Attribute".Length]) + .Order(); + + declared.ShouldBe(Oracles.Select(oracle => oracle.Attribute).Concat(PairOracles.Keys).Order()); + } + + [Fact] + public void The_catalogue_names_are_unique_and_its_pairs_reach_every_verdict() + { + TypeArgumentKinds.All.Select(kind => kind.Name).ShouldBeUnique(); + TypeArgumentKinds.Pairs.Select(pair => pair.Name).ShouldBeUnique(); + + TypeArgumentKinds.Pairs.Select(pair => pair.Proved).Distinct().Count().ShouldBe(3); + + // A silence is for a kind the oracle rejects; one it accepts needs none. + SilentUntilThree.Keys.ShouldAllBe(silent => !Single(silent.Attribute).Meets(TypeArgumentKinds.All.Single(kind => kind.Name == silent.Kind))); + } + + private static Task Run(string subject, string use, bool breaking) + { + var test = new AnalyzerTest<TypeArgumentConstraintAnalyzer>(TypeArgumentKinds.Candidates + subject); + test.TestState.Sources.Add(("Use.cs", use)); + + if (breaking) + test.ExpectedDiagnostics.Add(new DiagnosticResult("CMTK0010", DiagnosticSeverity.Error).WithLocation(0)); + + return test.RunAsync(TestContext.Current.CancellationToken); + } +} diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintVariantTests.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintVariantTests.cs new file mode 100644 index 00000000..0f5f5dd2 --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/TypeArgumentConstraintVariantTests.cs @@ -0,0 +1,58 @@ +using CodoMetis.TypeKit.Analyzers.Tests.Variants; +using Microsoft.CodeAnalysis; + +namespace CodoMetis.TypeKit.Analyzers.Tests; + +/// <summary> +/// CMTK0010 across the variant catalogue for a type argument (<see cref="TypeArgumentSites"/>): every +/// shape through which an argument reaches a parameter, with an argument that breaks the attribute and +/// one that meets it. +/// </summary> +/// <remarks> +/// Two kinds 2.0.0 shipped, <c>[Sealed]</c> and <c>[Concrete]</c>, and one 2.1.0 added, +/// <c>[ReadOnlyStruct]</c>, since the two differ at a method bound by pattern (decision 89). +/// </remarks> +public sealed class TypeArgumentConstraintVariantTests +{ + private const string Candidates = + """ + public abstract class Handler { } + public interface IHandler { } + public sealed class OrderHandler : Handler, IHandler { } + public readonly struct Frozen { } + + """; + + /// <summary>A 2.0.0 kind waits for 3.0.0 at a method bound by pattern, since an Error that reports more waits for a major version.</summary> + private static readonly Dictionary<string, string> PatternWaitsForThree = new() + { + ["a foreach's GetEnumerator"] = "decision 89", + ["an await's GetAwaiter"] = "decision 89", + ["a deconstruction"] = "decision 89", + ["a positional pattern"] = "decision 89", + ["a collection expression's Add"] = "decision 89", + }; + + private static readonly TypeArgumentSubject[] Subjects = + [ + new("[Sealed]", "[Sealed]", "Handler", "OrderHandler", Candidates, PatternWaitsForThree), + new("[Concrete]", "[Concrete]", "IHandler", "OrderHandler", Candidates, PatternWaitsForThree), + new("[ReadOnlyStruct]", "[ReadOnlyStruct]", "Handler", "Frozen", Candidates), + ]; + + public static TheoryData<string, string, bool> Cases() => TypeArgumentSiteRun.Cases(Subjects); + + [Theory] + [MemberData(nameof(Cases))] + public Task Each_site_reports_a_breaking_argument_once_and_a_meeting_one_never(string subject, string site, bool breaking) => + TypeArgumentSiteRun.Verify<TypeArgumentConstraintAnalyzer>( + Subjects.Single(candidate => candidate.Name == subject), site, breaking, "CMTK0010", DiagnosticSeverity.Error); + + [Fact] + public void The_catalogue_is_not_empty_and_its_site_names_are_unique() + { + TypeArgumentSites.All.Count.ShouldBeGreaterThanOrEqualTo(50); + TypeArgumentSites.All.Select(site => site.Name).ShouldBeUnique(); + PatternWaitsForThree.Keys.ShouldAllBe(name => TypeArgumentSites.All.Any(site => site.Name == name)); + } +} diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentKinds.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentKinds.cs new file mode 100644 index 00000000..2abbdedb --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentKinds.cs @@ -0,0 +1,327 @@ +namespace CodoMetis.TypeKit.Analyzers.Tests.Variants; + +/// <summary>What a closed type argument is, as its declaration says.</summary> +[Flags] +public enum Is +{ + Class = 1 << 0, + Record = 1 << 1, + Struct = 1 << 2, + Enum = 1 << 3, + Delegate = 1 << 4, + Interface = 1 << 5, + Array = 1 << 6, + Dynamic = 1 << 7, + Abstract = 1 << 8, + Sealed = 1 << 9, + ReadOnly = 1 << 10, + Flags = 1 << 11, + + /// <summary><c>Enum</c>, <c>Delegate</c>, <c>MulticastDelegate</c>, <c>ValueType</c> and <c>Array</c>: abstract classes no type written in C# derives from (CS0644).</summary> + SpecialBase = 1 << 12, + + /// <summary>Implements <c>IValue<T></c> for some <c>T</c>, or is it.</summary> + ValueObjectMarker = 1 << 13, + + /// <summary>Implements <c>IClosedSet<TSelf></c>.</summary> + ClosedSetMarker = 1 << 14, +} + +/// <summary>What a type parameter given as the argument declares in its <c>where</c> clause.</summary> +[Flags] +public enum Declares +{ + Nothing = 0, + Class = 1 << 0, + Struct = 1 << 1, + Unmanaged = 1 << 2, + New = 1 << 3, + + /// <summary>A class type as a bound, <c>Enum</c> and <c>Delegate</c> included.</summary> + ClassBound = 1 << 4, + + /// <summary><c>where T : Enum</c>, which every enum meets, and <c>Enum</c> itself.</summary> + EnumBound = 1 << 5, + + /// <summary>A record as a bound; only records derive from records (CS8865).</summary> + RecordBound = 1 << 6, + + InterfaceBound = 1 << 7, + + /// <summary><c>IValue<T></c> or an interface deriving from it, as a bound.</summary> + ValueObjectBound = 1 << 8, + + /// <summary><c>IClosedSet<T></c> as a bound.</summary> + ClosedSetBound = 1 << 9, +} + +/// <summary> +/// A type parameter given as the argument: its constraints, the attributes it carries, and the +/// parameter it is bound by (<c>where T : U</c>), if any. +/// </summary> +public sealed record Parameter(Declares Declares, params string[] Carries) +{ + public Parameter? BoundBy { get; init; } +} + +/// <summary>One kind of type a type argument can be.</summary> +/// <param name="Name">What the argument is, as a test case names it.</param> +/// <param name="Argument">The argument as written.</param> +/// <param name="Parameters">The type parameter list of the generic the argument is written in, empty for none.</param> +/// <param name="Where">That generic's constraint clauses.</param> +public abstract record TypeArgumentKind(string Name, string Argument, string Parameters, string Where) +{ + /// <summary>The attributes the kind's own declaration carries, which a base package must declare for it to compile.</summary> + public virtual IEnumerable<string> Mentions => []; + + public override string ToString() => Name; +} + +/// <summary>A type whatever its type parameters are: a closed type, or one built from a parameter.</summary> +public sealed record KindOfType(string Name, string Argument, Is Is, string Parameters = "", string Where = "") + : TypeArgumentKind(Name, Argument, Parameters, Where); + +/// <summary>A type parameter of the generic the argument is written in, <c>T</c>.</summary> +public sealed record KindOfParameter(string Name, string Parameters, string Where, Parameter Facts) + : TypeArgumentKind(Name, "T", Parameters, Where) +{ + public override IEnumerable<string> Mentions => Walk(Facts).SelectMany(parameter => parameter.Carries); + + private static IEnumerable<Parameter> Walk(Parameter? parameter) + { + for (; parameter is not null; parameter = parameter.BoundBy) yield return parameter; + } +} + +/// <summary>What docs/type-constraints.md proves of a pair: nothing, that its two are never one type, or never related.</summary> +public enum Apart +{ + Unproved, + Distinct, + Unrelated, +} + +/// <summary>Two arguments given to two parameters that constrain each other.</summary> +/// <param name="Name">What the pair is, as a test case names it.</param> +/// <param name="First">The argument for the first parameter.</param> +/// <param name="Second">The argument for the second, where a breach is reported.</param> +/// <param name="Proved">What the docs' rules prove of the pair; <c>[Unrelated]</c> implies <c>[Distinct]</c>.</param> +/// <param name="Because">The rule of docs/type-constraints.md that decides <paramref name="Proved"/>.</param> +/// <param name="Parameters">The type parameter list of the generic the pair is written in, empty for none.</param> +/// <param name="Where">That generic's constraint clauses.</param> +public sealed record PairKind(string Name, string First, string Second, Apart Proved, string Because, string Parameters = "", string Where = "") +{ + public override string ToString() => Name; +} + +/// <summary> +/// The variant catalogue for analyzer input shape (AIS) when the input is a type argument, on its second +/// axis (design/adr/0095): what the argument is. <see cref="TypeArgumentSites"/> is the first, where it +/// is written. +/// </summary> +/// <remarks> +/// <para> +/// Seven of CMTK0010's fourteen fixes of 2026-10-13 and 14 (design/fix-history.md, chain A) were about +/// what an argument is, not where: an enum and a delegate taken as concrete, a type parameter whose +/// constraints prove the attribute, a bound or an argument built from a type parameter, an <c>Enum</c> +/// bound against <c>struct</c>, an interface deriving from the value-object marker, <c>dynamic</c>. +/// </para> +/// <para> +/// A kind states what the type is (<see cref="Is"/>) or what the parameter declares +/// (<see cref="Declares"/>), as the language has it, and leaves the verdict to an oracle per attribute +/// the rule supplies. A pair states what docs/type-constraints.md proves of it, and the rule it follows +/// from, since what proves two arguments apart is that page's rule and not the language's. +/// </para> +/// <para> +/// Each kind is written at one site, a field's type, since the sites are the other axis. A kind the +/// language adds goes here. +/// </para> +/// </remarks> +public static class TypeArgumentKinds +{ + /// <summary>The types the kinds name, a file of their own.</summary> + public const string Candidates = + """ + global using System; + global using System.Collections.Generic; + global using CodoMetis.TypeKit; + + public class Widget { } + public abstract class Handler { } + public interface IHandler { } + public sealed class OrderHandler : Handler, IHandler { } + public class Tracked : Handler { } + public record Note(int Id); + public sealed record Placed(int Id); + public abstract record Command; + public struct Mutable { public int Value; } + public readonly struct Frozen { } + public record struct Spot(int X); + public readonly record struct Stamp(int X); + public enum Shade { Light, Dark } + [Flags] public enum Rights { None = 0, Read = 1, Write = 2 } + public delegate void Notify(); + public class Node<T> { } + public sealed class Leaf : Node<Leaf> { } + + public readonly struct Id : CodoMetis.TypeKit.ValueObjects.IValue<int> { } + public class IdClass : CodoMetis.TypeKit.ValueObjects.IValue<int> { } + public interface IDerivedValue : CodoMetis.TypeKit.ValueObjects.IValue<int> { } + + public sealed class Palette : CodoMetis.TypeKit.ClosedSets.IClosedSet<Palette> + { + public string Name => ""; + public string QualifiedName => ""; + public static IReadOnlyList<Palette> All => []; + public static bool TryParse(string? name, out Palette member) { member = null!; return false; } + } + + """; + + private const string ValueMarker = "CodoMetis.TypeKit.ValueObjects.IValue<int>"; + + /// <summary>A file that writes <paramref name="generic"/> closed over <paramref name="arguments"/> as a field's type.</summary> + public static string Use(string parameters, string where, string generic, string arguments) => + $$""" + #nullable enable + public static class Use{{parameters}} {{where}} + { + public static {{generic}}<{{arguments}}>? Field; + } + """; + + public static IReadOnlyList<TypeArgumentKind> All { get; } = + [ + // Declared in source. + new KindOfType("a class", "Widget", Is.Class), + new KindOfType("an abstract class", "Handler", Is.Class | Is.Abstract), + new KindOfType("a sealed class", "OrderHandler", Is.Class | Is.Sealed), + new KindOfType("a record", "Note", Is.Class | Is.Record), + new KindOfType("a sealed record", "Placed", Is.Class | Is.Record | Is.Sealed), + new KindOfType("an abstract record", "Command", Is.Class | Is.Record | Is.Abstract), + new KindOfType("a struct", "Mutable", Is.Struct), + new KindOfType("a readonly struct", "Frozen", Is.Struct | Is.ReadOnly), + new KindOfType("a record struct", "Spot", Is.Struct | Is.Record), + new KindOfType("a readonly record struct", "Stamp", Is.Struct | Is.Record | Is.ReadOnly), + new KindOfType("an enum", "Shade", Is.Enum), + new KindOfType("a flags enum", "Rights", Is.Enum | Is.Flags), + new KindOfType("a delegate", "Notify", Is.Delegate | Is.Sealed), + new KindOfType("an interface", "IHandler", Is.Interface), + new KindOfType("a value object struct", "Id", Is.Struct | Is.ReadOnly | Is.ValueObjectMarker), + new KindOfType("a value object class", "IdClass", Is.Class | Is.ValueObjectMarker), + new KindOfType("an interface deriving from the value-object marker", "IDerivedValue", Is.Interface | Is.ValueObjectMarker), + new KindOfType("the value-object marker", ValueMarker, Is.Interface | Is.ValueObjectMarker), + new KindOfType("a closed set", "Palette", Is.Class | Is.Sealed | Is.ClosedSetMarker), + + // Declared in metadata, or by the language. + new KindOfType("a primitive", "int", Is.Struct | Is.ReadOnly), + new KindOfType("a sealed class from metadata", "string", Is.Class | Is.Sealed), + new KindOfType("an abstract class from metadata", "System.IO.Stream", Is.Class | Is.Abstract), + new KindOfType("a readonly struct from metadata", "Guid", Is.Struct | Is.ReadOnly), + new KindOfType("an enum from metadata", "DayOfWeek", Is.Enum), + new KindOfType("a flags enum from metadata", "System.IO.FileAccess", Is.Enum | Is.Flags), + new KindOfType("a generic class closed over a struct", "List<int>", Is.Class), + new KindOfType("object", "object", Is.Class), + new KindOfType("dynamic", "dynamic", Is.Dynamic), + new KindOfType("a nullable reference type", "string?", Is.Class | Is.Sealed), + new KindOfType("a nullable value type", "int?", Is.Struct), + new KindOfType("a tuple", "(int, string)", Is.Struct), + new KindOfType("an array", "int[]", Is.Array | Is.Sealed), + new KindOfType("a jagged array of an abstract class", "Handler[][]", Is.Array | Is.Sealed), + new KindOfType("the abstract base Enum", "Enum", Is.Class | Is.Abstract | Is.SpecialBase), + new KindOfType("the abstract base Delegate", "Delegate", Is.Class | Is.Abstract | Is.SpecialBase), + new KindOfType("the abstract base ValueType", "ValueType", Is.Class | Is.Abstract | Is.SpecialBase), + new KindOfType("the abstract base MulticastDelegate", "MulticastDelegate", Is.Class | Is.Abstract | Is.SpecialBase), + new KindOfType("the abstract base Array", "Array", Is.Class | Is.Abstract | Is.SpecialBase), + + // Built from a type parameter: what it is does not depend on the parameter. + new KindOfType("a class built from a parameter", "List<T>", Is.Class, "<T>"), + new KindOfType("an array built from a parameter", "T[]", Is.Array | Is.Sealed, "<T>"), + new KindOfType("a nullable built from a struct parameter", "T?", Is.Struct, "<T>", "where T : struct"), + new KindOfType("a delegate built from a parameter", "Func<T>", Is.Delegate | Is.Sealed, "<T>"), + new KindOfType("a tuple built from a parameter", "(T, int)", Is.Struct, "<T>"), + + // A type parameter: what every closed use of it can be. + new KindOfParameter("an unconstrained parameter", "<T>", "", new(Declares.Nothing)), + new KindOfParameter("a class-constrained parameter", "<T>", "where T : class", new(Declares.Class)), + new KindOfParameter("a struct-constrained parameter", "<T>", "where T : struct", new(Declares.Struct)), + new KindOfParameter("an unmanaged-constrained parameter", "<T>", "where T : unmanaged", new(Declares.Unmanaged)), + new KindOfParameter("a new()-constrained parameter", "<T>", "where T : new()", new(Declares.New)), + new KindOfParameter("an Enum-bound parameter", "<T>", "where T : Enum", new(Declares.ClassBound | Declares.EnumBound)), + new KindOfParameter("a struct, Enum parameter", "<T>", "where T : struct, Enum", new(Declares.Struct | Declares.ClassBound | Declares.EnumBound)), + new KindOfParameter("a Delegate-bound parameter", "<T>", "where T : Delegate", new(Declares.ClassBound)), + new KindOfParameter("a parameter bound by an abstract class", "<T>", "where T : Handler", new(Declares.ClassBound)), + new KindOfParameter("a parameter bound by an abstract record", "<T>", "where T : Command", new(Declares.ClassBound | Declares.RecordBound)), + new KindOfParameter("a parameter bound by an interface", "<T>", "where T : IHandler", new(Declares.InterfaceBound)), + new KindOfParameter("a parameter bound by IEquatable<T>", "<T>", "where T : IEquatable<T>", new(Declares.InterfaceBound)), + new KindOfParameter("a parameter bound by Node<T>", "<T>", "where T : Node<T>", new(Declares.ClassBound)), + new KindOfParameter("a parameter bound by IValue<int>", "<T>", $"where T : {ValueMarker}", new(Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("a struct parameter bound by IValue<int>", "<T>", $"where T : struct, {ValueMarker}", new(Declares.Struct | Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("an unmanaged parameter bound by IValue<int>", "<T>", $"where T : unmanaged, {ValueMarker}", new(Declares.Unmanaged | Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("a class parameter bound by IValue<int>", "<T>", $"where T : class, {ValueMarker}", new(Declares.Class | Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("a new() parameter bound by IValue<int>", "<T>", $"where T : {ValueMarker}, new()", new(Declares.New | Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("a parameter bound by a class and IValue<int>", "<T>", $"where T : Handler, {ValueMarker}", new(Declares.ClassBound | Declares.InterfaceBound | Declares.ValueObjectBound)), + new KindOfParameter("a parameter bound by IClosedSet<T>", "<T>", "where T : class, CodoMetis.TypeKit.ClosedSets.IClosedSet<T>", new(Declares.Class | Declares.InterfaceBound | Declares.ClosedSetBound)), + new KindOfParameter("T : U, U carrying [Sealed]", "<[Sealed] U, T>", "where T : U", new(Declares.Nothing) { BoundBy = new(Declares.Nothing, "Sealed") }), + new KindOfParameter("T : U, U bound by an abstract record", "<U, T>", "where U : Command where T : U", new(Declares.Nothing) { BoundBy = new(Declares.ClassBound | Declares.RecordBound) }), + new KindOfParameter("T : U, U bound by IValue<int>", "<U, T>", $"where U : {ValueMarker} where T : U", new(Declares.Nothing) { BoundBy = new(Declares.InterfaceBound | Declares.ValueObjectBound) }), + new KindOfParameter("T : U, U bound by a class and IValue<int>", "<U, T>", $"where U : Handler, {ValueMarker} where T : U", new(Declares.Nothing) { BoundBy = new(Declares.ClassBound | Declares.InterfaceBound | Declares.ValueObjectBound) }), + new KindOfParameter("a struct parameter carrying [ReadOnlyStruct]", "<[ReadOnlyStruct] T>", "where T : struct", new(Declares.Struct, "ReadOnlyStruct")), + new KindOfParameter("a struct, Enum parameter carrying [FlagsEnum]", "<[FlagsEnum] T>", "where T : struct, Enum", new(Declares.Struct | Declares.ClassBound | Declares.EnumBound, "FlagsEnum")), + .. Carrying("Abstract", "Sealed", "Record", "Concrete", "Interface", "Unsealed", "ValueObject", "ClosedSet", "FlagsEnum", "ReadOnlyStruct"), + ]; + + /// <summary>A parameter carrying each attribute, and nothing else.</summary> + private static IEnumerable<TypeArgumentKind> Carrying(params string[] attributes) => + attributes.Select(attribute => new KindOfParameter($"a parameter carrying [{attribute}]", $"<[{attribute}] T>", "", new(Declares.Nothing, attribute))); + + public static IReadOnlyList<PairKind> Pairs { get; } = + [ + // Two closed types: one type, related or neither. + new("two unrelated types", "int", "string", Apart.Unrelated, "neither converts to the other"), + new("one type twice", "Placed", "Placed", Apart.Unproved, "one type twice"), + new("one type twice, one annotated", "string?", "string", Apart.Unproved, "one type twice, nullable annotations aside"), + new("a base class and a derived one", "Handler", "OrderHandler", Apart.Distinct, "a base and a derived type"), + new("a derived class and its base", "OrderHandler", "Handler", Apart.Distinct, "a base and a derived type"), + new("an interface and an implementer", "IHandler", "OrderHandler", Apart.Distinct, "an interface and an implementer"), + new("object and a struct", "object", "int", Apart.Distinct, "object and anything"), + new("a struct and ValueType", "int", "ValueType", Apart.Distinct, "a struct and ValueType"), + new("an enum and Enum", "Shade", "Enum", Apart.Distinct, "an implicit boxing conversion"), + new("two arrays related by covariance", "string[]", "object[]", Apart.Distinct, "an implicit reference conversion"), + new("two delegates related by variance", "Func<string>", "Func<object>", Apart.Distinct, "an implicit reference conversion"), + new("a struct and its nullable", "int", "int?", Apart.Unrelated, "no implicit identity, reference or boxing conversion between them"), + new("dynamic and object", "dynamic", "object", Apart.Unproved, "dynamic, which is object"), + + // A type parameter against another or against a type. + new("one parameter twice", "T", "T", Apart.Unproved, "one type twice", "<T>"), + new("two unconstrained parameters", "TA", "TB", Apart.Unproved, "nothing proves them apart", "<TA, TB>"), + new("two parameters carrying [Distinct]", "TA", "TB", Apart.Distinct, "both parameters carry [Distinct]", "<[Distinct] TA, [Distinct] TB>"), + new("two parameters carrying [Unrelated]", "TA", "TB", Apart.Unrelated, "both parameters carry [Unrelated]", "<[Unrelated] TA, [Unrelated] TB>"), + new("a struct parameter and a class parameter", "TS", "TC", Apart.Distinct, "struct against class proves [Distinct]; class alone proves nothing for [Unrelated]", "<TS, TC>", "where TS : struct where TC : class"), + new("a struct parameter and a class-bound parameter", "TS", "TC", Apart.Unrelated, "struct against a class bound other than object, ValueType and Enum", "<TS, TC>", "where TS : struct where TC : Handler"), + new("an unmanaged parameter and a class-bound parameter", "TS", "TC", Apart.Unrelated, "unmanaged against a class bound other than object, ValueType and Enum", "<TS, TC>", "where TS : unmanaged where TC : Handler"), + new("an Enum-bound parameter and a struct parameter", "TE", "TS", Apart.Unproved, "a class bound other than Enum, which every enum meets", "<TE, TS>", "where TE : Enum where TS : struct"), + new("parameters bound by unrelated classes", "TA", "TB", Apart.Unrelated, "both have class bounds, and neither derives from the other", "<TA, TB>", "where TA : Handler where TB : Widget"), + new("parameters bound by a base class and a derived one", "TA", "TB", Apart.Unproved, "class bounds where one derives from the other", "<TA, TB>", "where TA : Handler where TB : Tracked"), + new("parameters bound by one class built from each", "TA", "TB", Apart.Unproved, "Node<TA> and Node<TB> are one chain", "<TA, TB>", "where TA : Node<TA> where TB : Node<TB>"), + new("a parameter and a type it admits", "T", "int", Apart.Unproved, "nothing proves them apart", "<T>"), + new("a class parameter and a struct", "T", "int", Apart.Distinct, "against a type, the type breaks the parameter's constraints; class alone proves nothing for [Unrelated]", "<T>", "where T : class"), + new("a struct parameter and a class", "T", "string", Apart.Unrelated, "struct against a class other than object, ValueType and Enum", "<T>", "where T : struct"), + new("a struct parameter and ValueType", "T", "ValueType", Apart.Distinct, "against a type, the type breaks the parameter's constraints; ValueType is excluded for [Unrelated]", "<T>", "where T : struct"), + new("an Enum-bound parameter and Enum", "T", "Enum", Apart.Unproved, "where T : Enum admits Enum", "<T>", "where T : Enum"), + new("a parameter bound by IEquatable<T>, and int", "T", "int", Apart.Unproved, "where T : IEquatable<T> admits int", "<T>", "where T : IEquatable<T>"), + new("a parameter bound by Node<T>, and Leaf", "T", "Leaf", Apart.Unproved, "a class bound built from a parameter is met by a type whose chain holds its definition", "<T>", "where T : Node<T>"), + + // Types built from a type parameter, held as the parameter is. + new("List<T> and List<int>", "List<T>", "List<int>", Apart.Unproved, "List<T> is held as T is", "<T>"), + new("List<T> and List<int>, T : class", "List<T>", "List<int>", Apart.Distinct, "an argument no use can make the other's; one chain for [Unrelated]", "<T>", "where T : class"), + new("List<T> and HashSet<int>", "List<T>", "HashSet<int>", Apart.Unrelated, "another definition; two classes of different chains", "<T>"), + new("T[] and int[]", "T[]", "int[]", Apart.Unproved, "T[] is held as T is", "<T>"), + new("IEnumerable<T> and List<int>", "IEnumerable<T>", "List<int>", Apart.Distinct, "another definition; an interface never proves [Unrelated]", "<T>"), + new("List<TA> and List<TB>", "List<TA>", "List<TB>", Apart.Unproved, "List<TA> and List<TB> are one when TA and TB are", "<TA, TB>"), + ]; + + /// <summary>The attributes a pair kind's declaration carries.</summary> + public static IEnumerable<string> Mentions(PairKind pair) => + new[] { "Distinct", "Unrelated" }.Where(attribute => pair.Parameters.Contains($"[{attribute}]")); +} diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs new file mode 100644 index 00000000..ca86c371 --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSiteRun.cs @@ -0,0 +1,58 @@ +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace CodoMetis.TypeKit.Analyzers.Tests.Variants; + +/// <summary>Runs one site of <see cref="TypeArgumentSites"/> for one subject of a rule.</summary> +internal static partial class TypeArgumentSiteRun +{ + /// <summary>Every subject crossed with every site, each with the breaking argument and with the meeting one.</summary> + public static TheoryData<string, string, bool> Cases(IEnumerable<TypeArgumentSubject> subjects) + { + var cases = new TheoryData<string, string, bool>(); + + foreach (var subject in subjects) + { + foreach (var site in TypeArgumentSites.All) + { + cases.Add(subject.Name, site.Name, true); + cases.Add(subject.Name, site.Name, false); + } + } + + return cases; + } + + /// <summary> + /// A breaking argument at a <see cref="Reach.Use"/> site is reported once, at the site's span, with + /// <paramref name="id"/>; anything else, the meeting argument everywhere and a site the subject names + /// as silent, is reported nowhere. + /// </summary> + public static Task Verify<TAnalyzer>(TypeArgumentSubject subject, string siteName, bool breaking, string id, DiagnosticSeverity severity) + where TAnalyzer : DiagnosticAnalyzer, new() + { + var site = TypeArgumentSites.All.Single(candidate => candidate.Name == siteName); + var argument = breaking ? subject.Breaking : subject.Meeting; + var silenced = subject.SilentAt?.ContainsKey(site.Name) ?? false; + var reported = breaking && site.Reach == Reach.Use && !silenced; + var use = Fill(site.Use, subject, argument); + + var test = new AnalyzerTest<TAnalyzer>(Fill(TypeArgumentSites.Subjects + subject.Declarations, subject, argument)); + test.TestState.Sources.Add((site.File, reported ? use : Unmarked(use))); + + if (reported) + test.ExpectedDiagnostics.Add(new DiagnosticResult(id, severity).WithLocation(0)); + + return test.RunAsync(TestContext.Current.CancellationToken); + } + + private static string Fill(string source, TypeArgumentSubject subject, string argument) => + source.Replace("{C}", subject.Constraint).Replace("{A}", argument); + + private static string Unmarked(string source) => Location().Replace(source, "$1"); + + [GeneratedRegex(@"\{\|#0:(.*?)\|\}", RegexOptions.Singleline)] + private static partial Regex Location(); +} diff --git a/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSites.cs b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSites.cs new file mode 100644 index 00000000..1b14aed9 --- /dev/null +++ b/test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSites.cs @@ -0,0 +1,249 @@ +namespace CodoMetis.TypeKit.Analyzers.Tests.Variants; + +/// <summary>What a rule that judges a type argument does at a site.</summary> +public enum Reach +{ + /// <summary>The argument reaches the type parameter: a breaking one is reported once, at the site's <c>{|#0:…|}</c> span.</summary> + Use, + + /// <summary>The generic is named and no argument is used (a cref, a <c>nameof</c>, an unbound <c>typeof</c>, an error type): never reported.</summary> + Mention, + + /// <summary>Generated code that maps to no markup: never reported (<c>GeneratedCode.Report</c>).</summary> + Hidden, +} + +/// <summary>One shape of source through which a type argument reaches a type parameter.</summary> +/// <param name="Name">What the site is, as a test case names it.</param> +/// <param name="Reach">What a rule reports there.</param> +/// <param name="Use"> +/// The source of the site, a file of its own. <c>{C}</c> is the constraint the rule reads on the +/// parameter, <c>{A}</c> the type argument. Statements go through <see cref="TypeArgumentSites.Body"/>, +/// whose method has <c>a</c> of type <c>{A}</c> and the subjects' receivers in scope. +/// </param> +/// <param name="File">The file's name, which decides whether the compiler takes it for generated code.</param> +public sealed record TypeArgumentSite(string Name, Reach Reach, string Use, string File = "Use.cs") +{ + public override string ToString() => Name; +} + +/// <summary> +/// The variant catalogue for analyzer input shape (AIS) when the input is a type argument +/// (design/adr/0095): every shape of source through which a type argument reaches a type parameter. +/// </summary> +/// <remarks> +/// <para> +/// A fix to a rule that judges type arguments runs here, so that it is tested across the class of +/// input its defect lives in and not only on the one input a review found. CMTK0010's fourteen fixes of +/// 2026-10-13 and 14 (design/fix-history.md, chain A) were found one shape at a time: a method group, +/// then crefs and implementations, then extension blocks and generic attributes, then a null-conditional +/// call and <c>nameof</c>, then generated code. +/// </para> +/// <para> +/// Each site is a language form, not a CMTK0010 test: the catalogue declares its generic subjects with a +/// constraint <c>{C}</c> the rule supplies, and is run with an argument that breaks it and one that meets +/// it, so a silent site and a false report are both caught. A shape the language adds goes here, for +/// every rule at once. +/// </para> +/// </remarks> +public static class TypeArgumentSites +{ + /// <summary>The generic subjects every site uses; each type parameter the rule reads carries <c>{C}</c>.</summary> + public const string Subjects = + """ + global using System; + global using System.Runtime.CompilerServices; + global using CodoMetis.TypeKit; + + public sealed class Box<{C} T> { public static int Count => 0; } + public class Base<{C} T> { } + public delegate void Handle<{C} T>(T value); + + [AttributeUsage(AttributeTargets.All)] + public sealed class MarkAttribute<{C} T> : Attribute { } + + public static class Runner + { + public static void Run<{C} T>(T value) { } + } + + public sealed class Holder + { + public void Inst<{C} T>() { } + public void Take<{C} T>(T value) { } + } + + public static class Classic + { + public static void Ext<{C} T>(this T value) { } + } + + public sealed class Crate<T> { } + + public static class Blocks + { + extension<{C} T>(T value) + { + public void Go() { } + public int Prop => 1; + public static void Make() { } + } + + extension<{C} T>(Crate<T> crate) + { + public void Open() { } + public static Crate<T> operator +(Crate<T> left, int right) => left; + } + } + + public interface ISender { void Send<{C} T>(T value); } + public sealed class Sender : ISender { public void Send<T>(T value) { } } + public class BaseSender { public void Send<T>(T value) { } } + public sealed class DerivedSender : BaseSender, ISender { } + public class OpenSender : BaseSender, ISender { } + public abstract class Overridable { public abstract void Go<{C} T>(); } + public sealed class Overriding : Overridable { public override void Go<T>() { } } + public interface IFactory { static abstract void Make<{C} T>(); } + public sealed class Factory : IFactory { public static void Make<T>() { } } + public interface IRunner { void Run<{C} T>() { } } + public interface IFastRunner : IRunner { } + + public sealed class Bag<T> { } + public struct Cursor<T> { public T Current => default!; public bool MoveNext() => false; } + public readonly struct Waiter<T> : INotifyCompletion + { + public bool IsCompleted => true; + public T GetResult() => default!; + public void OnCompleted(Action continuation) { } + } + + public sealed class Heap<T> : System.Collections.Generic.IEnumerable<T> + { + public System.Collections.Generic.IEnumerator<T> GetEnumerator() => throw new NotSupportedException(); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public static class Patterns + { + public static Cursor<T> GetEnumerator<{C} T>(this Bag<T> bag) => default; + public static Waiter<T> GetAwaiter<{C} T>(this Bag<T> bag) => default; + public static void Deconstruct<{C} T>(this Bag<T> bag, out int first, out int second) => (first, second) = (0, 0); + public static void Add<{C} T>(this Heap<T> heap, T item) { } + } + + """; + + /// <summary>Statements in a method with a value of the argument's type and a receiver for each subject.</summary> + public static string Body(string statements) => + $$""" + public static class Use + { + public static async System.Threading.Tasks.Task Go({A} a, Holder? holder, Bag<{A}> bag, Crate<{A}> crate, object o) + { + {{statements}} + await System.Threading.Tasks.Task.CompletedTask; + } + } + """; + + private const string GeneratedHeader = "// <auto-generated/>\n"; + + private const string WrittenCall = "public static class Use { public static void Go({A} a) => Runner.Run<{|#0:{A}|}>(a); }"; + + private const string InferredCall = "public static class Use { public static void Go({A} a) => {|#0:Runner.Run(a)|}; }"; + + public static IReadOnlyList<TypeArgumentSite> All { get; } = + [ + // Written in a generic name. + new("a field's type", Reach.Use, "public static class Use { public static Box<{|#0:{A}|}>? Field; }"), + new("a parameter's type", Reach.Use, "public static class Use { public static void Go(Box<{|#0:{A}|}> box) { } }"), + new("a return type", Reach.Use, "public static class Use { public static Box<{|#0:{A}|}>? Go() => null; }"), + new("a local's type", Reach.Use, Body("Box<{|#0:{A}|}>? local = null;")), + new("an object creation", Reach.Use, Body("_ = new Box<{|#0:{A}|}>();")), + new("a closed typeof", Reach.Use, Body("_ = typeof(Box<{|#0:{A}|}>);")), + new("a static member of the closed type", Reach.Use, Body("_ = Box<{|#0:{A}|}>.Count;")), + new("a type pattern", Reach.Use, Body("_ = o is Box<{|#0:{A}|}>;")), + new("a cast", Reach.Use, Body("_ = (Box<{|#0:{A}|}>?)o;")), + new("a default expression", Reach.Use, Body("_ = default(Box<{|#0:{A}|}>);")), + new("an array of the closed type", Reach.Use, Body("Box<{|#0:{A}|}>[] array = [];")), + new("a nested type argument", Reach.Use, Body("System.Collections.Generic.List<Box<{|#0:{A}|}>>? list = null;")), + new("a tuple element", Reach.Use, Body("(Box<{|#0:{A}|}>?, int) pair = (null, 1);")), + new("a generic delegate", Reach.Use, Body("Handle<{|#0:{A}|}>? handle = null;")), + new("a base type", Reach.Use, "public class Use : Base<{|#0:{A}|}> { }"), + new("a constraint clause", Reach.Use, "public static class Use { public static void Go<TBox>() where TBox : Base<{|#0:{A}|}> { } }"), + new("a using alias", Reach.Use, "using Alias = Box<{|#0:{A}|}>;\npublic static class Use { public static Alias? Field; }"), + new("a generic attribute", Reach.Use, "[Mark<{|#0:{A}|}>] public sealed class Use { }"), + new("a generic attribute named in full", Reach.Use, "[MarkAttribute<{|#0:{A}|}>] public sealed class Use { }"), + + // A method reached by name. + new("a call with written arguments", Reach.Use, Body("Runner.Run<{|#0:{A}|}>(a);")), + new("a call with inferred arguments", Reach.Use, Body("{|#0:Runner.Run(a)|};")), + new("a method group, inferred", Reach.Use, Body("Action<{A}> group = {|#0:Runner.Run|};")), + new("a method group, written", Reach.Use, Body("Action<{A}> group = Runner.Run<{|#0:{A}|}>;")), + new("a method group as an argument", Reach.Use, Body("Hand({|#0:Runner.Run|});\nstatic void Hand(Action<{A}> handler) { }")), + new("a method group cast", Reach.Use, Body("_ = (Action<{A}>){|#0:Runner.Run|};")), + new("a generic local function, inferred", Reach.Use, Body("{|#0:Local(a)|};\nstatic void Local<{C} T>(T value) { }")), + new("a call in a lambda", Reach.Use, Body("Action act = () => Runner.Run<{|#0:{A}|}>(a);")), + new("a null-conditional call, written", Reach.Use, Body("holder?.Inst<{|#0:{A}|}>();")), + new("a null-conditional call, inferred", Reach.Use, Body("holder?{|#0:.Take(a)|};")), + new("a classic extension method", Reach.Use, Body("{|#0:a.Ext()|};")), + new("a classic extension method called statically", Reach.Use, Body("{|#0:Classic.Ext(a)|};")), + + // A C# 14 extension block's type parameter, inferred from the receiver. + new("an extension block's method", Reach.Use, Body("{|#0:a.Go()|};")), + new("an extension block's property", Reach.Use, Body("_ = {|#0:a.Prop|};")), + new("an extension block's static member", Reach.Use, Body("{|#0:{A}.Make()|};")), + new("an extension block's method group", Reach.Use, Body("Action group = {|#0:crate.Open|};")), + new("an extension block's operator", Reach.Use, Body("_ = {|#0:crate + 1|};")), + new("an extension block's member, lowered", Reach.Use, Body("{|#0:Blocks.Go(a)|};")), + + // A constraint declared on another member than the one called. + new("an interface method's implementation", Reach.Use, Body("new Sender().Send<{|#0:{A}|}>(a);")), + new("an override", Reach.Use, Body("new Overriding().Go<{|#0:{A}|}>();")), + new("a base class's method implementing an interface for a derived class", Reach.Use, Body("new DerivedSender().Send<{|#0:{A}|}>(a);")), + new("a base class's implementation called unqualified in the derived class", Reach.Use, "public sealed class Use : BaseSender, ISender { public void Go({A} a) => Send<{|#0:{A}|}>(a); }"), + new("a base class's implementation called through base.", Reach.Use, "public sealed class Use : BaseSender, ISender { public void Go({A} a) => base.Send<{|#0:{A}|}>(a); }"), + new("a base class's implementation through a constrained type parameter", Reach.Use, "public static class Use { public static void Go<TSender>(TSender sender, {A} a) where TSender : OpenSender => sender.Send<{|#0:{A}|}>(a); }"), + new("a static method implementing a static abstract one", Reach.Use, Body("Factory.Make<{|#0:{A}|}>();")), + new("a static abstract method through a type parameter", Reach.Use, "public static class Use { public static void Go<TFactory>() where TFactory : IFactory => TFactory.Make<{|#0:{A}|}>(); }"), + new("a default interface method through a derived interface", Reach.Use, "public static class Use { public static void Go(IFastRunner runner) => runner.Run<{|#0:{A}|}>(); }"), + + // A method the compiler binds by pattern; nothing names it. + new("a foreach's GetEnumerator", Reach.Use, Body("foreach (var item in {|#0:bag|}) { }")), + new("an await's GetAwaiter", Reach.Use, Body("_ = {|#0:await bag|};")), + new("a deconstruction", Reach.Use, Body("{|#0:var (p, q) = bag|};")), + new("a positional pattern", Reach.Use, Body("_ = bag is {|#0:(1, 2)|};")), + new("a collection expression's Add", Reach.Use, Body("Heap<{A}> heap = {|#0:[a]|};")), + + // Named, not used. + new("a cref to the generic type", Reach.Mention, "/// <summary>See <see cref=\"Box{T}\"/> and <see cref=\"Base{T}\"/>.</summary>\npublic static class Use { }"), + new("a cref to the generic method", Reach.Mention, "/// <summary>See <see cref=\"Runner.Run{T}(T)\"/>.</summary>\npublic static class Use { }"), + new("a nameof of the closed type", Reach.Mention, Body("_ = nameof(Box<{A}>);")), + new("a nameof of a member of the closed type", Reach.Mention, Body("_ = nameof(Box<{A}>.Count);")), + new("an unbound typeof", Reach.Mention, Body("_ = typeof(Box<>);")), + new("an argument that does not exist", Reach.Mention, "public static class Use { public static Box<{|CS0246:Missing|}>? Field; }"), + + // Generated code: reported only where it maps to markup. + new("generated: an auto-generated header, written", Reach.Hidden, GeneratedHeader + WrittenCall), + new("generated: an auto-generated header, inferred", Reach.Hidden, GeneratedHeader + InferredCall), + new("generated: a .g.cs file", Reach.Hidden, WrittenCall, "Use.g.cs"), + new("generated: under #line hidden", Reach.Hidden, "#line hidden\n" + WrittenCall + "\n#line default"), + new("generated: mapped to Razor markup", Reach.Use, GeneratedHeader + "#line 7 \"Component.razor\"\n" + WrittenCall + "\n#line default", "Component.razor.g.cs"), + ]; +} + +/// <summary> +/// What a rule supplies to run the catalogue: the constraint it reads, an argument that breaks it and +/// one that meets it, the declarations those need, and the sites where the rule, by a decision, is +/// silent, each with the decision. +/// </summary> +public sealed record TypeArgumentSubject( + string Name, + string Constraint, + string Breaking, + string Meeting, + string Declarations, + IReadOnlyDictionary<string, string>? SilentAt = null) +{ + public override string ToString() => Name; +} diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/ComponentNullablePropertyTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/ComponentNullablePropertyTests.cs new file mode 100644 index 00000000..393af919 --- /dev/null +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/ComponentNullablePropertyTests.cs @@ -0,0 +1,315 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Text.Json.Serialization; +using CodoMetis.TypeKit.CompilerServices; +using CodoMetis.TypeKit.Generators.Probes; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.AspNetCore.Tests; + +/// <summary> +/// A nullable value object that is a component admits null as a property wherever the serializer's contract says the property +/// is nullable, as its wrapped type's property does there: a get-only property bound by the constructor, a field under +/// <c>IncludeFields</c>, and a reference-type property in <c>#nullable disable</c> code (decision 36). Each is written as +/// ASP.NET writes a settable annotated property of the same value object beside it, <c>oneOf: [null, component]</c>, and +/// under OpenAPI 2.0 as its <c>allOf: [{x-nullable: true}]</c> without the reference (KB-026). +/// </summary> +/// <remarks> +/// ASP.NET puts the null around a component only for a property whose <c>NullabilityInfoContext</c> write state is nullable, +/// which a get-only property (unknown), a field (not a property) and an oblivious reference type are not. The wrapped type, +/// which is no component, gets its null from <c>JsonPropertyInfo</c> in all three. Without the mark the value object's +/// property was the bare reference where the wrapped type's admitted null (C21OpenapiSchemaParityProbe, confirmed by the +/// code-analyst run of 2026-10-18). +/// </remarks> +public sealed class ComponentNullablePropertyTests +{ + private static readonly ConcurrentDictionary<OpenApiSpecVersion, Task<OpenApiJson>> Documents = new(); + + public static TheoryData<OpenApiSpecVersion, string, string> Positions() + { + var data = new TheoryData<OpenApiSpecVersion, string, string>(); + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }) + { + data.Add(version, "get-only", "GetOnlyTimeSpan"); + data.Add(version, "field", "FieldTimeSpan"); + data.Add(version, "oblivious", "ObliviousString"); + } + + return data; + } + + [Theory] + [MemberData(nameof(Positions))] + public async Task A_nullable_value_object_component_admits_null_where_its_wrapped_type_does(OpenApiSpecVersion version, string position, string twin) + { + var document = await Documents.GetOrAdd(version, static v => ProbeHost.DocumentAsync( + openApi => openApi.AddTypeKit(), + json: json => json.SerializerOptions.IncludeFields = true, + version: v, + endpoints: app => + { + app.MapPost("/get-only/vo", ([FromBody] GetOnlyDuration v) => 0); + app.MapPost("/get-only/twin", ([FromBody] GetOnlyTimeSpan v) => 0); + app.MapPost("/field/vo", ([FromBody] ComponentField<ProbeDuration?> v) => 0); + app.MapPost("/field/twin", ([FromBody] FieldTimeSpan v) => 0); + app.MapPost("/oblivious/vo", ([FromBody] ObliviousLabel v) => 0); + app.MapPost("/oblivious/twin", ([FromBody] ObliviousString v) => 0); + })); + + var holder = Body(document, version, $"/{position}/vo"); + var valueObject = Property(document, holder, "p"); + // ASP.NET's own form for a nullable property of the component, settable and annotated, beside it. + var asAspNetWritesIt = Property(document, holder, "q"); + var wrapped = Property(document, Body(document, version, $"/{position}/twin"), "p"); + + if (version == OpenApiSpecVersion.OpenApi2_0) + (asAspNetWritesIt["allOf"]?[0]?["x-nullable"]?.GetValue<bool>() == true && asAspNetWritesIt["$ref"] is null).ShouldBeTrue(asAspNetWritesIt.ToJsonString()); + else + asAspNetWritesIt["oneOf"]?[1]?["$ref"].ShouldNotBeNull(asAspNetWritesIt.ToJsonString()); + + valueObject.ShouldDescribeTheSameAs(asAspNetWritesIt, $"{position}: the value object's property against its settable annotated sibling"); + + // The wrapped type admits null there, and where the null-or keeps the reference, it is the component's content or null. + JsonNode.DeepEquals(NullablePositionTests.WithoutNull(wrapped), wrapped).ShouldBeFalse($"{twin}'s property admits no null: {wrapped.ToJsonString()}"); + if (version != OpenApiSpecVersion.OpenApi2_0) + NullablePositionTests.WithoutNull(Resolve(document, valueObject["oneOf"]![1]!)).ShouldDescribeTheSameAs(NullablePositionTests.WithoutNull(wrapped), $"{position} against {twin}"); + } + + /// <summary> + /// The input class: a property's nullability as <c>NullabilityInfoContext</c> reads it (ASP.NET's, for a component) against + /// <c>JsonPropertyInfo</c> (the serializer's, and ASP.NET's for an inlined schema). Each variant is a value object's member + /// and its wrapped type's, at the same declared nullability; the reference forms are the settable members beside them. + /// </summary> + private static readonly (string Name, Type ValueObject, Type Wrapped)[] Variants = + [ + // A struct value object, made nullable or not. + ("settable", typeof(NvSettable<ProbeDuration?>), typeof(NvSettable<TimeSpan?>)), + ("get-only", typeof(NvGetOnly<ProbeDuration?>), typeof(NvGetOnly<TimeSpan?>)), + ("computed", typeof(NvComputed<ProbeDuration?>), typeof(NvComputed<TimeSpan?>)), + ("field", typeof(NvField<ProbeDuration?>), typeof(NvField<TimeSpan?>)), + ("readonly-field", typeof(NvReadOnlyField<ProbeDuration?>), typeof(NvReadOnlyField<TimeSpan?>)), + ("init", typeof(NvInit<ProbeDuration?>), typeof(NvInit<TimeSpan?>)), + ("required-init", typeof(NvRequiredInit<ProbeDuration?>), typeof(NvRequiredInit<TimeSpan?>)), + ("positional", typeof(NvPositional<ProbeDuration?>), typeof(NvPositional<TimeSpan?>)), + ("private-set-included", typeof(NvIncluded<ProbeDuration?>), typeof(NvIncluded<TimeSpan?>)), + ("struct-holder-get-only", typeof(NvStructGetOnly<ProbeDuration?>), typeof(NvStructGetOnly<TimeSpan?>)), + // Types of their own: ASP.NET names the component of NvSettable<TimeSpan> and of NvSettable<TimeSpan?> alike, and + // describes both as the first it meets. + ("settable-not-nullable", typeof(NvSettableNotNullable<ProbeDuration>), typeof(NvSettableNotNullable<TimeSpan>)), + ("get-only-not-nullable", typeof(NvGetOnlyNotNullable<ProbeDuration>), typeof(NvGetOnlyNotNullable<TimeSpan>)), + ("field-not-nullable", typeof(NvFieldNotNullable<ProbeDuration>), typeof(NvFieldNotNullable<TimeSpan>)), + // A class value object, annotated, oblivious, or under a nullability attribute. + ("label-settable", typeof(NvLabelSettable), typeof(NvStringSettable)), + ("label-get-only", typeof(NvLabelGetOnly), typeof(NvStringGetOnly)), + ("label-field", typeof(NvLabelField), typeof(NvStringField)), + ("label-required-init", typeof(NvLabelRequiredInit), typeof(NvStringRequiredInit)), + ("label-positional", typeof(NvLabelPositional), typeof(NvStringPositional)), + ("label-oblivious", typeof(NvLabelOblivious), typeof(NvStringOblivious)), + ("label-oblivious-get-only", typeof(NvLabelObliviousGetOnly), typeof(NvStringObliviousGetOnly)), + ("label-oblivious-field", typeof(NvLabelObliviousField), typeof(NvStringObliviousField)), + ("label-allow-null", typeof(NvLabelAllowNull), typeof(NvStringAllowNull)), + ("label-maybe-null", typeof(NvLabelMaybeNull), typeof(NvStringMaybeNull)), + ("label-disallow-null", typeof(NvLabelDisallowNull), typeof(NvStringDisallowNull)), + ("label-not-null", typeof(NvLabelNotNull), typeof(NvStringNotNull)), + ("label-not-nullable", typeof(NvLabelNotNullable), typeof(NvStringNotNullable)), + ("label-get-only-not-nullable", typeof(NvLabelGetOnlyNotNullable), typeof(NvStringGetOnlyNotNullable)), + // A class value object typed by a type parameter, whose annotation the reflected property does not carry. + ("label-type-parameter-settable", typeof(NvSettable<ProbeLabel>), typeof(NvSettable<string>)), + ("label-type-parameter-get-only", typeof(NvGetOnly<ProbeLabel>), typeof(NvGetOnly<string>)), + ("label-type-parameter-field", typeof(NvField<ProbeLabel>), typeof(NvField<string>)), + ("label-type-parameter-required-init", typeof(NvRequiredInit<ProbeLabel>), typeof(NvRequiredInit<string>)), + ]; + + private static readonly ConcurrentDictionary<(OpenApiSpecVersion, bool, bool), Task<OpenApiJson>> VariantDocuments = new(); + + public static TheoryData<OpenApiSpecVersion, bool, bool, string> VariantRows() + { + var data = new TheoryData<OpenApiSpecVersion, bool, bool, string>(); + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }) + foreach (var components in new[] { true, false }) + foreach (var respectNullableAnnotations in new[] { false, true }) + foreach (var (name, _, _) in Variants) + data.Add(version, components, respectNullableAnnotations, name); + + return data; + } + + [Theory] + [MemberData(nameof(VariantRows))] + public async Task A_value_object_member_admits_null_exactly_where_its_wrapped_type_s_does( + OpenApiSpecVersion version, bool components, bool respectNullableAnnotations, string variant) + { + var document = await VariantDocuments.GetOrAdd((version, components, respectNullableAnnotations), static key => ProbeHost.DocumentAsync( + openApi => + { + openApi.AddTypeKit(); + if (!key.Item2) + openApi.CreateSchemaReferenceId = type => + (Nullable.GetUnderlyingType(type.Type) ?? type.Type).GetCustomAttribute<GeneratedValueObjectAttribute>() is not null + ? null + : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + }, + json: json => + { + json.SerializerOptions.IncludeFields = true; + json.SerializerOptions.RespectNullableAnnotations = key.Item3; + }, + version: key.Item1, + endpoints: app => + { + foreach (var (name, valueObject, wrapped) in Variants) + { + MapBody(app, $"/variants/{name}/vo", valueObject); + MapBody(app, $"/variants/{name}/twin", wrapped); + } + })); + + JsonNode Member(string name, string side) => Property(document, Body(document, version, $"/variants/{name}/{side}"), "p"); + bool AdmitsNull(JsonNode schema) => !JsonNode.DeepEquals(NullablePositionTests.WithoutNull(schema), schema); + + var valueObject = Member(variant, "vo"); + var wrapped = Member(variant, "twin"); + var label = variant.StartsWith("label-", StringComparison.Ordinal); + + if (components) + { + // Not vacuous: ASP.NET's two forms differ, and the wrapped type admits null exactly where its declaration says. + JsonNode.DeepEquals(Member(label ? "label-settable" : "settable", "vo"), Member(label ? "label-not-nullable" : "settable-not-nullable", "vo")).ShouldBeFalse(); + AdmitsNull(wrapped).ShouldBe(!variant.EndsWith("-not-nullable", StringComparison.Ordinal), wrapped.ToJsonString()); + + // ASP.NET's own forms for the component: its settable annotated property, and its property that is not nullable. + var reference = AdmitsNull(wrapped) + ? Member(label ? "label-settable" : "settable", "vo") + : Member(label ? "label-not-nullable" : "settable-not-nullable", "vo"); + valueObject.ShouldDescribeTheSameAs(reference, $"{variant}, the wrapped type's {wrapped.ToJsonString()}"); + } + else + { + NullablePositionTests.Inline(document, valueObject)!.ShouldDescribeTheSameAs(NullablePositionTests.Inline(document, wrapped)!, variant); + } + } + + private static void MapBody(WebApplication app, string path, Type type) => + typeof(ComponentNullablePropertyTests).GetMethod(nameof(MapBodyOf), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(type).Invoke(null, [app, path]); + + private static void MapBodyOf<T>(WebApplication app, string path) => app.MapPost(path, ([FromBody] T v) => 0); + + private static JsonNode Body(OpenApiJson document, OpenApiSpecVersion version, string path) + { + var operation = document.Root["paths"]?[path]?["post"] ?? throw new InvalidOperationException($"No operation post {path}."); + var body = version == OpenApiSpecVersion.OpenApi2_0 + ? operation["parameters"]!.AsArray().Single(p => p!["in"]!.GetValue<string>() == "body")!["schema"]! + : operation["requestBody"]!["content"]!["application/json"]!["schema"]!; + + return Resolve(document, body); + } + + private static JsonNode Property(OpenApiJson document, JsonNode holder, string name) => + holder["properties"]?[name]?.DeepClone() ?? throw new InvalidOperationException($"No property '{name}' in {holder.ToJsonString()}."); + + private static JsonNode Resolve(OpenApiJson document, JsonNode schema) => + schema["$ref"] is { } reference + ? (document.Root["components"]?["schemas"] ?? document.Root["definitions"])?[reference.GetValue<string>().Split('/')[^1]] + ?? throw new InvalidOperationException($"No schema '{reference}'.") + : schema; +} + +public sealed class GetOnlyDuration(ProbeDuration? p) +{ + public ProbeDuration? P { get; } = p; + + public ProbeDuration? Q { get; set; } +} + +public sealed class GetOnlyTimeSpan(TimeSpan? p) +{ + public TimeSpan? P { get; } = p; + + public TimeSpan? Q { get; set; } +} + +/// <summary>A field of the type argument, serialized under <c>IncludeFields</c>.</summary> +public sealed class ComponentField<T> +{ + public T P = default!; + + public T Q { get; set; } = default!; +} + +public sealed class FieldTimeSpan +{ + public TimeSpan? P; + + public TimeSpan? Q { get; set; } +} + +#nullable disable +public sealed class ObliviousLabel +{ + public ProbeLabel P { get; set; } +#nullable enable + public ProbeLabel? Q { get; set; } +#nullable disable +} + +public sealed class ObliviousString +{ + public string P { get; set; } +#nullable enable + public string? Q { get; set; } +#nullable disable +} +#nullable restore + +// The variants: the member is p, each beside its wrapped type's (Nv...String... beside Nv...Label...). +public sealed class NvSettable<T> { public T P { get; set; } = default!; } +public sealed class NvGetOnly<T>(T p) { public T P { get; } = p; } +public sealed class NvComputed<T> { public T P => default!; } +public sealed class NvField<T> { public T P = default!; } +public sealed class NvReadOnlyField<T>(T p) { public readonly T P = p; } +public sealed class NvInit<T> { public T P { get; init; } = default!; } +public sealed class NvRequiredInit<T> { public required T P { get; init; } } +public sealed record NvPositional<T>(T P); +public sealed class NvIncluded<T> { [JsonInclude] public T P { get; private set; } = default!; } +public readonly struct NvStructGetOnly<T>(T p) { public T P { get; } = p; } +public sealed class NvSettableNotNullable<T> { public T P { get; set; } = default!; } +public sealed class NvGetOnlyNotNullable<T>(T p) { public T P { get; } = p; } +public sealed class NvFieldNotNullable<T> { public T P = default!; } + +public sealed class NvLabelSettable { public ProbeLabel? P { get; set; } } +public sealed class NvStringSettable { public string? P { get; set; } } +public sealed class NvLabelGetOnly(ProbeLabel? p) { public ProbeLabel? P { get; } = p; } +public sealed class NvStringGetOnly(string? p) { public string? P { get; } = p; } +public sealed class NvLabelField { public ProbeLabel? P; } +public sealed class NvStringField { public string? P; } +public sealed class NvLabelRequiredInit { public required ProbeLabel? P { get; init; } } +public sealed class NvStringRequiredInit { public required string? P { get; init; } } +public sealed record NvLabelPositional(ProbeLabel? P); +public sealed record NvStringPositional(string? P); +public sealed class NvLabelAllowNull { [AllowNull] public ProbeLabel P { get; set; } = ProbeLabel.From("x"); } +public sealed class NvStringAllowNull { [AllowNull] public string P { get; set; } = ""; } +public sealed class NvLabelMaybeNull { [MaybeNull] public ProbeLabel P { get; set; } = ProbeLabel.From("x"); } +public sealed class NvStringMaybeNull { [MaybeNull] public string P { get; set; } = ""; } +public sealed class NvLabelDisallowNull { [DisallowNull] public ProbeLabel? P { get; set; } } +public sealed class NvStringDisallowNull { [DisallowNull] public string? P { get; set; } } +public sealed class NvLabelNotNull { [NotNull] public ProbeLabel? P { get; set; } = ProbeLabel.From("x"); } +public sealed class NvStringNotNull { [NotNull] public string? P { get; set; } = ""; } +public sealed class NvLabelNotNullable { public ProbeLabel P { get; set; } = ProbeLabel.From("x"); } +public sealed class NvStringNotNullable { public string P { get; set; } = ""; } +public sealed class NvLabelGetOnlyNotNullable(ProbeLabel p) { public ProbeLabel P { get; } = p; } +public sealed class NvStringGetOnlyNotNullable(string p) { public string P { get; } = p; } + +#nullable disable +public sealed class NvLabelOblivious { public ProbeLabel P { get; set; } } +public sealed class NvStringOblivious { public string P { get; set; } } +public sealed class NvLabelObliviousGetOnly(ProbeLabel p) { public ProbeLabel P { get; } = p; } +public sealed class NvStringObliviousGetOnly(string p) { public string P { get; } = p; } +public sealed class NvLabelObliviousField { public ProbeLabel P; } +public sealed class NvStringObliviousField { public string P; } +#nullable restore diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedParameterTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedParameterTests.cs new file mode 100644 index 00000000..05252090 --- /dev/null +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedParameterTests.cs @@ -0,0 +1,245 @@ +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Reflection; +using System.Text.Json.Serialization; +using CodoMetis.TypeKit.CompilerServices; +using CodoMetis.TypeKit.Generators.Probes; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.AspNetCore.Tests; + +/// <summary> +/// A parameter outside the body binds through <c>TryParse</c>, not JSON, so a converter that describes its type, or the +/// type a value object over it wraps, says nothing of it: a <c>decimal</c>, a <c>decimal?</c>, and a value object over +/// either, as a route, query, header or form parameter and as the items of a query or header array, is what ASP.NET +/// writes for the same parameter of the underlying type in a host without the converter and without <c>AddTypeKit()</c>. +/// That holds in MVC and in minimal APIs, in OpenAPI 3.1, 3.0 and 2.0, with everything inlined, value objects alone +/// inlined or components, under both number handlings, for a value object declared here and one in a referenced +/// assembly, which ASP.NET hands to the transformers differently. +/// </summary> +/// <remarks> +/// Found by the code analyst (2026-10-16, A2 and A3): a query or header array of a described type lost its +/// <c>items</c>, and a value object over a described type was its JSON spelling (<c>"type":"string"</c>, whose +/// pattern refuses <c>1.50</c>, which <c>TryParse</c> binds) as a header, a form field, a query scalar and a query or +/// header array's items. Where value objects are components, a parameter of one over a described type is written +/// inline (decision 106): the component is the JSON positions', shared with every use. +/// </remarks> +public sealed class DescribedParameterTests +{ + private static readonly ConcurrentDictionary<(OpenApiSpecVersion, string, bool Strict, bool Described), Task<OpenApiJson>> Documents = new(); + + /// <summary>The parameter in the described host, and the parameter of the underlying type in the plain host it is compared with.</summary> + private static readonly (string Parameter, string Twin)[] Pairs = + [ + // The described type itself, a sibling of c56d38a in the array position (A2). + (Key<decimal>(), Key<decimal>()), + (Key<decimal?>(), Key<decimal?>()), + // A value object over it, declared in this assembly and in a referenced one (A3), which ASP.NET hands to the + // transformers differently. The items of a nullable one's array admit null, as decimal?[]'s do, where the + // parameter itself does not: the element is told from the parameter by its type (IsParameterItself; the + // analyst's seed S2, which no test caught before these rows, fails the 72 query-items and header-items rows). + (Key<ParameterMoney>(), Key<decimal>()), + (Key<ParameterMoney?>(), Key<decimal?>()), + (Key<ProbeAmount>(), Key<decimal>()), + (Key<ProbeAmount?>(), Key<decimal?>()), + ]; + + private static readonly Type[] TypeArguments = + [typeof(decimal), typeof(decimal?), typeof(ParameterMoney), typeof(ParameterMoney?), typeof(ProbeAmount), typeof(ProbeAmount?)]; + + private static readonly string[] References = ["inline-all", "inline-value-objects", "components"]; + + private static readonly OpenApiSpecVersion[] Versions = [OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0]; + + public static TheoryData<OpenApiSpecVersion, string, bool, string, string> Positions() + { + var data = new TheoryData<OpenApiSpecVersion, string, bool, string, string>(); + foreach (var version in Versions) + foreach (var references in References) + foreach (var strict in new[] { false, true }) + foreach (var host in new[] { "mvc", "minimal" }) + foreach (var position in new[] { "route", "query", "query-default", "header", "form", "query-items", "header-items" }) + data.Add(version, references, strict, host, position); + + return data; + } + + [Theory] + [MemberData(nameof(Positions))] + public async Task A_parameter_of_a_described_type_or_a_value_object_over_one_is_what_ASP_NET_writes_for_the_underlying_type_without_the_converter( + OpenApiSpecVersion version, string references, bool strict, string host, string position) + { + var described = await DocumentAsync(version, references, strict, described: true); + var plain = await DocumentAsync(version, references, strict, described: false); + + var differences = new List<string>(); + foreach (var (parameter, twin) in Pairs) + { + // A value object's default in MVC stops ASP.NET's own document (DefaultController). + if (position == "query-default" && host == "mvc" && parameter != twin) continue; + + var actual = NullablePositionTests.Inline(described, NullablePositionTests.Schema(described, version, host, position, parameter)); + var expected = NullablePositionTests.Inline(plain, NullablePositionTests.Schema(plain, version, host, position, twin)); + + if (!JsonNode.DeepEquals(actual, expected)) + differences.Add($"{parameter}: {actual?.ToJsonString()}\n {twin} without the converter: {expected?.ToJsonString()}"); + } + + differences.ShouldBeEmpty(string.Join("\n", differences)); + } + + public static TheoryData<OpenApiSpecVersion, string, bool> DocumentVariants() + { + var data = new TheoryData<OpenApiSpecVersion, string, bool>(); + foreach (var version in Versions) + foreach (var references in References) + foreach (var strict in new[] { false, true }) + data.Add(version, references, strict); + + return data; + } + + /// <summary> + /// The JSON positions of the same value objects keep the converter's description in the same document, where the + /// parameters come first: a component a parameter registered first would say the parameter's schema for every use + /// (KB-027). + /// </summary> + [Theory] + [MemberData(nameof(DocumentVariants))] + public async Task A_value_object_s_JSON_positions_keep_the_described_spelling_beside_parameters_of_it(OpenApiSpecVersion version, string references, bool strict) + { + var document = await DocumentAsync(version, references, strict, described: true); + var body = NullablePositionTests.Inline(document, Body(document, version))!; + + foreach (var property in new[] { "money", "amount" }) + { + var schema = body["properties"]![property]!.AsObject(); + schema.ShouldDescribeTheSameAs(DecimalString, $"the property '{property}'"); + } + } + + private static readonly JsonObject DecimalString = new() + { + ["pattern"] = @"^-?(?:0|[1-9]\d*)(?:\.\d*[1-9])?$", + ["type"] = "string", + ["format"] = "decimal", + }; + + private static JsonNode Body(OpenApiJson document, OpenApiSpecVersion version) + { + var operation = document.Root["paths"]!["/minimal/json"]!["post"]!; + return version == OpenApiSpecVersion.OpenApi2_0 + ? operation["parameters"]!.AsArray().Single(p => p!["in"]!.GetValue<string>() == "body")!["schema"]! + : operation["requestBody"]!["content"]!["application/json"]!["schema"]!; + } + + private static Task<OpenApiJson> DocumentAsync(OpenApiSpecVersion version, string references, bool strict, bool described) => + Documents.GetOrAdd((version, references, strict, described), static key => ProbeHost.DocumentAsync( + openApi => + { + if (key.Item2 == "inline-all") openApi.CreateSchemaReferenceId = _ => null; + if (key.Described) openApi.AddTypeKit(); + + // The README's way to inline value objects alone. + if (key.Item2 == "inline-value-objects") + openApi.CreateSchemaReferenceId = type => + (Nullable.GetUnderlyingType(type.Type) ?? type.Type).GetCustomAttribute<GeneratedValueObjectAttribute>() is not null + ? null + : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + }, + json: json => + { + if (key.Strict) json.SerializerOptions.NumberHandling = JsonNumberHandling.Strict; + if (key.Described) json.SerializerOptions.Converters.Add(new DecimalStringJsonConverter()); + }, + version: key.Item1, + endpoints: MapMinimal, + controllers: [.. TypeArguments.Select(type => typeof(ParameterController<>).MakeGenericType(type)), typeof(DefaultController)], + services: services => services.Configure<MvcOptions>(mvc => mvc.Conventions.Add(new ParameterRoutes())))); + + private static string Key<T>() => Key(typeof(T)); + + private static string Key(Type type) => Nullable.GetUnderlyingType(type) is { } held ? held.Name + "-nullable" : type.Name; + + private static void MapMinimal(WebApplication app) + { + Map<decimal>(app); Map<decimal?>(app); Map<ParameterMoney>(app); Map<ParameterMoney?>(app); Map<ProbeAmount>(app); Map<ProbeAmount?>(app); + + // A parameter with a default, which ASP.NET writes in the parameter's schema, or on the reference to a component. + // A [DefaultValue] that the type converter reads: minimal APIs cannot bind a C# default of a type parameter, nor + // default(T) of a struct ("Argument types do not match"), and MVC's attributes cannot name a type parameter. + app.MapGet("/minimal/Decimal/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(decimal), "1.5")] decimal v) => 0); + app.MapGet("/minimal/Decimal-nullable/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(decimal), "1.5")] decimal? v) => 0); +#pragma warning disable ASP0020 // ParameterMoney implements IParsable once generated; a query parameter with an attribute meets it too + app.MapGet("/minimal/ParameterMoney/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(ParameterMoney), "1.5")] ParameterMoney v) => 0); + app.MapGet("/minimal/ParameterMoney-nullable/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(ParameterMoney), "1.5")] ParameterMoney? v) => 0); +#pragma warning restore ASP0020 + app.MapGet("/minimal/ProbeAmount/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(ProbeAmount), "1.5")] ProbeAmount v) => 0); + app.MapGet("/minimal/ProbeAmount-nullable/query-default", ([FromQuery(Name = "v"), DefaultValue(typeof(ProbeAmount), "1.5")] ProbeAmount? v) => 0); + + // A JSON position of each value object, after the parameters. + app.MapPost("/minimal/json", ([FromBody] ParameterBody body) => 0); + } + + private static void Map<T>(IEndpointRouteBuilder app) + { + var at = "/minimal/" + Key<T>(); + app.MapGet(at + "/query", ([FromQuery(Name = "v")] T v) => 0); + app.MapGet(at + "/header", ([FromHeader(Name = "v")] T v) => 0); + app.MapGet(at + "/query-items", ([FromQuery(Name = "v")] T[] v) => 0); + app.MapGet(at + "/header-items", ([FromHeader(Name = "v")] T[] v) => 0); + app.MapPost(at + "/form", ([FromForm(Name = "v")] T v) => 0).DisableAntiforgery(); + app.MapGet(at + "/route/{v}", (T v) => 0); + } + + public sealed record ParameterBody(ParameterMoney Money, ProbeAmount Amount); + + [ApiController] + public sealed class ParameterController<T> : ControllerBase + { + [HttpGet("query")] public int Query([FromQuery(Name = "v")] T v) => 0; + [HttpGet("header")] public int Header([FromHeader(Name = "v")] T v) => 0; + [HttpGet("query-items")] public int QueryItems([FromQuery(Name = "v")] T[] v) => 0; + [HttpGet("header-items")] public int HeaderItems([FromHeader(Name = "v")] T[] v) => 0; + [HttpPost("form")] public int Form([FromForm(Name = "v")] T v) => 0; + [HttpGet("route/{v}")] public int Route(T v) => 0; + } + + /// <summary> + /// A query parameter with a default, which an attribute cannot give a type parameter. Not of a value object: MVC + /// describes it by ASP.NET's placeholder <see cref="string"/>, and ASP.NET throws writing the value object's default as + /// a string, with or without <c>AddTypeKit()</c>. + /// </summary> + [ApiController] + public sealed class DefaultController : ControllerBase + { + [HttpGet("mvc/Decimal/query-default")] public int Decimal([FromQuery(Name = "v"), DefaultValue(typeof(decimal), "1.5")] decimal v) => 0; + [HttpGet("mvc/Decimal-nullable/query-default")] public int NullableDecimal([FromQuery(Name = "v"), DefaultValue(typeof(decimal), "1.5")] decimal? v) => 0; + } + + /// <summary>Puts each closed <see cref="ParameterController{T}"/> under <c>mvc/{its type argument}</c>.</summary> + private sealed class ParameterRoutes : IControllerModelConvention + { + public void Apply(ControllerModel controller) + { + if (!controller.ControllerType.IsGenericType || controller.ControllerType.GetGenericTypeDefinition() != typeof(ParameterController<>)) return; + + controller.Selectors.Clear(); + controller.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel(new RouteAttribute("mvc/" + Key(controller.ControllerType.GenericTypeArguments[0]))), + }); + } + } +} + +/// <summary>A value object over a described type, declared in the endpoints' own assembly.</summary> +public readonly partial record struct ParameterMoney : IValue<decimal>; diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedSchemaTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedSchemaTests.cs index 4ed5aea1..d00ee653 100644 --- a/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedSchemaTests.cs +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/DescribedSchemaTests.cs @@ -198,6 +198,75 @@ public async Task A_parameter_keeps_ASP_NET_s_own_description(OpenApiSpecVersion described.Parameter("/probes/kinds", "get", "amount").ShouldDescribeTheSameAs(plain.Parameter("/probes/kinds", "get", "amount"), "a value object over a decimal"); } + /// <summary> + /// The input class of a parameter of a described type: where it binds from, the type, the document's + /// version, the number handling, and the describing converter or none. Each builds the document, and the + /// parameter is what ASP.NET writes, without <c>AddTypeKit()</c> or the converter, for the same version and + /// number handling. Found by the probe gate: the 2.0 refusal meant for a description refused ASP.NET's own + /// <c>["number","string"]</c> of a decimal read from a string. + /// </summary> + public static TheoryData<string, OpenApiSpecVersion, bool, bool> ParameterVariants() + { + var data = new TheoryData<string, OpenApiSpecVersion, bool, bool>(); + foreach (var path in ParameterPaths.Keys) + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }) + foreach (var strict in new[] { false, true }) + foreach (var converter in new[] { true, false }) + data.Add(path, version, strict, converter); + return data; + } + + /// <summary>The parameter <c>q</c> of each source and type, by path.</summary> + private static readonly Dictionary<string, string> ParameterPaths = new() + { + ["/parameters/route/{q}"] = "route, decimal", + ["/parameters/route-nullable/{q}"] = "route, decimal?", + ["/parameters/query"] = "query, decimal", + ["/parameters/query-nullable"] = "query, decimal?", + ["/parameters/header"] = "header, decimal", + ["/parameters/header-nullable"] = "header, decimal?", + }; + + private static void ParameterEndpoints(WebApplication app) + { + app.MapGet("/parameters/route/{q}", int (decimal q) => 0); + app.MapGet("/parameters/route-nullable/{q}", int (decimal? q) => 0); + app.MapGet("/parameters/query", int (decimal q) => 0); + app.MapGet("/parameters/query-nullable", int (decimal? q) => 0); + app.MapGet("/parameters/header", int ([Microsoft.AspNetCore.Mvc.FromHeader(Name = "q")] decimal q) => 0); + app.MapGet("/parameters/header-nullable", int ([Microsoft.AspNetCore.Mvc.FromHeader(Name = "q")] decimal? q) => 0); + } + + private static readonly System.Collections.Concurrent.ConcurrentDictionary<(OpenApiSpecVersion, bool Strict, bool Converter, bool TypeKit), Task<OpenApiJson>> ParameterDocuments = new(); + + private static Task<OpenApiJson> ParameterDocumentAsync(OpenApiSpecVersion version, bool strict, bool converter, bool typeKit) => + ParameterDocuments.GetOrAdd((version, strict, converter, typeKit), key => ProbeHost.DocumentAsync( + openApi: key.TypeKit ? openApi => openApi.AddTypeKit() : null, + json: json => + { + if (key.Strict) json.SerializerOptions.NumberHandling = JsonNumberHandling.Strict; + if (key.Converter) json.SerializerOptions.Converters.Add(new DecimalStringJsonConverter()); + }, + version: key.Item1, + endpoints: ParameterEndpoints)); + + /// <summary>The parameter object whole: 2.0 writes a parameter's keywords on it, 3.x in its schema.</summary> + private static JsonNode ParameterObject(OpenApiJson document, string path) => + document.Root["paths"]?[path]?["get"]?["parameters"]?.AsArray().SingleOrDefault(p => p!["name"]!.GetValue<string>() == "q") + ?? throw new InvalidOperationException($"GET {path} has no parameter 'q'."); + + [Theory] + [MemberData(nameof(ParameterVariants))] + public async Task A_parameter_of_a_described_type_is_what_ASP_NET_writes_without_the_converter(string path, OpenApiSpecVersion version, bool strict, bool converter) + { + var context = $"{ParameterPaths[path]}, {version}, {(strict ? "Strict" : "AllowReadingFromString")}, {(converter ? "described converter" : "no converter")}"; + + var described = await ParameterDocumentAsync(version, strict, converter, typeKit: true); + var aspNet = await ParameterDocumentAsync(version, strict, converter: false, typeKit: false); + + ParameterObject(described, path).ShouldDescribeTheSameAs(ParameterObject(aspNet, path), context); + } + [Theory] [MemberData(nameof(Versions))] public async Task A_property_s_own_described_converter_is_described_and_its_neighbour_left_alone(OpenApiSpecVersion version) @@ -626,6 +695,63 @@ public async Task A_resolver_s_converter_for_a_nullable_type_describes_it_inline } } + /// <summary>Nullable elements of a described decimal and of a value object over one, in a list and a dictionary.</summary> + public sealed record Optionals(List<decimal?> Lines, Dictionary<string, decimal?> Taxes, List<ProbeAmount?> Amounts, Dictionary<string, ProbeAmount?> Named); + + public static TheoryData<string, bool, OpenApiSpecVersion> NullableElementHosts() + { + var data = new TheoryData<string, bool, OpenApiSpecVersion>(); + foreach (var how in new[] { "a decimal converter", "a resolver's nullable decimal converter", "a converter replacing the value object's", "a resolver's converter replacing the value object's" }) + foreach (var components in new[] { true, false }) + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0 }) + data.Add(how, components, version); + return data; + } + + /// <summary> + /// ASP.NET drops the element of a list or a dictionary of a nullable type whose converter is one's own, a + /// <c>decimal?</c> under a decimal converter or a resolver's, and a <c>ProbeAmount?</c> under a converter that replaces + /// the generated one; the element put back is the description or null, as the options write it, judged by a + /// validator, with everything inlined and with components. Closes the analyst's seed S9 (2026-10-16): the null of the + /// element put back was held by no test. Where the value object is a component, ASP.NET visited the element put back + /// again as <c>ProbeAmount?</c>, which then read as the component, and the null was taken out (found by this test). + /// </summary> + [Theory] + [MemberData(nameof(NullableElementHosts))] + public async Task A_nullable_element_put_back_admits_null_as_the_options_write_it(string how, bool components, OpenApiSpecVersion version) + { + await using var host = await ProbeHost.StartAsync(openApi => + { + if (!components) openApi.CreateSchemaReferenceId = _ => null; + openApi.AddTypeKit(); + }, json => + { + switch (how) + { + case "a decimal converter": json.SerializerOptions.Converters.Add(new DecimalStringJsonConverter()); break; + case "a resolver's nullable decimal converter": json.SerializerOptions.TypeInfoResolverChain.Insert(0, new ResolverOf<decimal?>(new NullableDecimalCents())); break; + case "a converter replacing the value object's": json.SerializerOptions.Converters.Add(new CentsConverter()); break; + default: json.SerializerOptions.TypeInfoResolverChain.Insert(0, new ResolverOf<ProbeAmount>(new CentsConverter())); break; + } + }, version, app => app.MapPost("/optionals", int (Optionals optionals) => 0)); + var document = await host.DocumentAsync(); + var body = Inline(document, document.Request("/optionals", "post")); + var options = host.Services.GetRequiredService<IOptions<JsonOptions>>().Value.SerializerOptions; + + var amount = ProbeAmount.From(7.80m); + var written = System.Text.Json.JsonSerializer.SerializeToNode( + new Optionals([7.80m, null], new() { ["k"] = 7.80m, ["n"] = null }, [amount, null], new() { ["k"] = amount, ["n"] = null }), options)!.AsObject(); + + foreach (var (property, value) in written) + { + var schema = body["properties"]![property]!; + var element = Inline(document, (schema["items"] ?? schema["additionalProperties"]).ShouldNotBeNull($"{how}, {version}: '{property}' has no element: {schema.ToJsonString()}")); + element.AsObject().Count.ShouldBeGreaterThan(0, $"{how}, {version}: '{property}' has the empty element"); + PatchSchemaTests.Validator(document, schema, version)(value!.ToJsonString()) + .ShouldBeTrue($"{how}, {version}: '{property}' is written as {value.ToJsonString()}, which {schema.ToJsonString()} refuses"); + } + } + /// <summary> /// An <c>if</c> and its branches judge the whole value, so a conditional description admits null only in /// a branch beside it: in a nullable property and in a patch, judged by a validator. diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/FaultResponseTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/FaultResponseTests.cs index 5bd8a2bb..d548b049 100644 --- a/test/CodoMetis.TypeKit.AspNetCore.Tests/FaultResponseTests.cs +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/FaultResponseTests.cs @@ -59,6 +59,15 @@ private static void MapOrders(IServiceCollection services) => _ => StatusCodes.Status403Forbidden, }); + private static void MapOrderProblemTypes(IServiceCollection services) => + services.AddFaultResponses<OrderFault>(fault => + FaultResponse.Status(fault switch + { + OrderFault.NotFound => StatusCodes.Status404NotFound, + OrderFault.AlreadyShipped => StatusCodes.Status409Conflict, + _ => StatusCodes.Status403Forbidden, + }).WithType(new Uri($"https://problems.example/{fault}"))); + [Theory] [MemberData(nameof(Ids))] public async Task A_success_is_its_value_and_a_fault_is_problem_details_with_its_mapped_status(int id) @@ -384,13 +393,7 @@ public async Task Problem_responses_the_endpoint_declares_itself_describe_fault_ { if (inline) options.CreateSchemaReferenceId = type => type.Type == typeof(ProblemDetails) ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); }, - services: services => services.AddFaultResponses<OrderFault>(fault => - FaultResponse.Status(fault switch - { - OrderFault.NotFound => StatusCodes.Status404NotFound, - OrderFault.AlreadyShipped => StatusCodes.Status409Conflict, - _ => StatusCodes.Status403Forbidden, - }).WithType(new Uri($"https://problems.example/{fault}"))), + services: MapOrderProblemTypes, endpoints: app => app.MapGet("/declared/{id:int}", (int id) => Find(id).ToHttpResult()) .ProducesProblem(StatusCodes.Status403Forbidden).ProducesProblem(StatusCodes.Status404NotFound).ProducesProblem(StatusCodes.Status409Conflict)); @@ -405,6 +408,146 @@ public async Task Problem_responses_the_endpoint_declares_itself_describe_fault_ notFound["description"]!.GetValue<string>().ShouldBe("Not Found. Problem types: https://problems.example/NotFound"); } + /// <summary> + /// Every way an endpoint declares a problem response itself describes <c>fault</c> and <c>rule</c> and names the + /// problem types, and keeps the media type it declares: <c>Produces<ProblemDetails>(404)</c> and + /// <c>[ProducesResponseType<ProblemDetails>(404)]</c> declare <c>application/json</c>, where the transformer + /// looked for <c>application/problem+json</c> alone, so their 404 named no problem type and its schema had + /// neither (reported 2026-10-16). A type derived from <see cref="ProblemDetails"/> is a component of its own, + /// which gets them too. Decision 95's input class: declaration form × host × media type declared × problem + /// type × document version × a host that inlines the problem schema. + /// </summary> + [Theory] + [MemberData(nameof(DeclaredProblems))] + public async Task A_problem_response_the_endpoint_declares_itself_describes_fault_and_rule_under_the_media_type_it_declares( + string host, string form, string media, string type, OpenApiSpecVersion version, bool inline) + { + var withTypeKit = await DeclaredAsync(typeKit: true); + var aspNet = await DeclaredAsync(typeKit: false); + + var notFound = Responses(withTypeKit, "/declared/{id}", "get")["404"].ShouldNotBeNull(); + notFound["description"]!.GetValue<string>().ShouldBe("Not Found. Problem types: https://problems.example/NotFound"); + if (version == OpenApiSpecVersion.OpenApi2_0) + DeclaredMediaTypes(aspNet).Except(DeclaredMediaTypes(withTypeKit)).ShouldBeEmpty("2.0's produces is the operation's, the added 403 and 409 among it"); + else + DeclaredMediaTypes(withTypeKit).ShouldBe(DeclaredMediaTypes(aspNet), "the media type is the one the endpoint declares"); + + var schemas = notFound["content"] is JsonObject content ? [.. content.Select(each => each.Value!["schema"]!)] : (List<JsonNode>)[notFound["schema"]!]; + schemas.ShouldNotBeEmpty(); + foreach (var schema in schemas) + { + var problem = schema["$ref"] is { } reference + ? reference.GetValue<string>()[2..].Split('/').Aggregate(withTypeKit, (node, step) => node[step]!) + : schema; + problem["properties"]!["fault"].ShouldNotBeNull(problem.ToJsonString()); + problem["properties"]!["rule"].ShouldNotBeNull(problem.ToJsonString()); + } + + async Task<JsonNode> DeclaredAsync(bool typeKit) + { + await using var app = await StartAsync( + services: MapOrderProblemTypes, + openApi: options => + { + if (inline) options.CreateSchemaReferenceId = info => typeof(ProblemDetails).IsAssignableFrom(info.Type) ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(info); + }, + version: version, + mvc: host == Mvc, + controllers: host == Mvc ? [DeclaringControllers[(form, media, type)]] : null, + endpoints: host == Mvc ? null : app => MapDeclared(app, form, media, type), + controls: false, + orders: false, + typeKit: typeKit); + + return await DocumentAsync(app); + } + } + + private const string Minimal = "minimal", Mvc = "mvc"; + private const string ProducesProblem = "ProducesProblem", Produces = "Produces<T>", ProducesOfType = "Produces(typeof)"; + private const string Attribute = "[ProducesResponseType<T>]", AttributeOfType = "[ProducesResponseType(typeof)]"; + private const string Unnamed = "(none)", Json = "application/json", ProblemJson = "application/problem+json"; + private const string Problem = nameof(ProblemDetails), ValidationProblem = nameof(HttpValidationProblemDetails); + + /// <summary> + /// The declarations of a 404 problem response, by host: ASP.NET's builder methods on a minimal API + /// (<c>ProducesProblem</c> is <c>ProducesValidationProblem</c> for the derived type), the attributes on a lambda + /// or an action; each with no content type, <c>application/json</c> and <c>application/problem+json</c>. + /// </summary> + public static TheoryData<string, string, string, string, OpenApiSpecVersion, bool> DeclaredProblems() + { + var data = new TheoryData<string, string, string, string, OpenApiSpecVersion, bool>(); + foreach (var (host, forms) in new (string, string[])[] { (Minimal, [ProducesProblem, Produces, ProducesOfType, Attribute, AttributeOfType]), (Mvc, [Attribute, AttributeOfType]) }) + foreach (var form in forms) + foreach (var media in (string[])[Unnamed, Json, ProblemJson]) + foreach (var type in (string[])[Problem, ValidationProblem]) + foreach (var version in (OpenApiSpecVersion[])[OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0]) + foreach (var inline in (bool[])[false, true]) + data.Add(host, form, media, type, version, inline); + + return data; + } + + private static void MapDeclared(WebApplication app, string form, string media, string type) + { + const string path = "/declared/{id:int}"; + const int status = StatusCodes.Status404NotFound; + var contentType = media == Unnamed ? null : media; + + _ = (form, type) switch + { + (ProducesProblem, Problem) => app.MapGet(path, (int id) => Find(id).ToHttpResult()).ProducesProblem(status, contentType), + (ProducesProblem, _) => app.MapGet(path, (int id) => Find(id).ToHttpResult()).ProducesValidationProblem(status, contentType), + (Produces, Problem) => app.MapGet(path, (int id) => Find(id).ToHttpResult()).Produces<ProblemDetails>(status, contentType), + (Produces, _) => app.MapGet(path, (int id) => Find(id).ToHttpResult()).Produces<HttpValidationProblemDetails>(status, contentType), + (ProducesOfType, _) => app.MapGet(path, (int id) => Find(id).ToHttpResult()) + .Produces(status, type == Problem ? typeof(ProblemDetails) : typeof(HttpValidationProblemDetails), contentType), + _ => app.MapGet(path, DeclaringLambdas[(form, media, type)]), + }; + } + + private static readonly Dictionary<(string Form, string Media, string Type), Delegate> DeclaringLambdas = new() + { + [(Attribute, Unnamed, Problem)] = [ProducesResponseType<ProblemDetails>(404)] (int id) => Find(id).ToHttpResult(), + [(Attribute, Json, Problem)] = [ProducesResponseType<ProblemDetails>(404, Json)] (int id) => Find(id).ToHttpResult(), + [(Attribute, ProblemJson, Problem)] = [ProducesResponseType<ProblemDetails>(404, ProblemJson)] (int id) => Find(id).ToHttpResult(), + [(Attribute, Unnamed, ValidationProblem)] = [ProducesResponseType<HttpValidationProblemDetails>(404)] (int id) => Find(id).ToHttpResult(), + [(Attribute, Json, ValidationProblem)] = [ProducesResponseType<HttpValidationProblemDetails>(404, Json)] (int id) => Find(id).ToHttpResult(), + [(Attribute, ProblemJson, ValidationProblem)] = [ProducesResponseType<HttpValidationProblemDetails>(404, ProblemJson)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, Unnamed, Problem)] = [ProducesResponseType(typeof(ProblemDetails), 404)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, Json, Problem)] = [ProducesResponseType(typeof(ProblemDetails), 404, Json)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, ProblemJson, Problem)] = [ProducesResponseType(typeof(ProblemDetails), 404, ProblemJson)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, Unnamed, ValidationProblem)] = [ProducesResponseType(typeof(HttpValidationProblemDetails), 404)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, Json, ValidationProblem)] = [ProducesResponseType(typeof(HttpValidationProblemDetails), 404, Json)] (int id) => Find(id).ToHttpResult(), + [(AttributeOfType, ProblemJson, ValidationProblem)] = [ProducesResponseType(typeof(HttpValidationProblemDetails), 404, ProblemJson)] (int id) => Find(id).ToHttpResult(), + }; + + private static readonly Dictionary<(string Form, string Media, string Type), Type> DeclaringControllers = new() + { + [(Attribute, Unnamed, Problem)] = typeof(DeclaresProblem), + [(Attribute, Json, Problem)] = typeof(DeclaresJsonProblem), + [(Attribute, ProblemJson, Problem)] = typeof(DeclaresProblemJsonProblem), + [(Attribute, Unnamed, ValidationProblem)] = typeof(DeclaresValidationProblem), + [(Attribute, Json, ValidationProblem)] = typeof(DeclaresJsonValidationProblem), + [(Attribute, ProblemJson, ValidationProblem)] = typeof(DeclaresProblemJsonValidationProblem), + [(AttributeOfType, Unnamed, Problem)] = typeof(DeclaresProblemOfType), + [(AttributeOfType, Json, Problem)] = typeof(DeclaresJsonProblemOfType), + [(AttributeOfType, ProblemJson, Problem)] = typeof(DeclaresProblemJsonProblemOfType), + [(AttributeOfType, Unnamed, ValidationProblem)] = typeof(DeclaresValidationProblemOfType), + [(AttributeOfType, Json, ValidationProblem)] = typeof(DeclaresJsonValidationProblemOfType), + [(AttributeOfType, ProblemJson, ValidationProblem)] = typeof(DeclaresProblemJsonValidationProblemOfType), + }; + + /// <summary>The media types the declared 404 is written with: its content's, or a 2.0 operation's <c>produces</c>, which are every response's.</summary> + private static string[] DeclaredMediaTypes(JsonNode document) + { + var operation = document["paths"]!["/declared/{id}"]!["get"]!; + + return operation["responses"]!["404"]!["content"] is JsonObject content + ? [.. content.Select(media => media.Key)] + : [.. operation["produces"]!.AsArray().Select(media => media!.GetValue<string>())]; + } + /// <summary>A host whose document has no fault response keeps ASP.NET's problem schema, and the whole document, byte for byte.</summary> [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_1)] @@ -467,6 +610,7 @@ private static async Task<WebApplication> StartAsync( Action<OpenApiOptions>? openApi = null, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_1, bool mvc = false, + Type[]? controllers = null, Action<WebApplication>? endpoints = null, bool map = true, bool controls = true, @@ -490,7 +634,7 @@ private static async Task<WebApplication> StartAsync( builder.Services.AddControllers().ConfigureApplicationPartManager(parts => { parts.ApplicationParts.Clear(); - parts.FeatureProviders.Add(new OnlyControllers(typeof(OrdersController))); + parts.FeatureProviders.Add(new OnlyControllers(controllers ?? [typeof(OrdersController)])); }); } @@ -583,4 +727,88 @@ public sealed class OrdersController : ControllerBase [HttpGet("/mvc/orders/{id:int}")] public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); } + + [ApiController] + public sealed class DeclaresProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<ProblemDetails>(404)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresJsonProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<ProblemDetails>(404, Json)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresProblemJsonProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<ProblemDetails>(404, ProblemJson)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresValidationProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<HttpValidationProblemDetails>(404)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresJsonValidationProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<HttpValidationProblemDetails>(404, Json)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresProblemJsonValidationProblem : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType<HttpValidationProblemDetails>(404, ProblemJson)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(ProblemDetails), 404)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresJsonProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(ProblemDetails), 404, Json)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresProblemJsonProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(ProblemDetails), 404, ProblemJson)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresValidationProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(HttpValidationProblemDetails), 404)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresJsonValidationProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(HttpValidationProblemDetails), 404, Json)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } + + [ApiController] + public sealed class DeclaresProblemJsonValidationProblemOfType : ControllerBase + { + [HttpGet("/declared/{id:int}"), ProducesResponseType(typeof(HttpValidationProblemDetails), 404, ProblemJson)] + public Results<Ok<Order>, FaultProblem<OrderFault>> Get(int id) => Find(id).ToHttpResult(); + } } diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/NullablePositionTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/NullablePositionTests.cs new file mode 100644 index 00000000..6e550fae --- /dev/null +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/NullablePositionTests.cs @@ -0,0 +1,475 @@ +using System.Collections.Concurrent; +using System.Reflection; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using CodoMetis.TypeKit.CompilerServices; +using CodoMetis.TypeKit.Generators.Probes; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.AspNetCore.Tests; + +/// <summary> +/// A nullable value object, or a value object where it is not nullable, publishes in every position of an +/// operation (body, query, header, form, route, response, a list's elements, a dictionary's values, a query or header +/// array's items, a property of a non-abstract polymorphic base's derived branch and of its own), in MVC +/// and in minimal APIs, with enums written by number or by name, and with everything inlined, value objects alone +/// inlined or components, exactly the schema ASP.NET publishes for the wrapped type in the same position at the same +/// nullability. +/// </summary> +/// <remarks> +/// <para> +/// ASP.NET treats the null of each position its own way: a <c>[FromBody] int?</c> is <c>oneOf: [null, X]</c> +/// whose <c>X</c> admits null again, a <c>[FromBody] string?</c> is the same without the inner null, and a +/// query, header or route <c>int?</c> admits no null at all. MVC hands a <c>[FromBody] Quantity?</c> to the +/// transformer as a <see cref="string"/> parameter, so it took the parameter's rule and lost the inner null; +/// minimal APIs hand a query <c>Quantity?</c> to it as the nullable value object, so it took a JSON +/// position's rule and gained a null <c>int?</c> does not have. +/// </para> +/// <para> +/// Where the host makes the value object a component, a nullable one in a body or a response is +/// <c>oneOf: [null, component]</c> (decision 36): the component is shared with every non-nullable use, so it +/// cannot admit null, and the inner null the wrapped type carries is the one difference allowed. +/// </para> +/// <para> +/// A query, header, route or form <c>DayOfWeek?</c> written by name lists null among its values, where its type +/// admits none: the value object over it left the null out (finding F3, C21 row 285). An element of a nullable +/// enum that is ASP.NET's component is the bare reference, without the null its JSON holds; an inlined nullable +/// value object over it admits the null (decision 36), which the comparison takes out. +/// </para> +/// </remarks> +public sealed class NullablePositionTests +{ + private static readonly ConcurrentDictionary<(OpenApiSpecVersion, string, bool), Task<OpenApiJson>> Documents = new(); + + /// <summary>The value object and the wrapped type it is compared with, each at its nullability.</summary> + private static readonly (string ValueObject, string Wrapped)[] Pairs = + [ + // Nullable structs over a number, a validated number, a Guid, an enum, a TimeSpan, a decimal, a bool and a date. + (Key<ProbeCount?>(), Key<int?>()), + (Key<ProbePercentage?>(), Key<int?>()), + (Key<ProbeId?>(), Key<Guid?>()), + (Key<ProbeWeekday?>(), Key<DayOfWeek?>()), + // A validated struct over an enum, the shape of C21 row 285. + (Key<PositionValidatedWeekday?>(), Key<DayOfWeek?>()), + // A nullable struct over a flags enum, written by name or by number as the host writes the enum. + (Key<PositionAccessMask?>(), Key<PositionAccess?>()), + (Key<ProbeDuration?>(), Key<TimeSpan?>()), + (Key<ProbeAmount?>(), Key<decimal?>()), + (Key<ProbeFlag?>(), Key<bool?>()), + (Key<ProbeDate?>(), Key<DateOnly?>()), + // Nullable structs over a reference type, compared with that type declared nullable. + (Key<ProbeName?>(), "String-nullable"), + (Key<ProbeUri?>(), "Uri-nullable"), + // A record class declared nullable. + ("ProbeLabel-nullable", "String-nullable"), + // Where they are not nullable, which must stay so. + (Key<ProbeCount>(), Key<int>()), + (Key<ProbeId>(), Key<Guid>()), + (Key<ProbeLabel>(), Key<string>()), + (Key<ProbeWeekday>(), Key<DayOfWeek>()), + (Key<PositionAccessMask>(), Key<PositionAccess>()), + ]; + + public static TheoryData<OpenApiSpecVersion, string, bool, string, string> Positions() + { + var data = new TheoryData<OpenApiSpecVersion, string, bool, string, string>(); + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }) + foreach (var references in new[] { "inline-all", "inline-value-objects", "components" }) + foreach (var enumsByName in new[] { false, true }) + foreach (var host in new[] { "mvc", "minimal" }) + foreach (var position in new[] { "body", "query", "header", "form", "route", "response", "elements", "values", "query-items", "header-items", "polymorphic-derived-property", "polymorphic-base-property" }) + { + // Where the value object is a component, a nullable element is the component or null, which the + // README names and SchemaTests holds; inlined, the element's schema is the position's own. + if (position is "elements" or "values" or "query-items" or "header-items" && references == "components") continue; + + // A nullable property of a component is the component or null (decision 36), which in 2.0 loses the + // reference (KB-026); PolymorphicBaseTests holds the base's branch to the derived one's there. + if (position is "polymorphic-derived-property" or "polymorphic-base-property" && references == "components") continue; + + // With nothing a component, ASP.NET names every polymorphic base's own branch "Base", one component for all + // of them: the first it meets. One document holds one there (PolymorphicBaseTests). + if (position == "polymorphic-base-property" && references == "inline-all") continue; + data.Add(version, references, enumsByName, host, position); + } + + return data; + } + + [Theory] + [MemberData(nameof(Positions))] + public async Task A_value_object_is_described_as_its_wrapped_type_in_the_same_position_at_the_same_nullability( + OpenApiSpecVersion version, string references, bool enumsByName, string host, string position) + { + var document = await Documents.GetOrAdd((version, references, enumsByName), static key => ProbeHost.DocumentAsync( + openApi => + { + if (key.Item2 == "inline-all") openApi.CreateSchemaReferenceId = _ => null; + openApi.AddTypeKit(); + + // The README's way to inline value objects alone: the wrapped enum stays ASP.NET's component. + if (key.Item2 == "inline-value-objects") + openApi.CreateSchemaReferenceId = type => + (Nullable.GetUnderlyingType(type.Type) ?? type.Type).GetCustomAttribute<GeneratedValueObjectAttribute>() is not null + ? null + : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + }, + json: json => + { + // An enum the host writes by name is a string enum, whose nullable form lists null among its values. + if (key.Item3) json.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + json.SerializerOptions.TypeInfoResolver = json.SerializerOptions.TypeInfoResolver!.WithAddedModifier(Polymorphic); + }, + version: key.Item1, + endpoints: MapMinimal, + controllers: [.. TypeArguments.Select(type => typeof(PositionController<>).MakeGenericType(type)), typeof(NullableReferenceController)], + services: services => services.Configure<MvcOptions>(mvc => mvc.Conventions.Add(new PositionRoutes())))); + + var differences = new List<string>(); + foreach (var (valueObject, wrapped) in Pairs) + { + var actual = Schema(document, version, host, position, valueObject); + var expected = Schema(document, version, host, position, wrapped); + + // Decision 36: a component shared with the non-nullable uses cannot admit null; the null is around it. + if (actual?["oneOf"] is JsonArray { Count: 2 } nullOr && nullOr[1]?["$ref"] is not null && expected?["oneOf"] is JsonArray { Count: 2 } wrappedNullOr) + wrappedNullOr[1] = WithoutNull(wrappedNullOr[1]!); + + // Decision 36 too: an inlined nullable element admits null, where ASP.NET's own element of a nullable + // component type (an enum's) is the bare reference, without the null its JSON holds. + var keyword = position == "values" ? "additionalProperties" : "items"; + if (wrapped.EndsWith("-nullable", StringComparison.Ordinal) + && Holder(actual) is { } container && container[keyword] is JsonObject element && element["$ref"] is null + && Holder(expected)?[keyword]?["$ref"] is not null) + { + if (JsonNode.DeepEquals(WithoutNull(element), element)) differences.Add($"{valueObject}: the element admits no null: {element.ToJsonString()}"); + container[keyword] = WithoutNull(element); + } + + if (!JsonNode.DeepEquals(Inline(document, actual), Inline(document, expected))) + differences.Add($"{valueObject}: {Inline(document, actual)?.ToJsonString()}\n {wrapped}: {Inline(document, expected)?.ToJsonString()}"); + } + + differences.ShouldBeEmpty(string.Join("\n", differences)); + } + + private static readonly Type[] TypeArguments = + [ + typeof(ProbeCount?), typeof(ProbePercentage?), typeof(ProbeId?), typeof(ProbeWeekday?), typeof(ProbeDuration?), typeof(ProbeAmount?), + typeof(ProbeFlag?), typeof(ProbeDate?), typeof(ProbeName?), typeof(ProbeUri?), typeof(ProbeCount), typeof(ProbeId), typeof(ProbeLabel), + typeof(ProbeWeekday), typeof(PositionAccessMask?), typeof(PositionAccessMask), typeof(PositionValidatedWeekday?), + typeof(int?), typeof(Guid?), typeof(DayOfWeek?), typeof(TimeSpan?), typeof(decimal?), typeof(bool?), typeof(DateOnly?), typeof(int), + typeof(Guid), typeof(string), typeof(DayOfWeek), typeof(PositionAccess?), typeof(PositionAccess), + ]; + + private static string Key<T>() => Key(typeof(T)); + + /// <summary>Makes each <see cref="PositionBase{T}"/> a non-abstract polymorphic base, which an attribute cannot name a closed type for.</summary> + private static void Polymorphic(System.Text.Json.Serialization.Metadata.JsonTypeInfo info) + { + if (!info.Type.IsGenericType || info.Type.GetGenericTypeDefinition() != typeof(PositionBase<>)) return; + + info.PolymorphismOptions = new System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions + { + DerivedTypes = { new System.Text.Json.Serialization.Metadata.JsonDerivedType(typeof(PositionDerived<>).MakeGenericType(info.Type.GenericTypeArguments), "derived") }, + }; + } + + private static string Key(Type type) => Nullable.GetUnderlyingType(type) is { } held ? held.Name + "-nullable" : type.Name; + + private static void MapMinimal(WebApplication app) + { + Map<ProbeCount?>(app); Map<ProbePercentage?>(app); Map<ProbeId?>(app); Map<ProbeWeekday?>(app); Map<ProbeDuration?>(app); + Map<ProbeAmount?>(app); Map<ProbeFlag?>(app); Map<ProbeDate?>(app); Map<ProbeName?>(app); Map<ProbeUri?>(app); + Map<ProbeCount>(app); Map<ProbeId>(app); Map<ProbeLabel>(app); Map<ProbeWeekday>(app); Map<PositionAccessMask?>(app); Map<PositionAccessMask>(app); Map<PositionValidatedWeekday?>(app); + Map<int?>(app); Map<Guid?>(app); Map<DayOfWeek?>(app); Map<TimeSpan?>(app); Map<decimal?>(app); Map<bool?>(app); Map<DateOnly?>(app); + Map<int>(app); Map<Guid>(app); Map<string>(app); Map<DayOfWeek>(app); Map<PositionAccess?>(app); Map<PositionAccess>(app); + + // The reference types at their declared nullability, which a type argument does not carry. + app.MapPost("/minimal/String-nullable/body", ([FromBody] string? v) => 0); + app.MapPost("/minimal/String-nullable/elements", ([FromBody] List<string?> v) => 0); + app.MapPost("/minimal/String-nullable/values", ([FromBody] Dictionary<string, string?> v) => 0); + app.MapGet("/minimal/String-nullable/query", ([FromQuery(Name = "v")] string? v) => 0); + app.MapGet("/minimal/String-nullable/header", ([FromHeader(Name = "v")] string? v) => 0); + app.MapGet("/minimal/String-nullable/query-items", ([FromQuery(Name = "v")] string?[] v) => 0); + app.MapGet("/minimal/String-nullable/header-items", ([FromHeader(Name = "v")] string?[] v) => 0); + app.MapPost("/minimal/String-nullable/form", ([FromForm(Name = "v")] string? v) => 0).DisableAntiforgery(); + app.MapGet("/minimal/String-nullable/route/{v}", (string? v) => 0); + app.MapGet("/minimal/String-nullable/response", string? () => null); + app.MapPost("/minimal/String-nullable/polymorphic-derived-property", ([FromBody] PositionString v) => 0); + app.MapPost("/minimal/String-nullable/polymorphic-base-property", ([FromBody] PositionString v) => 0); + app.MapPost("/minimal/Uri-nullable/body", ([FromBody] Uri? v) => 0); + app.MapPost("/minimal/Uri-nullable/elements", ([FromBody] List<Uri?> v) => 0); + app.MapPost("/minimal/Uri-nullable/values", ([FromBody] Dictionary<string, Uri?> v) => 0); + app.MapGet("/minimal/Uri-nullable/query", ([FromQuery(Name = "v")] Uri? v) => 0); + app.MapGet("/minimal/Uri-nullable/header", ([FromHeader(Name = "v")] Uri? v) => 0); + app.MapGet("/minimal/Uri-nullable/query-items", ([FromQuery(Name = "v")] Uri?[] v) => 0); + app.MapGet("/minimal/Uri-nullable/header-items", ([FromHeader(Name = "v")] Uri?[] v) => 0); + app.MapPost("/minimal/Uri-nullable/form", ([FromForm(Name = "v")] Uri? v) => 0).DisableAntiforgery(); + app.MapGet("/minimal/Uri-nullable/route/{v}", (Uri? v) => 0); + app.MapGet("/minimal/Uri-nullable/response", Uri? () => null); + app.MapPost("/minimal/Uri-nullable/polymorphic-derived-property", ([FromBody] PositionUri v) => 0); + app.MapPost("/minimal/Uri-nullable/polymorphic-base-property", ([FromBody] PositionUri v) => 0); + app.MapPost("/minimal/ProbeLabel-nullable/body", ([FromBody] ProbeLabel? v) => 0); + app.MapPost("/minimal/ProbeLabel-nullable/elements", ([FromBody] List<ProbeLabel?> v) => 0); + app.MapPost("/minimal/ProbeLabel-nullable/values", ([FromBody] Dictionary<string, ProbeLabel?> v) => 0); + app.MapGet("/minimal/ProbeLabel-nullable/query", ([FromQuery(Name = "v")] ProbeLabel? v) => 0); + app.MapGet("/minimal/ProbeLabel-nullable/header", ([FromHeader(Name = "v")] ProbeLabel? v) => 0); + app.MapGet("/minimal/ProbeLabel-nullable/query-items", ([FromQuery(Name = "v")] ProbeLabel?[] v) => 0); + app.MapGet("/minimal/ProbeLabel-nullable/header-items", ([FromHeader(Name = "v")] ProbeLabel?[] v) => 0); + app.MapPost("/minimal/ProbeLabel-nullable/form", ([FromForm(Name = "v")] ProbeLabel? v) => 0).DisableAntiforgery(); + app.MapGet("/minimal/ProbeLabel-nullable/route/{v}", (ProbeLabel? v) => 0); + app.MapGet("/minimal/ProbeLabel-nullable/response", ProbeLabel? () => null); + app.MapPost("/minimal/ProbeLabel-nullable/polymorphic-derived-property", ([FromBody] PositionLabel v) => 0); + app.MapPost("/minimal/ProbeLabel-nullable/polymorphic-base-property", ([FromBody] PositionLabel v) => 0); + } + + private static void Map<T>(IEndpointRouteBuilder app) + { + var at = "/minimal/" + Key<T>(); + app.MapPost(at + "/body", ([FromBody] T v) => 0); + app.MapPost(at + "/elements", ([FromBody] List<T> v) => 0); + app.MapPost(at + "/values", ([FromBody] Dictionary<string, T> v) => 0); + app.MapGet(at + "/query", ([FromQuery(Name = "v")] T v) => 0); + app.MapGet(at + "/header", ([FromHeader(Name = "v")] T v) => 0); + app.MapGet(at + "/query-items", ([FromQuery(Name = "v")] T[] v) => 0); + app.MapGet(at + "/header-items", ([FromHeader(Name = "v")] T[] v) => 0); + app.MapPost(at + "/form", ([FromForm(Name = "v")] T v) => 0).DisableAntiforgery(); + app.MapGet(at + "/route/{v}", (T v) => 0); + app.MapGet(at + "/response", T () => default!); + app.MapPost(at + "/polymorphic-derived-property", ([FromBody] PositionBase<T> v) => 0); + app.MapPost(at + "/polymorphic-base-property", ([FromBody] PositionBase<T> v) => 0); + } + + [ApiController] + public sealed class PositionController<T> : ControllerBase + { + [HttpPost("body")] public int Body([FromBody] T v) => 0; + [HttpPost("elements")] public int Elements([FromBody] List<T> v) => 0; + [HttpPost("values")] public int Values([FromBody] Dictionary<string, T> v) => 0; + [HttpGet("query")] public int Query([FromQuery(Name = "v")] T v) => 0; + [HttpGet("header")] public int Header([FromHeader(Name = "v")] T v) => 0; + [HttpGet("query-items")] public int QueryItems([FromQuery(Name = "v")] T[] v) => 0; + [HttpGet("header-items")] public int HeaderItems([FromHeader(Name = "v")] T[] v) => 0; + [HttpPost("form")] public int Form([FromForm(Name = "v")] T v) => 0; + [HttpGet("route/{v}")] public int Route(T v) => 0; + [HttpGet("response")] public T Answer() => default!; + [HttpPost("polymorphic-derived-property")] public int Derived([FromBody] PositionBase<T> v) => 0; + [HttpPost("polymorphic-base-property")] public int Base([FromBody] PositionBase<T> v) => 0; + } + + [ApiController] + public sealed class NullableReferenceController : ControllerBase + { + [HttpPost("mvc/String-nullable/body")] public int StringBody([FromBody] string? v) => 0; + [HttpPost("mvc/String-nullable/elements")] public int StringElements([FromBody] List<string?> v) => 0; + [HttpPost("mvc/String-nullable/values")] public int StringValues([FromBody] Dictionary<string, string?> v) => 0; + [HttpGet("mvc/String-nullable/query")] public int StringQuery([FromQuery(Name = "v")] string? v) => 0; + [HttpGet("mvc/String-nullable/header")] public int StringHeader([FromHeader(Name = "v")] string? v) => 0; + [HttpGet("mvc/String-nullable/query-items")] public int StringQueryItems([FromQuery(Name = "v")] string?[] v) => 0; + [HttpGet("mvc/String-nullable/header-items")] public int StringHeaderItems([FromHeader(Name = "v")] string?[] v) => 0; + [HttpPost("mvc/String-nullable/form")] public int StringForm([FromForm(Name = "v")] string? v) => 0; + [HttpGet("mvc/String-nullable/route/{v}")] public int StringRoute(string? v) => 0; + [HttpGet("mvc/String-nullable/response")] public string? StringResponse() => null; + [HttpPost("mvc/String-nullable/polymorphic-derived-property")] public int StringDerived([FromBody] PositionString v) => 0; + [HttpPost("mvc/String-nullable/polymorphic-base-property")] public int StringBase([FromBody] PositionString v) => 0; + [HttpPost("mvc/Uri-nullable/body")] public int UriBody([FromBody] Uri? v) => 0; + [HttpPost("mvc/Uri-nullable/elements")] public int UriElements([FromBody] List<Uri?> v) => 0; + [HttpPost("mvc/Uri-nullable/values")] public int UriValues([FromBody] Dictionary<string, Uri?> v) => 0; + [HttpGet("mvc/Uri-nullable/query")] public int UriQuery([FromQuery(Name = "v")] Uri? v) => 0; + [HttpGet("mvc/Uri-nullable/header")] public int UriHeader([FromHeader(Name = "v")] Uri? v) => 0; + [HttpGet("mvc/Uri-nullable/query-items")] public int UriQueryItems([FromQuery(Name = "v")] Uri?[] v) => 0; + [HttpGet("mvc/Uri-nullable/header-items")] public int UriHeaderItems([FromHeader(Name = "v")] Uri?[] v) => 0; + [HttpPost("mvc/Uri-nullable/form")] public int UriForm([FromForm(Name = "v")] Uri? v) => 0; + [HttpGet("mvc/Uri-nullable/route/{v}")] public int UriRoute(Uri? v) => 0; + [HttpGet("mvc/Uri-nullable/response")] public Uri? UriResponse() => null; + [HttpPost("mvc/Uri-nullable/polymorphic-derived-property")] public int UriDerived([FromBody] PositionUri v) => 0; + [HttpPost("mvc/Uri-nullable/polymorphic-base-property")] public int UriBase([FromBody] PositionUri v) => 0; + [HttpPost("mvc/ProbeLabel-nullable/body")] public int LabelBody([FromBody] ProbeLabel? v) => 0; + [HttpPost("mvc/ProbeLabel-nullable/elements")] public int LabelElements([FromBody] List<ProbeLabel?> v) => 0; + [HttpPost("mvc/ProbeLabel-nullable/values")] public int LabelValues([FromBody] Dictionary<string, ProbeLabel?> v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/query")] public int LabelQuery([FromQuery(Name = "v")] ProbeLabel? v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/header")] public int LabelHeader([FromHeader(Name = "v")] ProbeLabel? v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/query-items")] public int LabelQueryItems([FromQuery(Name = "v")] ProbeLabel?[] v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/header-items")] public int LabelHeaderItems([FromHeader(Name = "v")] ProbeLabel?[] v) => 0; + [HttpPost("mvc/ProbeLabel-nullable/form")] public int LabelForm([FromForm(Name = "v")] ProbeLabel? v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/route/{v}")] public int LabelRoute(ProbeLabel? v) => 0; + [HttpGet("mvc/ProbeLabel-nullable/response")] public ProbeLabel? LabelResponse() => null; + [HttpPost("mvc/ProbeLabel-nullable/polymorphic-derived-property")] public int LabelDerived([FromBody] PositionLabel v) => 0; + [HttpPost("mvc/ProbeLabel-nullable/polymorphic-base-property")] public int LabelBase([FromBody] PositionLabel v) => 0; + } + + /// <summary>Puts each closed <see cref="PositionController{T}"/> under <c>mvc/{its type argument}</c>.</summary> + private sealed class PositionRoutes : IControllerModelConvention + { + public void Apply(ControllerModel controller) + { + if (!controller.ControllerType.IsGenericType || controller.ControllerType.GetGenericTypeDefinition() != typeof(PositionController<>)) return; + + controller.Selectors.Clear(); + controller.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel(new RouteAttribute("mvc/" + Key(controller.ControllerType.GenericTypeArguments[0]))), + }); + } + } + + /// <summary>The schema of the position: the request body's, the parameter's, or the response's.</summary> + internal static JsonNode? Schema(OpenApiJson document, OpenApiSpecVersion version, string host, string position, string key) + { + var path = $"/{host}/{key}/{position}" + (position == "route" ? "/{v}" : ""); + var method = position is "body" or "form" or "elements" or "values" or "polymorphic-derived-property" or "polymorphic-base-property" ? "post" : "get"; + var operation = document.Root["paths"]?[path]?[method] ?? throw new InvalidOperationException($"No operation {method} {path}."); + var v2 = version == OpenApiSpecVersion.OpenApi2_0; + + var schema = position switch + { + // A minimal API writes a string as text/plain: the first content is the one there is, in any case alike. + "response" => v2 ? operation["responses"]!["200"]!["schema"] : operation["responses"]!["200"]!["content"]!.AsObject().First().Value!["schema"], + "body" or "elements" or "values" when v2 => operation["parameters"]!.AsArray().Single(p => p!["in"]!.GetValue<string>() == "body")!["schema"], + "body" or "elements" or "values" => operation["requestBody"]!["content"]!["application/json"]!["schema"], + "polymorphic-derived-property" or "polymorphic-base-property" => PolymorphicProperty(document, operation, v2, position == "polymorphic-base-property"), + "form" when !v2 => operation["requestBody"]!["content"]!.AsObject().First().Value!["schema"], + // A parameter: in 2.0 its keywords are on the parameter itself, so all but its name and description. + _ => operation["parameters"]!.AsArray().Single(p => p!["name"]!.GetValue<string>() == "v"), + }; + + return schema?.DeepClone(); + } + + /// <summary> + /// The property <c>v</c> of a polymorphic body's branch: the derived type's, the first of the root's <c>anyOf</c> (2.0: + /// <c>allOf</c>), or the base's own, its component <c>{Root}Base</c>, which 2.0 leaves out of the <c>allOf</c>. + /// </summary> + private static JsonNode? PolymorphicProperty(OpenApiJson document, JsonNode operation, bool v2, bool ofTheBase) + { + var body = v2 + ? operation["parameters"]!.AsArray().Single(p => p!["in"]!.GetValue<string>() == "body")!["schema"]! + : operation["requestBody"]!["content"]!["application/json"]!["schema"]!; + + var branch = ofTheBase + ? Target(document, body["$ref"]!.GetValue<string>() + "Base") + : Inline(document, body)![v2 ? "allOf" : "anyOf"]![0]!; + + return branch["properties"]?["v"] ?? throw new InvalidOperationException($"No property 'v' in {branch.ToJsonString()}."); + } + + /// <summary>What holds a container's <c>items</c> or <c>additionalProperties</c>: a body's schema, a 3.x parameter's schema, or a 2.0 parameter itself.</summary> + private static JsonObject? Holder(JsonNode? schema) => schema?["schema"] as JsonObject ?? schema as JsonObject; + + internal static JsonNode? Inline(OpenApiJson document, JsonNode? schema) => schema switch + { + JsonObject reference when reference["$ref"] is { } id => Inline(document, Target(document, id.GetValue<string>())), + JsonObject keywords => new JsonObject(keywords + .Where(keyword => keyword.Key is not ("name" or "description")) + .Select(keyword => KeyValuePair.Create(keyword.Key, Inline(document, keyword.Value)))), + JsonArray items => new JsonArray([.. items.Select(item => Inline(document, item))]), + _ => schema?.DeepClone(), + }; + + private static JsonNode Target(OpenApiJson document, string reference) + { + var name = reference.Split('/')[^1]; + return document.Root["components"]?["schemas"]?[name] ?? document.Root["definitions"]?[name] + ?? throw new InvalidOperationException($"No schema '{reference}'."); + } + + /// <summary>The schema without the null it admits: <c>null</c> out of its type and its values, <c>nullable</c> and a null branch gone.</summary> + internal static JsonNode WithoutNull(JsonNode schema) + { + var copy = schema.DeepClone().AsObject(); + copy.Remove("nullable"); + copy.Remove("x-nullable"); + if (copy["type"] is JsonArray types) + { + var kept = types.Where(type => type!.GetValue<string>() != "null").Select(type => type!.DeepClone()).ToArray(); + copy["type"] = kept.Length == 1 ? kept[0] : new JsonArray(kept); + } + + if (copy["enum"] is JsonArray values) + copy["enum"] = new JsonArray([.. values.Where(value => value is not null).Select(value => value!.DeepClone())]); + + if (copy["anyOf"] is JsonArray branches) + copy["anyOf"] = new JsonArray([.. branches.Where(branch => branch!["nullable"] is null).Select(branch => branch!.DeepClone())]); + + return copy; + } +} + +/// <summary>A flags enum, whose values combine: a host that writes it by name writes <c>"Read, Write"</c>.</summary> +[Flags] +public enum PositionAccess +{ + None = 0, + Read = 1, + Write = 2, +} + +/// <summary>A value object over a flags enum.</summary> +public readonly partial record struct PositionAccessMask : IValue<PositionAccess>; + +/// <summary>Why a <see cref="PositionValidatedWeekday"/> refuses a value.</summary> +public enum PositionWeekdayFault +{ + Undefined, +} + +/// <summary>A validated value object over an enum.</summary> +public readonly partial record struct PositionValidatedWeekday : IValidatedValue<PositionValidatedWeekday, DayOfWeek, PositionWeekdayFault> +{ + public static Result<PositionValidatedWeekday, PositionWeekdayFault> Create(DayOfWeek value) => + Enum.IsDefined(value) ? new PositionValidatedWeekday(value) : Result.Error(PositionWeekdayFault.Undefined); +} + +/// <summary>A non-abstract polymorphic base over a position's type: polymorphic by the document's resolver (<c>Polymorphic</c>).</summary> +public class PositionBase<T> +{ + public T V { get; set; } = default!; +} + +public sealed class PositionDerived<T> : PositionBase<T> +{ + public string Kind { get; set; } = ""; +} + +/// <summary>The polymorphic bases over the reference types at their declared nullability, which a type argument does not carry.</summary> +[JsonPolymorphic, JsonDerivedType(typeof(PositionStringDerived), "derived")] +public class PositionString +{ + public string? V { get; set; } +} + +public sealed class PositionStringDerived : PositionString +{ + public string Kind { get; set; } = ""; +} + +[JsonPolymorphic, JsonDerivedType(typeof(PositionUriDerived), "derived")] +public class PositionUri +{ + public Uri? V { get; set; } +} + +public sealed class PositionUriDerived : PositionUri +{ + public string Kind { get; set; } = ""; +} + +[JsonPolymorphic, JsonDerivedType(typeof(PositionLabelDerived), "derived")] +public class PositionLabel +{ + public ProbeLabel? V { get; set; } +} + +public sealed class PositionLabelDerived : PositionLabel +{ + public string Kind { get; set; } = ""; +} diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/PolymorphicBaseTests.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/PolymorphicBaseTests.cs new file mode 100644 index 00000000..fe827e49 --- /dev/null +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/PolymorphicBaseTests.cs @@ -0,0 +1,185 @@ +using System.Collections.Concurrent; +using System.Text.Json.Serialization; +using CodoMetis.TypeKit.Generators.Probes; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.AspNetCore.Tests; + +/// <summary> +/// A non-abstract <c>[JsonPolymorphic]</c> base is serialized as itself too, and its schema has a branch of its own beside +/// each derived type's (<c>{Base}Base</c>). Every property of that branch is described as the same property is in the +/// derived type's branch, which inherits it: a value object, a nullable one, a list of them, a patch of one, a closed set +/// and a list of them, a described decimal and a list of them, a value object over a described decimal, and a nested object +/// holding a value object; and the plain twin of each. In MVC and minimal APIs, OpenAPI 3.1, 3.0 and 2.0, with everything +/// inlined, value objects alone inlined, or components. +/// </summary> +/// <remarks> +/// Found by the code analyst (2026-10-16, A1): ASP.NET hands the base's own branch to no schema transformer, so it kept +/// what ASP.NET writes for a converter of one's own: <c>{}</c> for a value object, a list without <c>items</c>, a patch +/// as <c>{}</c>, a described decimal as <c>{"format":"double"}</c>. 2.0 leaves the base's branch out of the root's +/// <c>allOf</c>, for the plain twin too (ASP.NET's own); its definition is compared here. +/// </remarks> +public sealed class PolymorphicBaseTests +{ + private static readonly ConcurrentDictionary<(OpenApiSpecVersion, string, string, string), Task<OpenApiJson>> Documents = new(); + + public static TheoryData<OpenApiSpecVersion, string, string, string> Variants() + { + var data = new TheoryData<OpenApiSpecVersion, string, string, string>(); + foreach (var version in new[] { OpenApiSpecVersion.OpenApi3_1, OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }) + foreach (var references in new[] { "inline-all", "inline-value-objects", "components" }) + foreach (var host in new[] { "mvc", "minimal" }) + foreach (var model in new[] { nameof(PolyAnimal), nameof(PlainAnimal) }) + data.Add(version, references, host, model); + + return data; + } + + [Theory] + [MemberData(nameof(Variants))] + public async Task Each_property_of_a_polymorphic_base_s_own_branch_is_described_as_in_the_derived_branch( + OpenApiSpecVersion version, string references, string host, string model) + { + var document = await Documents.GetOrAdd((version, references, host, model), static key => ProbeHost.DocumentAsync( + openApi => + { + if (key.Item2 == "inline-all") openApi.CreateSchemaReferenceId = _ => null; + openApi.AddTypeKit(); + + // The README's way to inline value objects alone. + if (key.Item2 == "inline-value-objects") + openApi.CreateSchemaReferenceId = type => + System.Reflection.CustomAttributeExtensions.GetCustomAttribute<CodoMetis.TypeKit.CompilerServices.GeneratedValueObjectAttribute>(Nullable.GetUnderlyingType(type.Type) ?? type.Type) is not null + ? null + : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + }, + json: json => json.SerializerOptions.Converters.Add(new DecimalStringJsonConverter()), + version: key.Item1, + endpoints: key.Item3 == "minimal" + ? app => + { + if (key.Item4 == nameof(PolyAnimal)) app.MapPost("/poly", int (PolyAnimal animal) => 0); + else app.MapPost("/poly", int (PlainAnimal animal) => 0); + } + : null, + controllers: key.Item3 == "mvc" ? [key.Item4 == nameof(PolyAnimal) ? typeof(PolyAnimalController) : typeof(PlainAnimalController)] : null)); + + var baseBranch = Definitions(document)[references == "inline-all" ? "Base" : model + "Base"]?["properties"]?.AsObject() + ?? throw new InvalidOperationException($"No base branch: {string.Join(", ", Definitions(document).Select(c => c.Key))}"); + var derived = Derived(document.Root) ?? throw new InvalidOperationException("No derived branch."); + + baseBranch.Count.ShouldBeGreaterThanOrEqualTo(model == nameof(PolyAnimal) ? 11 : 9, baseBranch.ToJsonString()); + + var differences = new List<string>(); + foreach (var (name, schema) in baseBranch) + { + if (!JsonNode.DeepEquals(schema, derived[name])) + differences.Add($"{name}: {schema?.ToJsonString()}\n in the derived branch: {derived[name]?.ToJsonString()}"); + } + + differences.ShouldBeEmpty(string.Join("\n", differences)); + } + + private static JsonObject Definitions(OpenApiJson document) => + (document.Root["components"]?["schemas"] ?? document.Root["definitions"])?.AsObject() ?? []; + + /// <summary>The derived type's branch, wherever the document writes it: the one schema whose properties hold <c>meow</c>.</summary> + private static JsonObject? Derived(JsonNode? node) => node switch + { + JsonObject schema when schema["properties"] is JsonObject properties && properties.ContainsKey("meow") => properties, + JsonObject schema => schema.Select(keyword => Derived(keyword.Value)).FirstOrDefault(found => found is not null), + JsonArray items => items.Select(Derived).FirstOrDefault(found => found is not null), + _ => null, + }; + + [ApiController] + public sealed class PolyAnimalController : ControllerBase + { + [HttpPost("/poly")] public int Post(PolyAnimal animal) => 0; + } + + [ApiController] + public sealed class PlainAnimalController : ControllerBase + { + [HttpPost("/poly")] public int Post(PlainAnimal animal) => 0; + } +} + +/// <summary>A base serialized as itself and as <see cref="PolyCat"/>, with a property of each kind TypeKit describes.</summary> +[JsonPolymorphic, JsonDerivedType(typeof(PolyCat), "cat")] +public class PolyAnimal +{ + public ProbeCount Plain { get; set; } + + public ProbeCount? Maybe { get; set; } + + public List<ProbeCount> Counts { get; set; } = []; + + public Patch<ProbeCount> Patched { get; init; } + + public ProbeStatus Status { get; set; } = ProbeStatus.Open; + + public List<ProbeStatus> Statuses { get; set; } = []; + + public decimal Amount { get; set; } + + public List<decimal> Amounts { get; set; } = []; + + public ProbeAmount Money { get; set; } + + public ProbeAmount? MaybeMoney { get; set; } + + // Get-only: ASP.NET's own null around a component's reference needs a setter, so the null is TypeKit's here. + public ProbeCount? Computed => null; + + public PolyInner Inner { get; set; } = new(); +} + +public sealed class PolyCat : PolyAnimal +{ + public string Meow { get; set; } = ""; +} + +public sealed class PolyInner +{ + public ProbeCount Count { get; set; } +} + +/// <summary><see cref="PolyAnimal"/> with the types its value objects wrap and a set's names.</summary> +[JsonPolymorphic, JsonDerivedType(typeof(PlainCat), "cat")] +public class PlainAnimal +{ + public int Plain { get; set; } + + public int? Maybe { get; set; } + + public List<int> Counts { get; set; } = []; + + public Patch<int> Patched { get; init; } + + public string Status { get; set; } = "Open"; + + public List<string> Statuses { get; set; } = []; + + public decimal Amount { get; set; } + + public List<decimal> Amounts { get; set; } = []; + + public int? Computed => null; + + public PlainInner Inner { get; set; } = new(); +} + +public sealed class PlainCat : PlainAnimal +{ + public string Meow { get; set; } = ""; +} + +public sealed class PlainInner +{ + public int Count { get; set; } +} diff --git a/test/CodoMetis.TypeKit.AspNetCore.Tests/ProbeHost.cs b/test/CodoMetis.TypeKit.AspNetCore.Tests/ProbeHost.cs index 635930fa..35fb13f5 100644 --- a/test/CodoMetis.TypeKit.AspNetCore.Tests/ProbeHost.cs +++ b/test/CodoMetis.TypeKit.AspNetCore.Tests/ProbeHost.cs @@ -41,9 +41,10 @@ public static async Task<OpenApiJson> DocumentAsync( Action<JsonOptions>? json = null, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_1, Action<WebApplication>? endpoints = null, - Type[]? controllers = null) + Type[]? controllers = null, + Action<IServiceCollection>? services = null) { - await using var host = await StartAsync(openApi, json, version, endpoints, controllers); + await using var host = await StartAsync(openApi, json, version, endpoints, controllers, services); return await host.DocumentAsync(); } @@ -52,7 +53,8 @@ public static async Task<ProbeHost> StartAsync( Action<JsonOptions>? json = null, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_1, Action<WebApplication>? endpoints = null, - Type[]? controllers = null) + Type[]? controllers = null, + Action<IServiceCollection>? services = null) { // Pinned, not read from ASPNETCORE_ENVIRONMENT: in Development the host adds the developer exception // page on its own, and a refusal then reaches the test as a 500 instead of the exception it asserts. @@ -71,6 +73,7 @@ public static async Task<ProbeHost> StartAsync( }); } if (json is not null) builder.Services.ConfigureHttpJsonOptions(json); + services?.Invoke(builder.Services); // The converters and the resolvers of one's own go on MVC's options too, as a host with controllers // must register them (DescribedOptionsAgreement refuses the document otherwise; DescribedSchemaTests diff --git a/test/CodoMetis.TypeKit.BashGuard.Tests/GuardTests.cs b/test/CodoMetis.TypeKit.BashGuard.Tests/GuardTests.cs index 4235a372..f7ce8c62 100644 --- a/test/CodoMetis.TypeKit.BashGuard.Tests/GuardTests.cs +++ b/test/CodoMetis.TypeKit.BashGuard.Tests/GuardTests.cs @@ -76,6 +76,14 @@ internal static string Payload(string command) [InlineData("the verdict piped into tail", "dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test | tail -5")] [InlineData("the verdict piped into head, redirected", "dotnet ./tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll build 2>&1 | head")] [InlineData("the verdict by dotnet's path, piped into less", "/usr/local/share/dotnet/dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test |& less")] + [InlineData("a query that runs a target", "dotnet msbuild src/A/A.csproj -getProperty:TargetPath -t:Build")] + [InlineData("a query that runs a target, spelled out", "dotnet msbuild src/A/A.csproj --target:Pack -getItem:Compile")] + [InlineData("a query that runs a target, Windows-style", "dotnet msbuild src/A/A.csproj /t:Build /getProperty:TargetPath")] + [InlineData("a target result, which builds", "dotnet msbuild src/A/A.csproj -getTargetResult:Build")] + [InlineData("a query that restores", "dotnet msbuild src/A/A.csproj -restore -getProperty:Version")] + [InlineData("a query that restores, short", "dotnet msbuild src/A/A.csproj -r -getItem:PackageReference")] + [InlineData("a property set, not queried", "dotnet msbuild src/A/A.csproj -property:Configuration=Release")] + [InlineData("a build that queries", "dotnet build src/A/A.csproj -getProperty:TargetPath")] public void Refused(string name, string command) => Guard(Payload(command)).Code.ShouldBe(2, name); // ---- what passes ---------------------------------------------------------------------------------- @@ -95,6 +103,11 @@ internal static string Payload(string command) [InlineData("the verdict tool redirected to a file", "dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll build > /tmp/v.txt 2>&1")] [InlineData("the verdict tool into grep", "dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test | grep FAIL")] [InlineData("the smoke test", "dotnet tools/ConsumerSmokeTest/bin/Release/net10.0/consumer-smoke-test.dll dist")] + [InlineData("a property query", "dotnet msbuild src/A/A.csproj -getProperty:IsRoslynComponent -getItem:ProjectReference -nodeReuse:false")] + [InlineData("an item query, spelled out", "dotnet msbuild src/A/A.csproj --getItem:Compile")] + [InlineData("a property query, Windows-style and any case", "dotnet msbuild src/A/A.csproj /GetProperty:Version")] + [InlineData("a property query with a property set", "dotnet msbuild src/A/A.csproj -p:Configuration=Release -getProperty:OutputPath")] + [InlineData("a property query in a loop", "for p in A B; do dotnet msbuild src/$p/$p.csproj -getItem:ProjectReference -getProperty:IsRoslynComponent; done")] [InlineData("dotnet as an argument", "echo dotnet build")] [InlineData("inside double quotes", "git commit -m \"dotnet build is refused\"")] [InlineData("inside double quotes with operators", "echo \"x && dotnet build && y\"")] diff --git a/test/CodoMetis.TypeKit.BuildVerdict.Tests/ToolSandbox.cs b/test/CodoMetis.TypeKit.BuildVerdict.Tests/ToolSandbox.cs index 450b686e..2a3bc4ca 100644 --- a/test/CodoMetis.TypeKit.BuildVerdict.Tests/ToolSandbox.cs +++ b/test/CodoMetis.TypeKit.BuildVerdict.Tests/ToolSandbox.cs @@ -53,7 +53,7 @@ public static (int Code, string Output, string Error) RunProcess( foreach (var a in arguments) psi.ArgumentList.Add(a); // The caller's own settings never reach the tool: a DOTNET_CLI_UI_LANGUAGE=en exported in the shell // that runs the tests would make the stub answer in English whether or not the tool pins it. - foreach (var name in new[] { "BUILD_VERDICT_LOG", "BUILD_VERDICT_EXIT", "BUILD_VERDICT_DOTNET", "STUB_CALLS", "STUB_ROOT", "STUB_BUILD", "DOTNET_CLI_UI_LANGUAGE" }) + foreach (var name in new[] { "BUILD_VERDICT_LOG", "BUILD_VERDICT_EXIT", "BUILD_VERDICT_DOTNET", "BUILD_VERDICT_LOG_DIR", "STUB_CALLS", "STUB_ROOT", "STUB_BUILD", "STUB_SHARED_LOG", "DOTNET_CLI_UI_LANGUAGE" }) psi.Environment.Remove(name); foreach (var (k, v) in environment ?? new Dictionary<string, string?>()) { diff --git a/test/CodoMetis.TypeKit.BuildVerdict.Tests/VerdictTests.cs b/test/CodoMetis.TypeKit.BuildVerdict.Tests/VerdictTests.cs index 2c3a5b41..82993309 100644 --- a/test/CodoMetis.TypeKit.BuildVerdict.Tests/VerdictTests.cs +++ b/test/CodoMetis.TypeKit.BuildVerdict.Tests/VerdictTests.cs @@ -270,6 +270,29 @@ public void An_unreadable_passing_run() Holds(); } + // The same passing run as GitHub Actions prints it: the test platform colours redirected output there, and + // an escape sequence opens the summary's lines. Measured 2026-10-15 on the first CI replay of the probes. + [Fact] + public void A_passing_run_in_colour() + { + var esc = (char)27; + var f = $""" + Running tests from test/A.Tests/bin/Debug/net10.0/A.Tests.dll (net10.0|x64) + {esc}[m{R}/test/A.Tests/bin/Debug/net10.0/A.Tests.dll (net10.0|x64) {esc}[32mpassed{esc}[m {esc}[90m(17s 005ms){esc}[m + + {esc}[32mTest run summary: Passed! + {esc}[m total: 177 + failed: 0 + {esc}[32m succeeded: 177 + {esc}[m skipped: 0 + duration: 17s 005ms + + """; + Exited("a passing run in colour exits 0", 0, Verdict(0, f, "test", "--project", "test/A.Tests")); + First("a passing run in colour reads its summary", "TESTS OK: 177 passed, 0 skipped, A.Tests, 17s 005ms"); + Holds(); + } + // A solution-wide filter that matched one test, which passed: every other assembly exits 8, and so does the run. [Fact] public void A_solution_filter_that_matched_and_passed() @@ -334,16 +357,21 @@ failed CodoMetis.TypeKit.Tests.ParsingTests.Rounds_trip(text: "1.5", culture: "d Actual: 15 at CodoMetis.TypeKit.Tests.ParsingTests.Rounds_trip(String text, String culture) in {R}/test/A.Tests/ParsingTests.cs:40 Standard output: noise from the test - {R}/test/A.Tests/bin/Debug/net10.0/A.Tests.dll (net10.0|arm64) failed with 3 error(s) (1s 147ms) + failed Probes.L01JsonBytesProbe.Row [#6 shape.kind=record-struct shape.wrapped=int] (5ms) + from {R}/test/A.Tests/bin/Debug/net10.0/A.Tests.dll (net10.0|arm64) + Assert.Fail() Failure: [L01 #6] bytes differ + at Probes.Rec.Run(String claim, Int32 idx) in {R}/test/A.Tests/Probes/Row.cs:12 + {R}/test/A.Tests/bin/Debug/net10.0/A.Tests.dll (net10.0|arm64) failed with 4 error(s) (1s 147ms) Exit code: 2 - """ + Summary(850, 3, 846, 1); + """ + Summary(850, 4, 845, 1); Exited("a failing run exits 1", 1, Verdict(2, f, "test")); - First("the failed count leads", "TESTS FAILED: 3 failed of 850 (846 passed, 1 skipped), solution, 1s 261ms"); + First("the failed count leads", "TESTS FAILED: 4 failed of 850 (845 passed, 1 skipped), solution, 1s 261ms"); Has("a block is headed Class.Method and file:line", "## ZzzVerdictProbeTests.Fails_on_a_value test/A.Tests/ZzzVerdictProbeTests.cs:6"); Has("a multi-line assertion is one line", " ShouldAssertException: public void Fails_on_a_valueOption.Some(2).Match(x => x, 0) should be 1 but was 2"); Has("an exception loses xUnit's wrapper and namespaces", " InvalidOperationException: probe threw"); Has("a theory keeps its arguments, dots and all", "## ParsingTests.Rounds_trip(text: \"1.5\", culture: \"de-DE\") test/A.Tests/ParsingTests.cs:40"); + Has("a data row keeps its label, dots and all", "## L01JsonBytesProbe.Row [#6 shape.kind=record-struct shape.wrapped=int] test/A.Tests/Probes/Row.cs:12"); Has("an Assert.Equal keeps expected and actual", "Assert.Equal() Failure: Values differ Expected: 1.5 Actual: 15"); Hasnt("from lines are dropped", " from "); Hasnt("frames outside the tree are dropped", "MethodBaseInvoker"); @@ -509,6 +537,7 @@ public void A_crashed_host() Console.WriteLine(en ? "Build succeeded.\nTime Elapsed 00:00:01.00" : "Der Buildvorgang wurde erfolgreich ausgeführt."); break; case "test": + if (Environment.GetEnvironmentVariable("STUB_SHARED_LOG") is { } shared) File.AppendAllText(calls, $"shared-log-exists={File.Exists(shared)}\n"); Console.Write(en ? " total: 3\n failed: 0\n succeeded: 3\n skipped: 0\n duration: 1s\n" : " gesamt: 3\n fehlgeschlagen: 0\n erfolgreich: 3\n"); break; } @@ -529,14 +558,18 @@ public void A_crashed_host() private string Call(int n) => RecordedCalls().ElementAtOrDefault(n - 1) ?? ""; private string LogFile => Path.Combine(ToolSandbox.RunProcess("git", ["-C", R, "rev-parse", "--absolute-git-dir"], sandbox.Tool.Work).Output.Trim(), "build-verdict-last.log"); - private int RunStub(string? build, params string[] arguments) + private int RunStub(string? build, params string[] arguments) => RunStubWith(new Dictionary<string, string?>(), build, arguments); + + private int RunStubWith(IReadOnlyDictionary<string, string?> extra, string? build, params string[] arguments) { File.Exists(Stub.Value).ShouldBeTrue($"the stub dotnet did not build: {Stub.Value}"); File.WriteAllText(Calls, ""); - return Run(new Dictionary<string, string?> + var environment = new Dictionary<string, string?> { ["BUILD_VERDICT_DOTNET"] = Stub.Value, ["STUB_CALLS"] = Calls, ["STUB_ROOT"] = R, ["STUB_BUILD"] = build, - }, arguments); + }; + foreach (var (name, value) in extra) environment[name] = value; + return Run(environment, arguments); } [Fact] @@ -569,6 +602,58 @@ public void A_pinned_test_run() Holds(); } + /// <summary> + /// The probe replay builds the probes once and runs many at a time; a build per run would race in the + /// project's obj directory. So --no-build runs the test step alone, and the verdict reads as it does after + /// a build. + /// </summary> + [Fact] + public void A_pinned_test_run_without_a_build() + { + var project = Path.GetFullPath(Path.Combine(R, "test", "A.Tests")); + Exited("a test run without a build passes", 0, RunStub(null, "test", "--no-build", "--project", Path.Combine("repo", "test", "A.Tests"), "--filter-class", "*A*")); + First("a test run without a build reads its summary", "TESTS OK: 3 passed, 0 skipped, A.Tests, --filter-class *A*, 1s"); + Check(RecordedCalls().Length == 2, "no build runs: the SDK check and the test step only", string.Join("; ", RecordedCalls())); + Check(Call(2) == $"en|test --no-build --project {project} --filter-class *A*", "the test step is --no-build with the filters", Call(2)); + var log = File.Exists(LogFile) ? File.ReadAllText(LogFile) : ""; + Check(log.Split('\n').Any(l => l.StartsWith("==== build-verdict: dotnet test", StringComparison.Ordinal)) && !log.Contains("Build succeeded", StringComparison.Ordinal), + "the log holds the test step and no build", log); + + Exited("a failing build is still reported when --no-build is not given", 1, RunStub("fail", "test", "--project", Path.Combine("repo", "test", "A.Tests"))); + Holds(); + } + + /// <summary> + /// The probe replay runs several of these at once. Each reads the log it wrote, and the shared log is + /// replaced only when the process is done: the stub sees no shared log during the test step, and the + /// verdict is read all the same. Measured 2026-10-16 before this: six at once reported one duration, the + /// last one's. + /// </summary> + [Fact] + public void A_process_reads_its_own_log_and_publishes_it_when_done() + { + if (File.Exists(LogFile)) File.Delete(LogFile); + Exited("a run passes", 0, RunStubWith(new Dictionary<string, string?> { ["STUB_SHARED_LOG"] = LogFile }, null, "test", "--no-build", "--project", Path.Combine("repo", "test", "A.Tests"))); + First("the verdict is read from the process's own log", "TESTS OK: 3 passed, 0 skipped, A.Tests, 1s"); + Check(RecordedCalls().Contains("shared-log-exists=False"), "the shared log is untouched while the process runs", string.Join("; ", RecordedCalls())); + Check(File.Exists(LogFile), "the shared log is published when the process is done", LogFile); + Check(!Directory.EnumerateFiles(Path.GetDirectoryName(LogFile)!).Any(f => f.EndsWith(".tmp", StringComparison.Ordinal)), "no log of the process is left beside the shared one", "a .tmp remains"); + Holds(); + } + + [Fact] + public void A_log_directory_keeps_each_process_s_log_and_leaves_the_shared_one_alone() + { + if (File.Exists(LogFile)) File.Delete(LogFile); + var logs = Path.Combine(sandbox.Tool.Work, "logs-" + Guid.NewGuid().ToString("N")); + Exited("a run passes", 0, RunStubWith(new Dictionary<string, string?> { ["BUILD_VERDICT_LOG_DIR"] = logs }, null, "test", "--no-build", "--project", Path.Combine("repo", "test", "A.Tests"))); + var kept = Directory.Exists(logs) ? Directory.GetFiles(logs) : []; + Check(kept.Length == 1 && System.Text.RegularExpressions.Regex.IsMatch(Path.GetFileName(kept[0]), @"^build-verdict-\d+\.log$"), "one log, named by the process id, is kept in the directory", string.Join("; ", kept)); + Check(kept.Length == 1 && File.ReadAllText(kept[0]).Contains("==== build-verdict: dotnet test", StringComparison.Ordinal), "the kept log is the run's", kept.Length == 1 ? File.ReadAllText(kept[0]) : "no log"); + Check(!File.Exists(LogFile), "the shared log is left alone", LogFile); + Holds(); + } + [Fact] public void A_test_run_whose_build_fails() { diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/CommitTrailerTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/CommitTrailerTests.cs new file mode 100644 index 00000000..96c3c758 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/CommitTrailerTests.cs @@ -0,0 +1,194 @@ +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every <c>fix:</c> commit since v2.1.0 names its defect class and its finder in two trailers, +/// <c>Defect-Class:</c> and <c>Found-By:</c>, from the vocabulary the fix history counted with +/// (<c>design/adr/0094-topic-branches-into-a-release-branch-and-fix-trailers.md</c>; <c>VA</c>, the variant step of +/// another fix, from <c>design/adr/0095-a-fix-is-tested-across-its-input-class.md</c>). +/// </summary> +/// <remarks> +/// The trailers are read by git's own trailer parser, so a line git does not take for a trailer (one +/// outside the message's last paragraph) does not count. A shallow clone, or one without the tag, is +/// refused rather than read: it would hold no fix commit, and the check would pass on nothing. +/// </remarks> +public sealed partial class CommitTrailerTests +{ + /// <summary>The last release before the trailers: the 2.2.0 cycle is the first that carries them.</summary> + private const string Since = "v2.1.0"; + + private static readonly HashSet<string> DefectClasses = + new(["FCI", "GTS", "AIS", "EFT", "API", "POS", "DOC", "TXT", "AOT", "SDL", "CRP", "TOOL"], StringComparer.Ordinal); + + private static readonly HashSet<string> Finders = + new(["RS", "CA", "PR", "ST", "OR", "US", "MS", "FR", "SF", "VA", "UN"], StringComparer.Ordinal); + + /// <summary> + /// Fix commits that reached a pushed branch without their trailers, which is never rewritten: the full + /// hash, and the trailers the commit should have carried. + /// </summary> + private static readonly Dictionary<string, (string DefectClass, string FoundBy)> Exempt = new(StringComparer.Ordinal) + { + // fix: a nullable value object is its wrapped type at the same nullability in every position (PR #28): its + // trailers stood a paragraph above Co-Authored-By, outside the trailer block. + ["686682291de8060074fd26a2ce4ae121d6dc515c"] = ("FCI", "PR"), + }; + + [Fact] + public void The_history_is_whole() => RequireWholeHistory(); + + [Fact] + public void Every_fix_since_2_1_0_names_its_defect_class_and_its_finder() + { + RequireWholeHistory(); + + var problems = FixCommits(Repository.Root, $"{Since}..HEAD") + .Where(commit => !Exempt.ContainsKey(commit.Hash)) + .SelectMany(Problems) + .ToArray(); + + problems.ShouldBeEmpty( + "A fix: commit carries `Defect-Class:` and `Found-By:` in its trailer block, one of each, from the tables of " + + "design/adr/0094 and 0095. Amend it before the branch is pushed; once pushed, add its hash to Exempt with the " + + "trailers it should have carried."); + } + + [Fact] + public void The_exemptions_use_the_vocabulary() + { + foreach (var (hash, (defectClass, foundBy)) in Exempt) + { + DefectClasses.ShouldContain(defectClass, $"Exempt {hash}"); + Finders.ShouldContain(foundBy, $"Exempt {hash}"); + } + } + + /// <summary> + /// The positive control: a throwaway repository with a fix commit of each kind, so an empty finding on + /// the real history means none, not a reader that sees nothing. + /// </summary> + [Fact] + public void The_reader_finds_each_kind_of_fix_commit() + { + var repository = Directory.CreateTempSubdirectory("codometis-typekit-trailers-").FullName; + + try + { + Git(repository, "init", "-q"); + Commit(repository, "fix: both trailers\n\nWhat was wrong.\n\nDefect-Class: GTS\nFound-By: CA\nCo-Authored-By: A <a@example.com>\n"); + Commit(repository, "fix(ef): no finder\n\nDefect-Class: EFT\n"); + Commit(repository, "feat: not a fix\n"); + Commit(repository, "fix!: an unknown class and a finder twice\n\nDefect-Class: XYZ\nFound-By: RS\nFound-By: CA\n"); + Commit(repository, "fix: trailers outside the last paragraph\n\nDefect-Class: GTS\nFound-By: CA\n\nA closing paragraph.\n"); + + var commits = FixCommits(repository, "HEAD").Reverse().ToArray(); + + commits.Select(commit => commit.Subject).ShouldBe( + [ + "fix: both trailers", + "fix(ef): no finder", + "fix!: an unknown class and a finder twice", + "fix: trailers outside the last paragraph", + ]); + + Problems(commits[0]).ShouldBeEmpty(); + Problems(commits[1]).ShouldHaveSingleItem().ShouldContain("no Found-By"); + Problems(commits[2]).Length.ShouldBe(2); + Problems(commits[2]).ShouldContain(problem => problem.Contains("Defect-Class XYZ")); + Problems(commits[2]).ShouldContain(problem => problem.Contains("Found-By twice")); + Problems(commits[3]).Length.ShouldBe(2, "A trailer outside the last paragraph is no trailer."); + } + finally + { + foreach (var file in Directory.EnumerateFiles(repository, "*", SearchOption.AllDirectories)) + File.SetAttributes(file, FileAttributes.Normal); // git's objects are read-only, which Windows will not delete + Directory.Delete(repository, recursive: true); + } + } + + private sealed record FixCommit(string Hash, string Subject, string[] DefectClass, string[] FoundBy); + + private static string[] Problems(FixCommit commit) + { + var name = $"{commit.Hash[..7]} {commit.Subject}"; + + return [.. Check(name, "Defect-Class", commit.DefectClass, DefectClasses), .. Check(name, "Found-By", commit.FoundBy, Finders)]; + + static IEnumerable<string> Check(string name, string key, string[] values, HashSet<string> vocabulary) + { + if (values.Length == 0) yield return $"{name}: no {key}"; + else if (values.Length > 1) yield return $"{name}: {key} twice or more ({string.Join(", ", values)})"; + else if (!vocabulary.Contains(values[0])) yield return $"{name}: {key} {values[0]} is none of {string.Join(", ", vocabulary)}"; + } + } + + /// <summary>The non-merge commits of <paramref name="range"/> whose subject is a conventional <c>fix:</c>, newest first.</summary> + private static FixCommit[] FixCommits(string repository, string range) + { + const string format = + "--format=%H%x1f%s%x1f%(trailers:key=Defect-Class,valueonly,separator=%x1d)%x1f%(trailers:key=Found-By,valueonly,separator=%x1d)%x1e"; + + return + [ + .. Git(repository, "log", "--no-merges", format, range) + .Split('\x1e') + .Select(record => record.Trim('\n', '\r')) + .Where(record => record.Length > 0) + .Select(record => record.Split('\x1f')) + .Where(fields => FixSubject().IsMatch(fields[1])) + .Select(fields => new FixCommit(fields[0], fields[1], Values(fields[2]), Values(fields[3]))) + ]; + + static string[] Values(string field) => [.. field.Split('\x1d').Select(value => value.Trim()).Where(value => value.Length > 0)]; + } + + [GeneratedRegex(@"^fix(\([^)]*\))?!?:")] + private static partial Regex FixSubject(); + + private static void RequireWholeHistory() + { + Git(Repository.Root, "rev-parse", "--is-shallow-repository").Trim().ShouldBe( + "false", + "A shallow clone holds no history to check: run `git fetch --unshallow`, or check out with fetch-depth: 0 in CI."); + + Run(Repository.Root, "rev-parse", "--verify", "--quiet", $"{Since}^{{commit}}").ExitCode.ShouldBe( + 0, + $"The tag {Since} is missing: run `git fetch --tags`."); + } + + private static void Commit(string repository, string message) + { + var file = Path.Combine(repository, ".git", "MESSAGE"); + File.WriteAllText(file, message); + + // No hook of the machine's runs here, and no signing prompt. + Git(repository, "-c", "user.name=Test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", + "-c", $"core.hooksPath={Path.Combine(repository, "no-hooks")}", "commit", "-q", "--allow-empty", "-F", file); + } + + private static string Git(string repository, params string[] arguments) + { + var (exitCode, output, errors) = Run(repository, arguments); + exitCode.ShouldBe(0, $"git {string.Join(' ', arguments)} failed: {errors}"); + return output; + } + + private static (int ExitCode, string Output, string Errors) Run(string repository, params string[] arguments) + { + using var process = Process.Start(new ProcessStartInfo("git", ["-C", repository, "-c", "i18n.logOutputEncoding=UTF-8", .. arguments]) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = Encoding.UTF8, + })!; + + var errors = process.StandardError.ReadToEndAsync(); + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return (process.ExitCode, output, errors.Result); + } +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ConditionalChainTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ConditionalChainTests.cs new file mode 100644 index 00000000..10a00a33 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ConditionalChainTests.cs @@ -0,0 +1,101 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// No conditional expression in the solution's sources nests more than <see cref="MaxDepth"/> +/// conditionals through its branches. A longer chain is an <c>if</c> and <c>return</c> per arm. +/// </summary> +/// <remarks> +/// <para> +/// Rider's type inference walks a nested conditional again at every level of it. The 26-arm chain +/// <c>BuiltInConverters.For</c> was written as froze Rider's backend whenever the file was analysed, +/// and its thread dump crashed the IDE; the compiler and the tests never noticed. +/// </para> +/// <para> +/// It reads syntax, so a chain in a comment or a string does not count, and the positive control +/// keeps it from passing because the walker stopped finding anything. +/// </para> +/// </remarks> +public sealed class ConditionalChainTests +{ + private const int MaxDepth = 8; + + private static readonly string[] SolutionFolders = ["src", "test", "tools", "benchmarks"]; + + [Fact] + public void The_solutions_sources_are_discovered() => + Sources().Count.ShouldBeGreaterThanOrEqualTo(400, string.Join(", ", Sources().Take(20))); + + [Fact] + public void A_chain_deeper_than_the_limit_is_found() + { + const string source = + """ + class C + { + int Deep(int x) => x == 1 ? 1 : x == 2 ? 2 : (x == 3 ? 3 : x == 4 ? 4 : x == 5 ? 5 : x == 6 ? 6 : x == 7 ? 7 : x == 8 ? 8 : x == 9 ? 9 : 0); + int AtTheLimit(int x) => x == 1 ? 1 : x == 2 ? 2 : x == 3 ? 3 : x == 4 ? 4 : x == 5 ? 5 : x == 6 ? 6 : x == 7 ? 7 : x == 8 ? 8 : 0; + int Separate(int x) => (x == 1 ? 1 : x == 2 ? 2 : x == 3 ? 3 : x == 4 ? 4 : 0) + (x == 5 ? 5 : x == 6 ? 6 : x == 7 ? 7 : x == 8 ? 8 : x == 9 ? 9 : 0); + } + """; + + DeepChains(source, Path.Combine(Repository.Root, "C.cs")).ShouldBe(["C.cs:3: 9 nested conditionals"]); + } + + [Fact] + public void No_conditional_chain_is_deeper_than_the_limit() + { + var chains = Sources().SelectMany(path => DeepChains(File.ReadAllText(path), path)).ToArray(); + + chains.ShouldBeEmpty( + $"Rider's type inference walks a nested conditional again at every level, and a long chain freezes its backend. " + + $"Write more than {MaxDepth} arms as an if and a return each:{Environment.NewLine}{string.Join(Environment.NewLine, chains)}"); + } + + private static IReadOnlyList<string> Sources() => + [ + .. SolutionFolders.Select(folder => Path.Combine(Repository.Root, folder)) + .Where(Directory.Exists) + .SelectMany(folder => Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories) + .Where(path => !Path.GetRelativePath(folder, path).Split(Path.DirectorySeparatorChar).Any(part => part is "bin" or "obj"))) + .Order(StringComparer.Ordinal) + ]; + + private static IEnumerable<string> DeepChains(string source, string path) + { + var cancellationToken = TestContext.Current.CancellationToken; + var tree = CSharpSyntaxTree.ParseText(source, path: path, cancellationToken: cancellationToken); + var lines = tree.GetText(cancellationToken).Lines; + + return tree.GetRoot(cancellationToken) + .DescendantNodes() + .OfType<ConditionalExpressionSyntax>() + .Where(conditional => !IsBranchOfConditional(conditional)) + .Select(conditional => (Node: conditional, Depth: Depth(conditional))) + .Where(chain => chain.Depth > MaxDepth) + .Select(chain => $"{Path.GetRelativePath(Repository.Root, path)}:{lines.GetLineFromPosition(chain.Node.SpanStart).LineNumber + 1}: {chain.Depth} nested conditionals"); + } + + /// <summary>The conditionals nested through branches, parentheses looked through.</summary> + private static int Depth(ExpressionSyntax expression) => + Unparenthesized(expression) is ConditionalExpressionSyntax conditional + ? 1 + Math.Max(Depth(conditional.WhenTrue), Depth(conditional.WhenFalse)) + : 0; + + private static bool IsBranchOfConditional(SyntaxNode node) + { + var child = node; + while (child.Parent is ParenthesizedExpressionSyntax parenthesized) child = parenthesized; + + return child.Parent is ConditionalExpressionSyntax parent && (parent.WhenTrue == child || parent.WhenFalse == child); + } + + private static ExpressionSyntax Unparenthesized(ExpressionSyntax expression) + { + while (expression is ParenthesizedExpressionSyntax parenthesized) expression = parenthesized.Expression; + return expression; + } +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/GeneratedHarnessTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/GeneratedHarnessTests.cs new file mode 100644 index 00000000..4737a5bb --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/GeneratedHarnessTests.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every generated file of the probe harness (<c>tools/probe/corpus/harness/*.g.cs</c>) is what its generator writes +/// from the tracked inputs today: the generator is run into a temporary directory and its output compared with the +/// committed file. +/// </summary> +/// <remarks> +/// The generators are file-based apps under <c>tools/probe/generators/</c> (design/adr/0092, form 1), which have no +/// tests of their own; this is their guard. Without it a hand edit of a <c>.g.cs</c>, or a rows file changed by +/// <c>prober rows</c> without regenerating the declarations, would leave the harness built from something nothing can +/// reproduce, as the Python originals did when they left the repository. The comparison ignores line endings only, +/// since a Windows checkout may write the committed file with CRLF and the generators write LF, as git stores it. +/// </remarks> +public sealed class GeneratedHarnessTests +{ + private const string Harness = "tools/probe/corpus/harness"; + + private const string Rows = "tools/probe/corpus/rows"; + + private const string Axes = ".claude/probe-axes.md"; + + /// <summary> + /// The one place the mapping lives: each generated file, the app that writes it and its arguments, the paths from + /// the repository root. The test adds <c>--out</c>. The EF catalogue is E01's and E02's first round of rows (the + /// rows appended since run on EfShapes3's and EfShapes4's complete grids), so it reads only those. + /// </summary> + private static readonly (string File, string App, string[] Arguments)[] Generators = + [ + ("Shapes.g.cs", "shapes.cs", ["--axes", Axes, "--l06", $"{Rows}/L06OffTheWireProbe.json.gz"]), + ("Containers.g.cs", "containers.cs", ["--axes", Axes]), + ("EfShapes.g.cs", "ef.cs", ["catalogue", .. EfCatalogue]), + ("EfShapes3.g.cs", "ef.cs", ["top-level", .. EfCatalogue]), + ("EfShapes4.g.cs", "ef.cs", ["nested", .. EfCatalogue]), + ("KeyShapes.g.cs", "ef.cs", ["keys", "--rows", $"{Rows}/E04IntegerKeyProbe.json.gz"]), + ("JsonFamilyShapes.g.cs", "json-family.cs", ["stand-ins"]), + ("L05Shapes.g.cs", "json-family.cs", ["l05", "--rows", $"{Rows}/L05EqualityProbe.json.gz"]), + ("C09Shapes.g.cs", "decls.cs", ["C09", "--rows", $"{Rows}/C09BaseRecordEqualityProbe.json.gz"]), + ("C11Shapes.g.cs", "decls.cs", ["C11", "--rows", $"{Rows}/C11ParseShapesBuildProbe.json.gz"]), + ("FaultClaimControllers.g.cs", "faults.cs", []), + ("RuleControllers.g.cs", "rules.cs", []), + ]; + + private static string[] EfCatalogue => + [ + "--rows", $"{Rows}/E01ColumnParityProbe.json.gz", "--first", "90", + "--rows", $"{Rows}/E02ValueTranslatesProbe.json.gz", "--first", "108", + ]; + + public static TheoryData<string> Files => [.. Generators.Select(generator => generator.File)]; + + [Fact] + public void Every_generated_file_of_the_harness_has_its_generator() + { + var committed = Directory.GetFiles(Path.Combine(Repository.Root, Harness), "*.g.cs") + .Select(Path.GetFileName).OfType<string>().Order(StringComparer.Ordinal).ToList(); + + committed.Count.ShouldBeGreaterThanOrEqualTo(12, $"{Harness} holds {committed.Count} generated files; it held twelve when this test was written."); + committed.ShouldBe( + Generators.Select(generator => generator.File).Order(StringComparer.Ordinal).ToList(), ignoreOrder: false, + $"Every {Harness}/*.g.cs is written by an app of tools/probe/generators, listed in {nameof(Generators)} with its arguments, and every listed file exists."); + } + + [Theory] + [MemberData(nameof(Files))] + public async Task The_committed_file_is_its_generators_output(string file) + { + var (_, app, arguments) = Generators.Single(generator => generator.File == file); + var directory = Directory.CreateTempSubdirectory("generated-harness-"); + try + { + var output = Path.Combine(directory.FullName, file); + string[] command = ["run", $"tools/probe/generators/{app}", "--", .. arguments, "--out", output]; + using var process = Process.Start(new ProcessStartInfo("dotnet", command) + { + WorkingDirectory = Repository.Root, + RedirectStandardOutput = true, + RedirectStandardError = true, + })!; + + var standardOutput = process.StandardOutput.ReadToEndAsync(TestContext.Current.CancellationToken); + var errors = process.StandardError.ReadToEndAsync(TestContext.Current.CancellationToken); + await process.WaitForExitAsync(TestContext.Current.CancellationToken); + + var invocation = $"dotnet {string.Join(' ', command[..^1])} {Harness}/{file}"; + process.ExitCode.ShouldBe(0, $"`{invocation}` failed:{Environment.NewLine}{await standardOutput}{await errors}"); + + var expected = (await File.ReadAllTextAsync(output, TestContext.Current.CancellationToken)).ReplaceLineEndings("\n"); + var actual = (await File.ReadAllTextAsync(Path.Combine(Repository.Root, Harness, file), TestContext.Current.CancellationToken)).ReplaceLineEndings("\n"); + (actual == expected).ShouldBeTrue( + $"{Harness}/{file} is not what {app} writes from the tracked inputs ({FirstDifference(actual, expected)}). " + + $"Regenerate it with `{invocation}` and commit the result; a hand edit belongs in the generator."); + } + finally + { + directory.Delete(recursive: true); + } + } + + private static string FirstDifference(string committed, string generated) + { + var (a, b) = (committed.Split('\n'), generated.Split('\n')); + for (var line = 0; line < Math.Min(a.Length, b.Length); line++) + { + if (a[line] != b[line]) return $"first difference at line {line + 1}: committed `{Cut(a[line])}`, generated `{Cut(b[line])}`"; + } + return $"the committed file has {a.Length} lines, the generated one {b.Length}"; + } + + private static string Cut(string line) => line.Length > 160 ? line[..160] + "..." : line; +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/PackageDependencyTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/PackageDependencyTests.cs index eca372b0..02534da9 100644 --- a/test/CodoMetis.TypeKit.Conventions.Tests/PackageDependencyTests.cs +++ b/test/CodoMetis.TypeKit.Conventions.Tests/PackageDependencyTests.cs @@ -72,6 +72,19 @@ public void No_package_depends_on_the_generators(string package) => .ShouldNotContain(GeneratorPackage, $"{package} depends on {GeneratorPackage}. The satellites work at run time against the interfaces, and taking Metalama on is the consumer's named choice."); + /// <summary> + /// The analyzers run on every shipping project's own code through an analyzer-only reference + /// (<c>src/Directory.Build.targets</c>). That reference is for this build, not for consumers, who get the + /// analyzers once, through the base package. + /// </summary> + [Theory] + [MemberData(nameof(DependentPackages))] + public void A_dependent_package_brings_the_analyzers_only_through_the_base_package(string package) => + Dependencies(packs.Nuspec(package)).Select(element => (string?)element.Attribute("id")) + .ShouldNotContain(AnalyzerPackage, + $"{package} depends on {AnalyzerPackage} directly. The reference in src/Directory.Build.targets runs the analyzers on " + + "this package's own code and must not reach its consumers: keep ReferenceOutputAssembly=\"false\" and PrivateAssets=\"all\" on it."); + private static string Version(XDocument nuspec) => nuspec.Descendants().Single(element => element.Name.LocalName == "version" && element.Parent?.Name.LocalName == "metadata").Value; diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ProbeCiteTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeCiteTests.cs new file mode 100644 index 00000000..de7969a7 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeCiteTests.cs @@ -0,0 +1,135 @@ +using System.Text.Json.Nodes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every reason a check gives for not applying or for meeting a refusal is cited, and what it cites exists: in the +/// probe corpus, the first argument of a check's <c>Inapplicable</c> or <c>Refused</c> is a <c>Cite</c> factory +/// (<c>NotPromised</c>, <c>Docs</c>, <c>Fact</c>, <c>Record</c>) called with a string literal, and a +/// <c>NotPromised</c> id is a not-promised entry of the probe's claim (of any claim, for a harness source no one +/// probe owns), a <c>Docs</c> source names a file of the repository, a <c>Fact</c> an entry under design/knowledge/ +/// and a <c>Record</c> a file under design/adr/. +/// </summary> +/// <remarks> +/// The factories check the form when a check runs; this holds, before any build, that an excuse names a reason a +/// reader can find. `prober register` and `publish` hold the same for the staged sources (SourceLint). It reads +/// syntax, so a call in a comment or a string does not count, and the positive control finds each rule's defect. No +/// floor yet: no probe calls a check's <c>Inapplicable</c> or <c>Refused</c> until the conversion that starts +/// with the per-check evidence. +/// </remarks> +public sealed class ProbeCiteTests +{ + private static string Corpus => Path.Combine(Repository.Root, "tools", "probe", "corpus"); + + [Fact] + public void Every_citation_in_the_corpus_is_a_factory_with_a_literal_naming_what_exists() + { + var context = Context.Of(Repository.Root); + Directory.EnumerateFiles(Corpus, "*.cs", SearchOption.AllDirectories).Order(StringComparer.Ordinal) + .SelectMany(path => Problems(Path.GetRelativePath(Repository.Root, path).Replace('\\', '/'), File.ReadAllText(path), context)) + .ShouldBeEmpty(); + } + + [Fact] + public void A_citation_built_from_text_or_naming_nothing_is_found() + { + const string source = + """ + class P1Probe + { + string? Check(Check c, string x, Entry e) + { + var refused = e.Refused(); + c.Inapplicable(Cite.NotPromised("c1-limit"), x); + c.Refused(Cite.Docs("README.md#what-typekit-guarantees")); + c.Refused(Cite.Fact("KB-001")); + c.Inapplicable(Cite.Record("0094"), "detail"); + var cite = Cite.NotPromised("c1-limit"); + c.Inapplicable(cite); + c.Refused(Cite.NotPromised("c1-" + x)); + c.Inapplicable(Cite.NotPromised("c2-limit")); + c.Refused(Cite.Docs("docs/gone.md#a")); + c.Refused(Cite.Fact("KB-999")); + c.Inapplicable(Cite.Record("9999")); + // c.Inapplicable(cite); + var text = "c.Inapplicable(cite)"; + return null; + } + } + """; + var context = new Context(Repository.Root, + new Dictionary<string, string>(StringComparer.Ordinal) { ["P1Probe"] = "C1" }, + new Dictionary<string, HashSet<string>>(StringComparer.Ordinal) { ["C1"] = ["c1-limit"], ["C2"] = ["c2-limit"] }); + Problems("probes/P1Probe.cs", source, context).ShouldBe( + [ + "probes/P1Probe.cs:11: Inapplicable(cite, ...) does not cite by a Cite factory with a string literal", + "probes/P1Probe.cs:12: Refused(Cite.NotPromised(\"c1-\" + x), ...) does not cite by a Cite factory with a string literal", + "probes/P1Probe.cs:13: Inapplicable(Cite.NotPromised(\"c2-limit\"), ...) names no not-promised entry of its claim C1", + "probes/P1Probe.cs:14: Refused(Cite.Docs(\"docs/gone.md#a\"), ...) names no file docs/gone.md in the repository", + "probes/P1Probe.cs:15: Refused(Cite.Fact(\"KB-999\"), ...) names no entry under design/knowledge/", + "probes/P1Probe.cs:16: Inapplicable(Cite.Record(\"9999\"), ...) names no record under design/adr/", + ]); + Problems("harness/Shared.cs", source.Replace("P1Probe", "Shared"), context) + .ShouldNotContain(p => p.Contains("c2-limit"), "a harness source no one probe owns may cite any claim's entry"); + } + + /// <summary>The repository's files, each claim's not-promised ids, and the claim of each probe by its name and by the class its filter selects.</summary> + private sealed record Context(string Root, Dictionary<string, string> ClaimOf, Dictionary<string, HashSet<string>> NotPromised) + { + public static Context Of(string root) + { + var corpus = Path.Combine(root, "tools", "probe", "corpus"); + var notPromised = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); + foreach (var claim in JsonNode.Parse(File.ReadAllText(Path.Combine(corpus, "claims.json")))!.AsArray().OfType<JsonObject>()) + notPromised[claim["id"]!.GetValue<string>()] = [.. (claim["not-promised"] as JsonArray ?? []).Select(n => n!["id"]!.GetValue<string>())]; + var claimOf = new Dictionary<string, string>(StringComparer.Ordinal); + foreach (var (name, entry) in JsonNode.Parse(File.ReadAllText(Path.Combine(corpus, "registry.json")))!["probes"]!.AsObject()) + { + var claim = entry!["claim"]!.GetValue<string>(); + claimOf[name] = claim; + if (entry["filter"]?.GetValue<string>().TrimStart('*') is { Length: > 0 } cls) claimOf.TryAdd(cls, claim); + } + return new Context(root, claimOf, notPromised); + } + } + + private static IEnumerable<string> Problems(string path, string text, Context context) + { + var claim = context.ClaimOf.GetValueOrDefault(Path.GetFileNameWithoutExtension(path)); + foreach (var call in CSharpSyntaxTree.ParseText(text).GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>()) + { + if (call.Expression is not MemberAccessExpressionSyntax { Name.Identifier.ValueText: var method and ("Inapplicable" or "Refused") }) continue; + var arguments = call.ArgumentList.Arguments; + if (arguments.Count == 0) continue; // an entry's own Refused() of the harness, not a check's + var line = call.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + var shown = $"{path}:{line}: {method}({arguments[0]}, ...)"; + if (arguments[0].Expression is not InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.ValueText: "Cite" }, Name.Identifier.ValueText: var factory and ("NotPromised" or "Docs" or "Fact" or "Record") }, + ArgumentList.Arguments: [{ Expression: LiteralExpressionSyntax literal }], + } || !literal.IsKind(SyntaxKind.StringLiteralExpression)) + { + yield return $"{shown} does not cite by a Cite factory with a string literal"; + continue; + } + var value = literal.Token.ValueText; + var missing = factory switch + { + "NotPromised" when claim is not null => context.NotPromised.GetValueOrDefault(claim)?.Contains(value) ?? false ? null : $"names no not-promised entry of its claim {claim}", + "NotPromised" => context.NotPromised.Values.Any(ids => ids.Contains(value)) ? null : "names no not-promised entry of any claim", + "Docs" => value.Split('#')[0] is var file && file.Length > 0 && !file.Contains("..", StringComparison.Ordinal) && File.Exists(Path.Combine(context.Root, file)) + ? null : $"names no file {value.Split('#')[0]} in the repository", + "Fact" => Named(Path.Combine(context.Root, "design", "knowledge"), value) ? null : "names no entry under design/knowledge/", + _ => Named(Path.Combine(context.Root, "design", "adr"), value) ? null : "names no record under design/adr/", + }; + if (missing is not null) yield return $"{shown} {missing}"; + } + } + + private static bool Named(string directory, string id) => + id.Length > 0 && Directory.Exists(directory) + && Directory.EnumerateFiles(directory, "*.md").Any(f => Path.GetFileName(f).StartsWith(id + "-", StringComparison.Ordinal) || Path.GetFileName(f) == id + ".md"); +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ProbeMessageTextTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeMessageTextTests.cs new file mode 100644 index 00000000..c0e67120 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeMessageTextTests.cs @@ -0,0 +1,148 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// No probe decides how a row ends by the words of a message, except to fail it: in the probe corpus (harness and probes), a +/// <c>.Message.Contains(</c>, <c>StartsWith(</c>, <c>EndsWith(</c> or <c>IndexOf(</c> stands only in the condition of a failure text, +/// an <c>if</c> whose branch returns one or raises a harness error, or a <c>?:</c> between <c>null</c> and one. A refusal the framework +/// makes on its own is shown by side (1) of the three-sided differential (the harness's <c>Sides</c>), compared by its type and stage; +/// an intended difference by a citation. A message read to excuse a row, the substring excuse decision 99 retired, is refused here. +/// </summary> +/// <remarks> +/// It reads syntax, as <see cref="ProbeNoteKeyTests"/> does: a lambda inside the condition counts as the condition (<c>errors.Any(e => +/// e.Message.StartsWith(...))</c>), an assignment, a <c>catch ... when</c> or a branch that passes does not. The EF family stays +/// two-sided in WS2's last step and is not changed by it: its one excuse by a message is listed in <see cref="Kept"/> with its reason, +/// and a listed site that no longer reads a message fails, so the list cannot outlive it. +/// </remarks> +public sealed class ProbeMessageTextTests +{ + private static string Corpus => Path.Combine(Repository.Root, "tools", "probe", "corpus"); + + private static readonly string[] Reads = ["Contains", "StartsWith", "EndsWith", "IndexOf"]; + + /// <summary>The sites kept, by file, each with why: the EF family is two-sided by decision of WS2's last step.</summary> + private static readonly Dictionary<string, string> Kept = new() + { + ["tools/probe/corpus/probes/E06OwnConverterNoValueProbe.cs"] = + "EF stays two-sided in WS2's last step: EF's 'could not be translated' refusal is matched by its words until the EF family gets its side (1)", + }; + + [Fact] + public void The_corpus_reads_of_a_message_are_found() => + Sources().Sum(source => Sites(source.Text).Count()).ShouldBeGreaterThanOrEqualTo(30, + "the corpus read a message's words at 33 places when this rule was written (WS2's three-sided differential), all but the one EF site a failure's condition"); + + [Fact] + public void No_message_text_decides_a_row_but_a_failure() => + Sources().Where(source => !Kept.ContainsKey(source.Path)).SelectMany(source => Problems(source.Path, source.Text)).ShouldBeEmpty(); + + [Fact] + public void Each_kept_site_still_reads_a_message_outside_a_failure() => + Kept.Keys.Where(path => !Problems(path, File.ReadAllText(Path.Combine(Repository.Root, path))).Any()).ShouldBeEmpty(); + + [Fact] + public void A_message_read_to_excuse_a_row_is_found() + { + const string source = + """ + class P + { + string? Check(Check c, Exception x, Exception? y, string[] names) + { + if (x.Message.Contains("maximum allowed depth")) { c.Note(NoteKeys.StjMaxdepth); return null; } + if (!x.Message.Contains("Refused")) return $"does not name the fault: {x.Message}"; + if (x.InnerException is { } i && i.Message.Contains("secret")) return "inner quotes the input"; + if (!x.Message.StartsWith("V") || x.Message.EndsWith(".")) return "not named: " + x.Message; + if (names.Any(n => !x.Message.Contains(n))) { r.Outcome("harness-error", "not named"); return null; } + var named = x.Message.Contains("V"); + try { throw x; } catch (Exception e) when (e.Message.Contains("could not be translated")) { return null; } + var missing = names.Where(n => !x.Message.Contains(n)).ToList(); + return x.Message.Contains("V") ? null : $"not named: {x.Message}"; + // x.Message.Contains("in a comment") is no call + } + } + """; + Problems("P.cs", source).ShouldBe( + [ + "P.cs:5: x.Message.Contains(\"maximum allowed depth\") decides a row by a message's words outside a failure", + "P.cs:10: x.Message.Contains(\"V\") decides a row by a message's words outside a failure", + "P.cs:11: e.Message.Contains(\"could not be translated\") decides a row by a message's words outside a failure", + "P.cs:12: x.Message.Contains(n) decides a row by a message's words outside a failure", + ]); + } + + private static List<(string Path, string Text)> Sources() => + [ + .. Directory.EnumerateFiles(Corpus, "*.cs", SearchOption.AllDirectories) + .Order(StringComparer.Ordinal) + .Select(path => (Path.GetRelativePath(Repository.Root, path).Replace('\\', '/'), File.ReadAllText(path))) + ]; + + /// <summary>Every <c>X.Message.Contains(...)</c> and its kin in a source.</summary> + private static IEnumerable<InvocationExpressionSyntax> Sites(string text) => + CSharpSyntaxTree.ParseText(text).GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>() + .Where(call => call.Expression is MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: var read, + Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: "Message" }, + } && Reads.Contains(read)); + + private static IEnumerable<string> Problems(string path, string text) + { + foreach (var call in Sites(text)) + if (!InAFailuresCondition(call)) + yield return $"{path}:{call.GetLocation().GetLineSpan().StartLinePosition.Line + 1}: {call} decides a row by a message's words outside a failure"; + } + + /// <summary> + /// The read stands in the condition of an <c>if</c> whose branch fails the row (returns a failure text, or raises a harness error), or + /// of a <c>?:</c> between <c>null</c> and a failure text. A lambda on the way up is part of that condition. + /// </summary> + private static bool InAFailuresCondition(SyntaxNode read) + { + for (var node = read; node.Parent is { } parent; node = parent) + { + switch (parent) + { + case IfStatementSyntax branch when branch.Condition == node: + return Fails(branch.Statement); + case ConditionalExpressionSyntax choice when choice.Condition == node: + return (IsNull(choice.WhenTrue) && IsFailure(choice.WhenFalse)) || (IsNull(choice.WhenFalse) && IsFailure(choice.WhenTrue)); + case StatementSyntax: + case CatchFilterClauseSyntax: + return false; + } + } + + return false; + } + + /// <summary>A branch that fails the row: it returns a failure text, or raises a harness error, first.</summary> + private static bool Fails(StatementSyntax statement) => statement switch + { + ReturnStatementSyntax { Expression: { } returned } => IsFailure(returned), + BlockSyntax { Statements: [var first, ..] } => Fails(first), + ExpressionStatementSyntax { Expression: InvocationExpressionSyntax raise } => RaisesAHarnessError(raise), + _ => false, + }; + + private static bool RaisesAHarnessError(InvocationExpressionSyntax call) => + call.Expression is MemberAccessExpressionSyntax { Name.Identifier.ValueText: "Outcome" } + && call.ArgumentList.Arguments is [{ Expression: LiteralExpressionSyntax { Token.ValueText: "harness-error" } }, ..]; + + /// <summary>A failure text: a string, an interpolated one, a concatenation with one, or a <c>?:</c> one of whose branches is one.</summary> + private static bool IsFailure(ExpressionSyntax expression) => expression switch + { + LiteralExpressionSyntax literal => literal.IsKind(SyntaxKind.StringLiteralExpression), + InterpolatedStringExpressionSyntax => true, + BinaryExpressionSyntax add when add.IsKind(SyntaxKind.AddExpression) => IsFailure(add.Left) || IsFailure(add.Right), + ConditionalExpressionSyntax choice => IsFailure(choice.WhenTrue) || IsFailure(choice.WhenFalse), + ParenthesizedExpressionSyntax inner => IsFailure(inner.Expression), + _ => false, + }; + + private static bool IsNull(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.NullLiteralExpression); +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ProbeNoteKeyTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeNoteKeyTests.cs new file mode 100644 index 00000000..8876ae13 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeNoteKeyTests.cs @@ -0,0 +1,94 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every note a probe makes names a key of the catalogue, and every skip names its finding as written +/// (decision 103): in the probe corpus, the first argument of a <c>Note</c> call is a member of +/// <c>NoteKeys</c>, and the first argument of a two-argument <c>Skip</c> (the harness's, not LINQ's) is a +/// string literal. +/// </summary> +/// <remarks> +/// The compiler already refuses a note key outside the catalogue, since <c>NoteKeys</c> has a private +/// constructor; this holds the shape for the sources the replay overlays, before any build, and for +/// <c>Skip</c>, whose finding id stays a string. It reads syntax, so a call in a comment or a string does +/// not count, and the positive control and the floor keep it from passing because the walker stopped +/// finding anything. +/// </remarks> +public sealed class ProbeNoteKeyTests +{ + private static string Corpus => Path.Combine(Repository.Root, "tools", "probe", "corpus"); + + [Fact] + public void The_corpus_notes_are_found() => + Calls(Sources()).Count(c => c.Method == "Note").ShouldBeGreaterThanOrEqualTo(100, + "the corpus made 136 notes when this rule was written, 127 once the http family's outcome notes became checks' citations (WS2 step 4), " + + "and 119 at WS2's close, where every note is a pass key or framework-refuses"); + + [Fact] + public void Every_note_and_skip_in_the_corpus_names_its_key_as_written() => + Sources().SelectMany(source => Problems(source.Path, source.Text)).ShouldBeEmpty(); + + [Fact] + public void A_key_or_a_finding_built_from_text_is_found() + { + const string source = + """ + class P + { + void Check(Row r, string x) + { + r.Note("msg:" + x, "detail"); + r.Note(NoteKeys.Compared, "fine"); + r.Skip("F" + x, "why"); + r.Skip("F7", "fine"); + var rest = new[] { 1, 2 }.Skip(x.Length); + // r.Note("in a comment", x); + } + } + """; + Problems("P.cs", source).ShouldBe( + [ + "P.cs:5: Note(\"msg:\" + x, ...) does not name a key of NoteKeys", + "P.cs:7: Skip(\"F\" + x, ...) does not name its finding as a string literal", + ]); + } + + private static List<(string Path, string Text)> Sources() => + [ + .. Directory.EnumerateFiles(Corpus, "*.cs", SearchOption.AllDirectories) + .Order(StringComparer.Ordinal) + .Select(path => (Path.GetRelativePath(Repository.Root, path).Replace('\\', '/'), File.ReadAllText(path))) + ]; + + private static IEnumerable<(string Method, InvocationExpressionSyntax Call)> Calls(IEnumerable<(string Path, string Text)> sources) => + sources.SelectMany(source => Calls(source.Text)); + + private static IEnumerable<(string Method, InvocationExpressionSyntax Call)> Calls(string text) => + CSharpSyntaxTree.ParseText(text).GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>() + .Select(call => (Method: call.Expression switch + { + MemberAccessExpressionSyntax member => member.Name.Identifier.ValueText, + IdentifierNameSyntax name => name.Identifier.ValueText, + _ => "", + }, Call: call)) + .Where(c => c.Method is "Note" or "Skip"); + + private static IEnumerable<string> Problems(string path, string text) + { + foreach (var (method, call) in Calls(text)) + { + var arguments = call.ArgumentList.Arguments; + var line = call.GetLocation().GetLineSpan().StartLinePosition.Line + 1; + if (method == "Note" && arguments.Count > 0 && !IsCatalogueKey(arguments[0].Expression)) + yield return $"{path}:{line}: Note({arguments[0]}, ...) does not name a key of NoteKeys"; + if (method == "Skip" && arguments.Count == 2 && !arguments[0].Expression.IsKind(SyntaxKind.StringLiteralExpression)) + yield return $"{path}:{line}: Skip({arguments[0]}, ...) does not name its finding as a string literal"; + } + } + + private static bool IsCatalogueKey(ExpressionSyntax expression) => + expression is MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.ValueText: "NoteKeys" } }; +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ProbeRowsTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeRowsTests.cs new file mode 100644 index 00000000..4d424afd --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ProbeRowsTests.cs @@ -0,0 +1,103 @@ +using System.IO.Compression; +using System.Text.Json.Nodes; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// The probe corpus keeps its rows per probe (<c>tools/probe/corpus/rows/<probe>.json.gz</c>): every registered +/// probe has a rows file named for it, each file names its probe's claim, a claim of <c>claims.json</c>, and no two rows of +/// one file have the same axis map. +/// </summary> +/// <remarks> +/// A shared rows file tied two probes of a claim together, so rows appended for one changed the other's identity, and +/// appending instead of regenerating left duplicates (C02 42 of 88, C20 90 of 351), each a row run again for nothing. A +/// rows file named for no registered probe is a leftover no replay runs. The positive control holds each rule to finding +/// its defect, and the floor keeps the walker from passing by finding no file. +/// </remarks> +public sealed class ProbeRowsTests +{ + private static string Corpus => Path.Combine(Repository.Root, "tools", "probe", "corpus"); + + [Fact] + public void The_rows_files_are_found() => + RowsFiles().Count.ShouldBeGreaterThanOrEqualTo(43, "the corpus had a rows file for each of its 43 probes when this rule was written"); + + [Fact] + public void Every_probe_has_rows_of_its_own_each_axis_map_once() => + Problems(RowsFiles(), Claims(), Registry()).ShouldBeEmpty(); + + [Fact] + public void A_duplicate_row_a_wrong_claim_a_leftover_file_and_a_probe_without_rows_are_found() + { + static Dictionary<string, string> Row(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + var files = new Dictionary<string, (string? Claim, List<Dictionary<string, string>> Rows)>(StringComparer.Ordinal) + { + ["P"] = ("C1", [Row("1", "1"), Row("1", "2"), new(StringComparer.Ordinal) { ["y"] = "1", ["x"] = "1" }]), + ["Q"] = ("C9", [Row("1", "1")]), + ["R"] = ("C1", [Row("1", "1")]), + ["Old"] = ("C1", [Row("2", "2")]), + ["N"] = (null, [Row("2", "2")]), + }; + var registry = new Dictionary<string, string>(StringComparer.Ordinal) { ["P"] = "C1", ["Q"] = "C9", ["R"] = "C2", ["N"] = "C1", ["S"] = "C1" }; + Problems(files, new HashSet<string>(StringComparer.Ordinal) { "C1", "C2" }, registry).ShouldBe( + [ + "rows/N.json.gz names no claim", + "rows/Old.json.gz names no registered probe", + "rows/P.json.gz: row 2 has the axis map of row 0", + "rows/Q.json.gz names claim C9, which claims.json does not have", + "rows/R.json.gz names claim C1, but its probe R is a probe of C2", + "S is a registered probe without a rows file (rows/S.json.gz)", + ]); + } + + /// <summary>Every rule's problems, in ordinal order: files by name, then the probes without a file.</summary> + private static List<string> Problems(IReadOnlyDictionary<string, (string? Claim, List<Dictionary<string, string>> Rows)> files, + IReadOnlySet<string> claims, IReadOnlyDictionary<string, string> registry) + { + var problems = new List<string>(); + foreach (var (probe, (claim, rows)) in files.OrderBy(f => f.Key, StringComparer.Ordinal)) + { + var name = $"rows/{probe}.json.gz"; + if (claim is null) problems.Add($"{name} names no claim"); + else if (!claims.Contains(claim)) problems.Add($"{name} names claim {claim}, which claims.json does not have"); + if (!registry.TryGetValue(probe, out var registered)) problems.Add($"{name} names no registered probe"); + else if (claim is not null && claims.Contains(claim) && registered != claim) problems.Add($"{name} names claim {claim}, but its probe {probe} is a probe of {registered}"); + var first = new Dictionary<string, int>(StringComparer.Ordinal); + for (var i = 0; i < rows.Count; i++) + { + var key = string.Join("\n", rows[i].OrderBy(kv => kv.Key, StringComparer.Ordinal).Select(kv => kv.Key + "=" + kv.Value)); + if (first.TryGetValue(key, out var earlier)) problems.Add($"{name}: row {i} has the axis map of row {earlier}"); + else first[key] = i; + } + } + foreach (var probe in registry.Keys.Where(p => !files.ContainsKey(p)).Order(StringComparer.Ordinal)) + problems.Add($"{probe} is a registered probe without a rows file (rows/{probe}.json.gz)"); + return problems; + } + + /// <summary>Every rows file of the corpus by its probe's name, with the claim it names and its rows' axis maps.</summary> + private static Dictionary<string, (string? Claim, List<Dictionary<string, string>> Rows)> RowsFiles() + { + var files = new Dictionary<string, (string?, List<Dictionary<string, string>>)>(StringComparer.Ordinal); + foreach (var path in Directory.EnumerateFiles(Path.Combine(Corpus, "rows")).Order(StringComparer.Ordinal)) + { + var name = Path.GetFileName(path); + name.ShouldEndWith(".json.gz", Case.Sensitive, $"rows/{name}: the corpus keeps its rows gzipped, one file per probe"); + using var gz = new GZipStream(File.OpenRead(path), CompressionMode.Decompress); + var document = JsonNode.Parse(gz)!; + var claim = document["claim"] is JsonValue c && c.TryGetValue<string>(out var s) ? s : null; + var rows = document["rows"]!.AsArray() + .Select(r => r!["row"]!.AsObject().ToDictionary(kv => kv.Key, kv => kv.Value!.GetValue<string>(), StringComparer.Ordinal)) + .ToList(); + files[name[..^".json.gz".Length]] = (claim, rows); + } + return files; + } + + private static HashSet<string> Claims() => + [.. JsonNode.Parse(File.ReadAllText(Path.Combine(Corpus, "claims.json")))!.AsArray().Select(c => c!["id"]!.GetValue<string>())]; + + private static Dictionary<string, string> Registry() => + JsonNode.Parse(File.ReadAllText(Path.Combine(Corpus, "registry.json")))!["probes"]!.AsObject() + .ToDictionary(kv => kv.Key, kv => kv.Value!["claim"]!.GetValue<string>(), StringComparer.Ordinal); +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ReleaseWiringTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ReleaseWiringTests.cs index fd0d0428..28441a1f 100644 --- a/test/CodoMetis.TypeKit.Conventions.Tests/ReleaseWiringTests.cs +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ReleaseWiringTests.cs @@ -25,6 +25,9 @@ public sealed partial class ReleaseWiringTests private const string SmokeTestProject = "tools/ConsumerSmokeTest/ConsumerSmokeTest.csproj"; private const string SmokeTest = "dotnet tools/ConsumerSmokeTest/bin/Release/net10.0/consumer-smoke-test.dll"; + /// <summary>The prober, a tool project run as its built dll (design/adr/0092), which the workflows run with no model (design/adr/0096).</summary> + private const string Prober = "dotnet tools/Prober/bin/Release/net10.0/prober.dll"; + private static string ReleaseWorkflow => Workflow(ReleaseWorkflowFileName); private static string BuildWorkflow => Workflow("dotnet.yml"); @@ -156,6 +159,216 @@ public void Every_job_that_runs_the_smoke_test_builds_it_first() } } + /// <summary> + /// Measured on PR #22's run, 2026-10-16: on a pull request that changed no package, the pack job took four + /// minutes and the consumer smoke test four more after a test job of five to eight, a thirteen-minute run. So on + /// a pull request the build workflow packs and smoke-tests only when the diff against the base branch touches a + /// path that can change a package or a trimmed consumer, as its <c>changes</c> job decides, and on a push always. + /// A job that loses the condition runs on every pull request again, which fails nothing but the maintainer's + /// time; a path the step no longer lists lets a change to a package merge unpacked and untested, silently. Both + /// are held here, by structure: the job-level keys and the case labels of the deciding step. + /// </summary> + [Fact] + public void The_build_workflow_packs_a_pull_request_only_when_the_diff_can_change_a_package() + { + var changes = Job(BuildWorkflow, "changes"); + var output = Regex.Match(changes, @"(?m)^ product:\s*\$\{\{ steps\.(?<id>[A-Za-z0-9_-]+)\.outputs\.product \}\}\s*$"); + + output.Success.ShouldBeTrue("dotnet.yml:changes has no `product` output taken from a step's output, so the pack and consumer jobs have nothing to read."); + + var decide = Steps(changes).SingleOrDefault(step => Regex.IsMatch(step, $@"(?m)^\s+id:\s*{Regex.Escape(output.Groups["id"].Value)}\s*$")); + + decide.ShouldNotBeNull($"dotnet.yml:changes has no step with id {output.Groups["id"].Value}, which its `product` output names."); + decide.ShouldMatch(@"(?m)^\s+if \[ ""\$GITHUB_EVENT_NAME"" != pull_request \]; then\s*$", + "dotnet.yml:changes does not answer true for every event but a pull request, so a push or the dispatch would skip the pack."); + decide.ShouldMatch(@"(?m)^\s+echo ""product=true"" >> ""\$GITHUB_OUTPUT""\s*$", "dotnet.yml:changes never writes product=true to the step's output."); + decide.ShouldMatch(@"(?m)^\s+git diff --name-only ""origin/\$GITHUB_BASE_REF\.\.\.HEAD"" > \S+\s*$", + "dotnet.yml:changes does not diff the pull request against its base branch (origin/$GITHUB_BASE_REF...HEAD) as a command of its own, whose failure fails the job."); + decide.ShouldMatch(@"(?m)^\s+echo ""product=\$product"" >> ""\$GITHUB_OUTPUT""\s*$", "dotnet.yml:changes never writes the diff's answer to the step's output."); + + foreach (var pattern in ProductPaths) + decide.ShouldMatch(CaseLabel(pattern), + $"dotnet.yml:changes no longer lists {pattern} as a case label of the deciding step, so a pull request changing it alone would merge unpacked and untested."); + + foreach (var name in new[] { "pack", "consumer" }) + { + var job = Job(BuildWorkflow, name); + + job.ShouldMatch(JobKey("needs", @"\[\s*test,\s*changes\s*\]"), $"dotnet.yml:{name} does not need [ test, changes ], so it cannot read the changes job's output."); + job.ShouldMatch(JobKey("if", Regex.Escape("needs.changes.outputs.product == 'true'")), + $"dotnet.yml:{name} is not conditioned on `needs.changes.outputs.product == 'true'`, so it runs on every pull request again."); + } + } + + /// <summary> + /// The release packs and smoke-tests always: the artifacts it publishes are the artifacts it tested, whatever + /// the tag's diff. Neither its verify job nor the pack or smoke test step carries a condition. + /// </summary> + [Fact] + public void The_release_packs_and_smoke_tests_unconditionally() + { + var job = Job(ReleaseWorkflow, "verify"); + var steps = Steps(job).ToList(); + + job.ShouldNotMatch(JobKey("if", ".*"), "release.yml:verify carries a job-level condition; the release packs and smoke-tests every tag."); + + foreach (var (what, step) in new[] { ("pack", steps.Single(candidate => candidate.Contains("dotnet pack", StringComparison.Ordinal))), ("smoke test", steps.Single(candidate => SmokeTestStep().IsMatch(candidate))) }) + step.ShouldNotMatch(@"(?m)^\s+if:", $"release.yml:verify's {what} step carries a condition; the release packs and smoke-tests every tag."); + } + + /// <summary> + /// The paths a pull request must touch for its diff to change a package or a trimmed consumer, each a case label + /// of the changes job's deciding step: the shipping projects, the build property files at any depth, any project + /// file, the SDK pin, the package versions, the workflows and the smoke test itself. + /// </summary> + private static readonly string[] ProductPaths = + ["src/", "Directory.*.props", "Directory.*.targets", "*.csproj", "global.json", "Directory.Packages.props", ".github/workflows/", "tools/ConsumerSmokeTest/"]; + + /// <summary>A bash case label naming the path pattern, alone or among alternatives, a trailing <c>*</c> allowed for a directory.</summary> + private static string CaseLabel(string pattern) => $@"(?m)^\s+(?:[^\s)|]+\|)*{Regex.Escape(pattern)}\*?(?:\|[^\s)|]+)*\)"; + + /// <summary> + /// CI replays the probe corpus with no model (design/adr/0096), split by family into legs that run at once + /// and a job that merges their states (design/adr/0101): on a push every probe, with coverage, and the merged + /// state saved for the branch's pull requests; on a pull request the base branch's state restored and only + /// the probes it stales replayed, without a footprint. The replay had once been documented as running in CI + /// while no workflow called it, and a dropped step fails nothing: a regression the corpus would catch merges + /// instead. So a dropped leg, a family in no leg, a replay that cannot fail its job, a merge that cannot fail + /// its job, a state saved from a pull request, or a job with no time limit each fail here. + /// </summary> + [Fact] + public void The_build_workflow_replays_every_probe_by_family_and_merges_the_legs() + { + var (name, job, steps) = ProberJob("dotnet.yml", BuildWorkflow); + var jobName = name[(name.IndexOf(':') + 1)..]; + + var listed = Regex.Match(job, @"(?m)^\s+family:\s*\[(?<families>[^\]]*)\]").Groups["families"].Value + .Split(',').Select(f => f.Trim()).Where(f => f.Length > 0).ToList(); + listed.ShouldBe(ReplayFamilies(), ignoreOrder: true, + $"{name}'s matrix does not list exactly the families of the axes file's replay configuration: a family in no leg is never replayed (`prober merge` then refuses), a leg of no family runs nothing."); + job.ShouldMatch(JobKey("timeout-minutes", @"\d+"), $"{name} has no timeout-minutes, so a hung replay runs to the six-hour default."); + job.ShouldMatch(@"PROBE_BRANCH=\$\{\{ github\.event_name == 'pull_request' && github\.base_ref \|\| github\.ref_name \}\}", + $"{name} does not take the base branch's state on a pull request (PROBE_BRANCH), so the base's footprints would not decide what the branch stales."); + + var replay = steps.FindIndex(step => ProberStep("replay").IsMatch(step)); + var build = steps.FindIndex(step => ProberBuild().IsMatch(step)); + replay.ShouldBeGreaterThanOrEqualTo(0, $"{name} does not run `prober replay`."); + build.ShouldBeInRange(0, replay - 1, $"{name} runs the prober without building it in Release first: build the solution or tools/Prober with -c Release."); + var lines = steps[replay].Split('\n').Where(line => ProberStep("replay").IsMatch(line)).Select(line => line.Trim()).ToList(); + lines.Count.ShouldBe(2, $"{name} does not run the replay one way on a push and another on a pull request."); + lines.Single(line => line.Contains(" --all", StringComparison.Ordinal)) + .ShouldMatch($@"^{Regex.Escape(Prober)} replay --all --branch ""\$PROBE_BRANCH"" --family ""\$\{{\{{ matrix\.family \}}\}}""$", + $"{name}'s push replay is not `replay --all` of the branch's family leg: every probe, with a footprint, for the state the branch saves."); + lines.Single(line => !line.Contains(" --all", StringComparison.Ordinal)) + .ShouldMatch($@"^{Regex.Escape(Prober)} replay --branch ""\$PROBE_BRANCH"" --family ""\$\{{\{{ matrix\.family \}}\}}"" --no-footprint$", + $"{name}'s pull request replay is not the family leg's stale probes without a footprint: coverage would be spent on a state nobody keeps."); + steps[replay].ShouldNotMatch(@"\|\|\s*true", $"{name} lets the replay fail without failing the job."); + steps[replay].ShouldNotMatch(@"(?m)^\s+continue-on-error:", $"{name} lets the replay fail without failing the job."); + + var restore = steps.FindIndex(step => step.Contains("uses: actions/cache/restore@", StringComparison.Ordinal)); + restore.ShouldBeInRange(0, replay - 1, $"{name} does not restore the base branch's probe state before the replay, so a pull request replays every probe."); + steps[restore].ShouldMatch(@"(?m)^\s+if:\s*github\.event_name == 'pull_request'\s*$", $"{name} restores a saved state on a push too, where decision 0101 keeps the replay from an empty state."); + steps[restore].ShouldMatch(@"restore-keys:\s*probe-state-\$\{\{ github\.base_ref \}\}-", $"{name} does not restore the base branch's newest saved state (restore-keys by base_ref)."); + + var merge = Jobs(BuildWorkflow).Single(candidate => Steps(candidate.Value).Any(step => ProberStep("merge").IsMatch(step))); + var mergeName = $"dotnet.yml:{merge.Key}"; + merge.Value.ShouldMatch(JobKey("needs", Regex.Escape(jobName)), $"{mergeName} does not follow the {jobName} legs."); + merge.Value.ShouldMatch(JobKey("if", @"always\(\)"), $"{mergeName} is skipped when a leg fails, so a failing run reports no status."); + merge.Value.ShouldMatch(JobKey("timeout-minutes", @"\d+"), $"{mergeName} has no timeout-minutes."); + merge.Value.ShouldMatch(StateDirectory, $"{mergeName} does not set PROBE_STATE_DIR, so the prober writes into a developer's state directory."); + var mergeSteps = Steps(merge.Value).ToList(); + var merging = mergeSteps.FindIndex(step => ProberStep("merge").IsMatch(step)); + var download = mergeSteps.FindIndex(step => step.Contains("uses: actions/download-artifact@", StringComparison.Ordinal)); + var status = mergeSteps.FindIndex(step => ProberStep("status").IsMatch(step)); + var save = mergeSteps.FindIndex(step => step.Contains("uses: actions/cache/save@", StringComparison.Ordinal)); + download.ShouldBeInRange(0, merging - 1, $"{mergeName} does not download the legs' states before merging them."); + mergeSteps[merging].ShouldMatch($@"(?m)^\s+run:\s*{Regex.Escape(Prober)} merge --branch ""\$PROBE_BRANCH"" --from \S", $"{mergeName} does not merge the legs' states with `prober merge --branch --from` alone on its run line."); + mergeSteps[merging].ShouldNotMatch(@"\|\|\s*true", $"{mergeName} lets the merge fail without failing the job, so a probe in no leg or a failed leg would pass."); + mergeSteps[merging].ShouldNotMatch(@"(?m)^\s+continue-on-error:", $"{mergeName} lets the merge fail without failing the job, so a probe in no leg or a failed leg would pass."); + status.ShouldBeGreaterThan(merging, $"{mergeName} does not run `prober status` after the merge, so the run reports no coverage."); + save.ShouldBeGreaterThan(merging, $"{mergeName} does not save the merged state for the branch's pull requests."); + mergeSteps[save].ShouldMatch(@"(?m)^\s+if:\s*github\.event_name == 'push'\s*$", $"{mergeName} saves the state from a pull request too, whose replay was partial and without a footprint."); + mergeSteps[save].ShouldMatch(@"key:\s*probe-state-\$\{\{ github\.ref_name \}\}-", $"{mergeName} does not save the state under the branch's key, which its pull requests restore by prefix."); + } + + /// <summary>A key of the job itself, at the job's indentation (four spaces), never a step's key of the same name.</summary> + private static string JobKey(string key, string value) => $@"(?m)^ {key}:\s*{value}\s*$"; + + /// <summary>The families of the axes file's replay configuration, which the matrix lists in full.</summary> + private static List<string> ReplayFamilies() + { + var text = File.ReadAllText(Path.Combine(Repository.Root, ".claude", "probe-axes.md")); + var start = text.IndexOf("```json probe-axes", StringComparison.Ordinal); + start.ShouldBeGreaterThanOrEqualTo(0, "No `json probe-axes` block in .claude/probe-axes.md."); + start = text.IndexOf('\n', start) + 1; + var end = text.IndexOf("\n```", start, StringComparison.Ordinal); + using var block = System.Text.Json.JsonDocument.Parse(text[start..end]); + var families = block.RootElement.GetProperty("replay").GetProperty("families").EnumerateObject().Select(property => property.Name).ToList(); + families.ShouldNotBeEmpty("The replay configuration of .claude/probe-axes.md names no families."); + return families; + } + + /// <summary> + /// The release replays the corpus and runs the gate before it packs (design/adr/0096), against the previous + /// release: on a tag push, <c>git describe</c> alone names the tag being released, and the gate would compare + /// the commit with itself and find nothing touched. The gate binds from 2.2.0 (design/adr/0097), so a refusal + /// fails the job: the step's exit code is the gate's own, either because nothing follows the gate's + /// <c>||</c> or because the step ends with <c>exit "$code"</c>, and it has no <c>continue-on-error</c>. + /// </summary> + [Fact] + public void The_release_replays_every_probe_and_runs_the_gate_before_packing() + { + var job = Job(ReleaseWorkflow, "verify"); + var steps = Steps(job).ToList(); + + var replay = StrictReplay("release.yml:verify", steps); + var gate = steps.FindIndex(step => ProberStep("gate").IsMatch(step)); + var pack = steps.FindIndex(step => step.Contains("dotnet pack", StringComparison.Ordinal)); + + gate.ShouldBeGreaterThan(replay, "release.yml:verify does not run `prober gate` after `prober replay --all`; the gate reads the replay's evidence."); + pack.ShouldBeGreaterThan(gate, "release.yml:verify packs before the probe gate has run."); + steps[gate].ShouldMatch(@"\bgate --since ""?\$", "The gate runs without --since, so on a tag push it compares the release with itself."); + steps[gate].ShouldNotMatch(@"(?m)^\s+continue-on-error:", "release.yml:verify lets the probe gate fail without failing the job; it binds from 2.2.0."); + (!Regex.IsMatch(steps[gate], $@"{Regex.Escape(Prober)} gate\b[^\n]*\|\|") || Regex.IsMatch(steps[gate], @"\n\s*exit ""?\$code""?\s*$")) + .ShouldBeTrue("release.yml:verify catches the probe gate's exit code and does not end the step with it, so a refusal does not fail the release; it binds from 2.2.0."); + job.ShouldMatch(StateDirectory, "release.yml:verify does not set PROBE_STATE_DIR, so the prober writes into a developer's state directory."); + } + + /// <summary>The one job of a workflow whose steps run `prober replay`: its name, its text and its steps.</summary> + private static (string Name, string Job, List<string> Steps) ProberJob(string workflow, string text) + { + var jobs = Jobs(text).Where(job => Steps(job.Value).Any(step => ProberStep("replay").IsMatch(step))).ToList(); + + jobs.Count.ShouldBe(1, $"{workflow} has no job that runs `prober replay`, or more than one."); + jobs[0].Value.ShouldMatch(StateDirectory, $"{workflow}:{jobs[0].Key} does not set PROBE_STATE_DIR, so the prober writes into a developer's state directory."); + + return ($"{workflow}:{jobs[0].Key}", jobs[0].Value, Steps(jobs[0].Value).ToList()); + } + + /// <summary> + /// The replay of every probe, built first, and able to fail the job: its exit code is the step's own, with no + /// <c>|| true</c> and no <c>continue-on-error</c>. + /// </summary> + private static int StrictReplay(string name, List<string> steps) + { + var replay = steps.FindIndex(step => ProberStep("replay").IsMatch(step)); + var build = steps.FindIndex(step => ProberBuild().IsMatch(step)); + + replay.ShouldBeGreaterThanOrEqualTo(0, $"{name} does not run `prober replay`."); + steps[replay].ShouldMatch($@"(?m)^\s+run:\s*{Regex.Escape(Prober)} replay --all\s*$", + $"{name} replays the probes some other way than `{Prober} replay --all` alone on its run line, so a failing probe may not fail the job."); + steps[replay].ShouldNotMatch(@"(?m)^\s+continue-on-error:", $"{name} lets the replay fail without failing the job."); + build.ShouldBeInRange(0, replay - 1, $"{name} runs the prober without building it in Release first: build the solution or tools/Prober with -c Release."); + + return replay; + } + + /// <summary>PROBE_STATE_DIR set for the job: as an env key, or written to <c>$GITHUB_ENV</c> (the runner context is not available to a job's env).</summary> + private const string StateDirectory = @"(?m)^\s+(PROBE_STATE_DIR:\s*\S|echo ""PROBE_STATE_DIR=\S+"" >> ""\$GITHUB_ENV"")"; + + /// <summary>A step that runs a prober command; <see cref="Steps"/> has already dropped the comment lines.</summary> + private static Regex ProberStep(string command) => new($@"{Regex.Escape(Prober)} {command}\b"); + /// <summary> /// Measured 2026-09-27: without the filter, the analyzer package's SBOM lists 13 Roslyn /// components while its nuspec declares no dependency at all. Measured 2026-09-28: the SDK adds @@ -265,6 +478,9 @@ private static IEnumerable<string> Steps(string job) => [GeneratedRegex(@"(?m)^\s+run:\s*dotnet build (CodoMetis\.TypeKit\.slnx|tools/ConsumerSmokeTest)\s+-c Release\s*$")] private static partial Regex SmokeTestBuild(); + [GeneratedRegex(@"(?m)^\s+run:\s*dotnet build (CodoMetis\.TypeKit\.slnx|tools/Prober)\s+-c Release\s*$")] + private static partial Regex ProberBuild(); + [GeneratedRegex(@"(?m)^\s+run:.*\bapt-get install\b.*\bclang\b.*\bzlib1g-dev\b")] private static partial Regex AotPrerequisites(); diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/SessionStartHookTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/SessionStartHookTests.cs new file mode 100644 index 00000000..73ea4959 --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/SessionStartHookTests.cs @@ -0,0 +1,76 @@ +using System.Text.Json; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every tool an agent runs is built when its session starts, in the checkout it works in, with a dotnet that has the +/// SDK global.json pins, and an agent is told when none could (<c>design/adr/0105-every-tool-is-built-in-the-session-s-checkout.md</c>). +/// </summary> +/// <remarks> +/// Measured over the sessions of 2026-10-15 and 2026-10-16: 70 of about 100 session starts failed their builds, because +/// Homebrew's dotnet (10.0.401) was first on the hook's PATH, and every session in a worktree found a tool missing, +/// because the hook built in <c>$CLAUDE_PROJECT_DIR</c>, which in such a session is the main checkout. +/// </remarks> +public sealed class SessionStartHookTests +{ + private const string Filter = "tools/AgentTools.slnf"; + + private static IReadOnlyList<string> Commands() + { + using var settings = JsonDocument.Parse(File.ReadAllText(Path.Combine(Repository.Root, ".claude", "settings.json"))); + return + [ + .. settings.RootElement.GetProperty("hooks").GetProperty("SessionStart").EnumerateArray() + .SelectMany(group => group.GetProperty("hooks").EnumerateArray()) + .Select(hook => hook.GetProperty("command").GetString() ?? "") + ]; + } + + /// <summary>Every tool project but the guard, discovered from tools/ so a new tool needs no edit here.</summary> + public static TheoryData<string> Tools => + [ + .. Directory.GetDirectories(Path.Combine(Repository.Root, "tools")) + .Select(Path.GetFileName).OfType<string>() + .Where(name => File.Exists(Path.Combine(Repository.Root, "tools", name, $"{name}.csproj")) && name != "BashGuard") + .Order(StringComparer.Ordinal) + ]; + + [Fact] + public void The_tools_are_found() => Tools.Select(row => row.Data).ShouldContain("Prober"); + + [Theory, MemberData(nameof(Tools))] + public void Every_tool_is_in_the_session_start_filter(string tool) + { + using var filter = JsonDocument.Parse(File.ReadAllText(Path.Combine(Repository.Root, Filter))); + filter.RootElement.GetProperty("solution").GetProperty("projects").EnumerateArray().Select(p => p.GetString()) + .ShouldContain($"tools/{tool}/{tool}.csproj", $"{Filter} lists every tool an agent runs, so a session starts with it built"); + } + + [Fact] + public void The_tools_are_built_in_the_sessions_checkout() + { + var command = Commands().SingleOrDefault(c => c.Contains(Filter, StringComparison.Ordinal)); + command.ShouldNotBeNull($"a SessionStart hook builds {Filter}"); + command.ShouldNotContain("$CLAUDE_PROJECT_DIR/" + Filter, Case.Sensitive, + "relative to the session's directory: in a worktree session $CLAUDE_PROJECT_DIR is the main checkout"); + } + + [Fact] + public void The_guard_is_built_where_the_hook_runs_it() => + Commands().ShouldContain(c => c.StartsWith("dotnet build \"$CLAUDE_PROJECT_DIR/tools/BashGuard/", StringComparison.Ordinal), + "the PreToolUse hook runs the guard from $CLAUDE_PROJECT_DIR"); + + [Fact] + public void Every_build_falls_back_to_the_installers_dotnet_and_says_when_none_builds() + { + foreach (var command in Commands()) + { + var steps = command.Split(" || "); + steps.Length.ShouldBe(3, command); + steps[0].ShouldStartWith("dotnet build ", Case.Sensitive, command); + steps[1].ShouldBe($"PATH=\"/usr/local/share/dotnet:$PATH\" {steps[0].Replace(" >/dev/null 2>&1", "", StringComparison.Ordinal)}", + "the same build with the SDK installer's dotnet first, as the verdict tool falls back"); + steps[2].ShouldStartWith("echo \"Session start: ", Case.Sensitive, "said on stdout, which the agent reads, with the hook exiting 0"); + } + } +} diff --git a/test/CodoMetis.TypeKit.Conventions.Tests/ShippingProjectAnalyzerTests.cs b/test/CodoMetis.TypeKit.Conventions.Tests/ShippingProjectAnalyzerTests.cs new file mode 100644 index 00000000..9bff305a --- /dev/null +++ b/test/CodoMetis.TypeKit.Conventions.Tests/ShippingProjectAnalyzerTests.cs @@ -0,0 +1,62 @@ +using System.Diagnostics; +using System.Text.Json; + +namespace CodoMetis.TypeKit.Conventions.Tests; + +/// <summary> +/// Every shipping project runs the CMTK analyzers on its own code, as a consumer's project does. +/// </summary> +/// <remarks> +/// <para> +/// A project reference does not bring a project's analyzers along (dotnet/roslyn#79272), so the +/// shipping projects built without them, and a breach in the library's own code compiled. Measured: the +/// Testing and AspNetCore packages forwarded <c>T1</c> and <c>T2</c> into <c>Fault<T1, T2></c>'s +/// <c>[Distinct]</c> parameters without carrying it, 38 CMTK0010 errors found only by adding the analyzer +/// by hand (1a9d7f2). +/// </para> +/// <para> +/// Read from MSBuild's evaluation of each project, which is where <c>src/Directory.Build.targets</c> +/// adds the reference, and not from the project files. The analyzer projects are left out by what they +/// are (<c>IsRoslynComponent</c>), not by name, so a new package is held to this with no edit here. +/// </para> +/// </remarks> +public sealed class ShippingProjectAnalyzerTests +{ + private const string AnalyzerProject = "CodoMetis.TypeKit.Analyzers"; + + public static TheoryData<string> Projects => [.. Repository.ShippingProjects()]; + + [Theory] + [MemberData(nameof(Projects))] + public async Task A_shipping_project_runs_the_CMTK_analyzers_on_its_own_code(string project) + { + var path = Path.Combine(Repository.Root, "src", project, $"{project}.csproj"); + + using var process = Process.Start(new ProcessStartInfo("dotnet", ["msbuild", path, "-getItem:ProjectReference", "-getProperty:IsRoslynComponent", "-nodeReuse:false"]) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + })!; + + var output = process.StandardOutput.ReadToEndAsync(TestContext.Current.CancellationToken); + var errors = process.StandardError.ReadToEndAsync(TestContext.Current.CancellationToken); + await process.WaitForExitAsync(TestContext.Current.CancellationToken); + + process.ExitCode.ShouldBe(0, $"dotnet msbuild {project} failed:{Environment.NewLine}{await output}{await errors}"); + + using var evaluation = JsonDocument.Parse(await output); + if (evaluation.RootElement.GetProperty("Properties").GetProperty("IsRoslynComponent").GetString() == "true") return; + + var references = evaluation.RootElement.GetProperty("Items").TryGetProperty("ProjectReference", out var items) + ? [.. items.EnumerateArray()] + : new List<JsonElement>(); + + var analyzed = references.Any(reference => reference.GetProperty("Filename").GetString() == AnalyzerProject + && reference.TryGetProperty("OutputItemType", out var type) && type.GetString() == "Analyzer"); + + analyzed.ShouldBeTrue( + $"{project} does not load {AnalyzerProject} as an analyzer, so a CMTK breach in its own code compiles. " + + "src/Directory.Build.targets adds the reference to every project that has none; a project that references " + + "the analyzer itself sets OutputItemType=\"Analyzer\" on that reference."); + } +} diff --git a/test/CodoMetis.TypeKit.DocsQuery.Tests/FactCitationTests.cs b/test/CodoMetis.TypeKit.DocsQuery.Tests/FactCitationTests.cs new file mode 100644 index 00000000..6ca113d4 --- /dev/null +++ b/test/CodoMetis.TypeKit.DocsQuery.Tests/FactCitationTests.cs @@ -0,0 +1,216 @@ +using DocsQuery; + +namespace CodoMetis.TypeKit.DocsQuery.Tests; + +/// <summary> +/// A fact is executable or it explains only (design/adr/0107): <c>check</c> fails a fact the probe corpus cites to +/// prune or excuse a row (a refused entry of the axes file, a not-promised entry or an obligation's declaration in +/// claims.json, a <c>Cite.Fact</c> or <c>Baseline.Fact</c> of the harness) when it has no test, and a <c>fixture:</c> +/// whose file is missing or not beside the test. +/// </summary> +public sealed class FactCitationTests : IDisposable +{ + private readonly TempRepo _repo = Store(new TempRepo()); + + public void Dispose() => _repo.Dispose(); + + private static string Fact(int number, string test, string fixture = "") => + $""" + --- + verified: 2026-10-15 + pins: + - sdk 10.0.4xx + source: the framework's own output, measured + turned-on: decision 1 + test: {test} + {fixture} + --- + + # KB-{number:D3}. A fact + """; + + /// <summary>KB-001 tested, with a fixture; KB-002 prose only.</summary> + private static TempRepo Store(TempRepo repo) => repo + .With("global.json", """{ "sdk": { "version": "10.0.402", "rollForward": "latestPatch" } }""") + .With("design/adr/0001-first.md", "# 0001. First\n\nDate: 2026-10-01\nStatus: accepted\n") + .With("design/knowledge/KB-001-tested.md", Fact(1, "test/CodoMetis.TypeKit.Facts/KB001.cs", "fixture: test/CodoMetis.TypeKit.Facts/KB001.fixture.json")) + .With("test/CodoMetis.TypeKit.Facts/KB001.cs", "public sealed class KB001 { }\n") + .With("test/CodoMetis.TypeKit.Facts/KB001.fixture.json", """{ "fact": "KB-001", "answers": [] }""") + .With("design/knowledge/KB-002-prose.md", Fact(2, "none, a sentence of the framework's documentation")); + + /// <summary>The axes file, its refused entries citing <paramref name="fact"/> in their reason.</summary> + private TempRepo Axes(string fact) => _repo.With(".claude/probe-axes.md", + $$""" + # Axes + + ```json probe-axes + { + "axes": {}, + "refused": { + "json.ReadCommentHandling": { + "Allow": "System.Text.Json refuses it on the options object" + }, + "mvc.MaxValidationDepth": { + "0": "MVC refuses it, fact:{{fact}}" + } + } + } + ``` + """); + + /// <summary>claims.json with a not-promised entry whose source is <paramref name="source"/> and an obligation declaration citing <paramref name="cite"/>.</summary> + private TempRepo Claims(string source, string cite) => _repo.With("tools/probe/corpus/claims.json", + $$""" + [ + { + "id": "C01", + "source": "README.md#a", + "obligations": [ + { + "id": "binds", + "sentence": "It binds.", + "inapplicable": [ + { "axes": { "x": ["1"] }, "cite": "not-promised:np" }, + { "axes": { "x": ["2"] }, "cite": "{{cite}}" } + ] + } + ], + "not-promised": [ + { "id": "np", "source": "{{source}}", "sentence": "So.", "axes": { "x": ["1"] } } + ] + } + ] + """); + + private List<string> Problems() => + [.. _repo.Queries().Check().Rows.Select(row => $"{row.Fields[0].Value} {row.Fields[1].Value} {row.Fields[2].Value}")]; + + [Fact] + public void Citations_of_a_tested_fact_check_clean_and_are_counted_by_kind() + { + Axes("KB-001"); + Claims("design/knowledge/KB-001-tested.md#kb-001-a-fact", "fact:KB-001"); + _repo.With("tools/probe/corpus/harness/Http.cs", "class Http { void M() { c.Inapplicable(Cite.Fact(\"KB-001\"), \"why\"); } }\n") + .With("tools/probe/corpus/probes/P.cs", "class P { Baseline b = Baseline.Fact( \"KB-001\" ); }\n"); + + var answer = _repo.Queries().Check(); + + answer.Rows.ShouldBeEmpty(answer.Text()); + answer.Summary.ShouldContain("5 fact citations (1 refused, 1 not-promised, 1 obligation, 2 harness)"); + var scan = FactCitations.Find(_repo.Root); + scan.Read.ShouldBe(new Dictionary<string, int> { ["refused"] = 2, ["not-promised"] = 1, ["obligation"] = 2, ["harness"] = 2 }); + scan.Citations.Select(c => $"{c.Kind} {c.At} {c.Fact} {c.Baseline}").ShouldBe( + [ + "refused .claude/probe-axes.md:11 KB-001 False", + "obligation tools/probe/corpus/claims.json:11 KB-001 False", + "not-promised tools/probe/corpus/claims.json:16 KB-001 False", + "harness tools/probe/corpus/harness/Http.cs:1 KB-001 False", + "harness tools/probe/corpus/probes/P.cs:1 KB-001 True", + ]); + } + + [Fact] + public void A_refused_entry_citing_a_fact_without_a_test_fails_where_it_is_written() + { + Axes("KB-002"); + + Problems().ShouldBe([".claude/probe-axes.md:11 fact-not-executable-cited KB-002"]); + _repo.Queries().Check().Rows.Single().Fields.Single(f => f.Name == "detail").Value + .ShouldBe("the refused entry mvc.MaxValidationDepth=0 cites KB-002, and its test: is 'none, a sentence of the framework's documentation': " + + "a fact without its test explains only, never prunes or excuses a row; write its test in test/CodoMetis.TypeKit.Facts/, or cite a docs sentence"); + } + + [Fact] + public void A_not_promised_entry_whose_source_is_a_fact_without_a_test_fails() + { + Claims("design/knowledge/KB-002-prose.md#kb-002-a-fact", "fact:KB-001"); + + Problems().ShouldBe(["tools/probe/corpus/claims.json:16 fact-not-executable-cited KB-002"]); + } + + [Fact] + public void An_obligation_s_declaration_citing_a_fact_without_a_test_fails() + { + Claims("README.md#a", "fact:KB-002"); + + Problems().ShouldBe(["tools/probe/corpus/claims.json:11 fact-not-executable-cited KB-002"]); + } + + [Fact] + public void A_harness_citation_of_a_fact_without_a_test_fails_and_one_in_a_comment_does_not_count() + { + _repo.With("tools/probe/corpus/harness/Http.cs", + """ + class Http + { + // Cite.Fact("KB-002") is only mentioned here + /* and Cite.Fact("KB-002") here */ + string url = "http://example"; object o = Cite.Fact("KB-002"); + void M() => c.Inapplicable(Cite.Fact("KB-002"), "why"); + } + """); + + Problems().ShouldBe(["tools/probe/corpus/harness/Http.cs:5 fact-not-executable-cited KB-002", "tools/probe/corpus/harness/Http.cs:6 fact-not-executable-cited KB-002"]); + } + + [Fact] + public void A_cited_fact_whose_test_file_is_missing_fails_twice() + { + _repo.With("design/knowledge/KB-003-test-gone.md", Fact(3, "test/CodoMetis.TypeKit.Facts/KB003.cs")) + .With("tools/probe/corpus/probes/P.cs", "class P { object o = Cite.Fact(\"KB-003\"); }\n"); + + Problems().ShouldBe( + [ + "design/knowledge/KB-003-test-gone.md:7 fact-untested test/CodoMetis.TypeKit.Facts/KB003.cs", + "tools/probe/corpus/probes/P.cs:1 fact-not-executable-cited KB-003", + ]); + } + + [Fact] + public void A_cited_fact_that_does_not_exist_fails() + { + _repo.With("tools/probe/corpus/probes/P.cs", "class P { object o = Cite.Fact(\"KB-099\"); }\n"); + + Problems().ShouldBe(["tools/probe/corpus/probes/P.cs:1 fact-not-executable-cited KB-099"]); + } + + /// <summary>A baseline is the fact's measured answer, so it takes the fixture as well as the test.</summary> + [Fact] + public void A_baseline_of_a_tested_fact_without_a_fixture_fails() + { + _repo.With("design/knowledge/KB-004-no-fixture.md", Fact(4, "test/CodoMetis.TypeKit.Facts/KB004.cs")) + .With("test/CodoMetis.TypeKit.Facts/KB004.cs", "public sealed class KB004 { }\n") + .With("tools/probe/corpus/harness/Http.cs", "class Http { Baseline a = Baseline.Fact(\"KB-001\"), b = Baseline.Fact(\"KB-004\"); object c = Cite.Fact(\"KB-004\"); }\n"); + + Problems().ShouldBe(["tools/probe/corpus/harness/Http.cs:1 fact-not-executable-cited KB-004"]); + } + + [Fact] + public void A_fixture_must_exist_beside_the_test_of_a_tested_fact() + { + _repo.With("design/knowledge/KB-005-fixture-gone.md", Fact(5, "test/CodoMetis.TypeKit.Facts/KB005.cs", "fixture: test/CodoMetis.TypeKit.Facts/KB005.fixture.json")) + .With("test/CodoMetis.TypeKit.Facts/KB005.cs", "public sealed class KB005 { }\n") + .With("design/knowledge/KB-006-fixture-elsewhere.md", Fact(6, "test/CodoMetis.TypeKit.Facts/KB006.cs", "fixture: test/fixtures/KB006.json")) + .With("test/CodoMetis.TypeKit.Facts/KB006.cs", "public sealed class KB006 { }\n") + .With("test/fixtures/KB006.json", "{}") + .With("design/knowledge/KB-007-fixture-untested.md", Fact(7, "none, prose", "fixture: test/CodoMetis.TypeKit.Facts/KB007.fixture.json")) + .With("test/CodoMetis.TypeKit.Facts/KB007.fixture.json", "{}") + .With("test/CodoMetis.TypeKit.Facts/KB008.fixture.json", "{}"); + + Problems().ShouldBe( + [ + "design/knowledge/KB-005-fixture-gone.md:8 fact-fixture-missing test/CodoMetis.TypeKit.Facts/KB005.fixture.json", + "design/knowledge/KB-006-fixture-elsewhere.md:8 fact-fixture test/fixtures/KB006.json", + "design/knowledge/KB-007-fixture-untested.md:8 fact-fixture test/CodoMetis.TypeKit.Facts/KB007.fixture.json", + "test/CodoMetis.TypeKit.Facts/KB008.fixture.json:1 fact-orphan-fixture test/CodoMetis.TypeKit.Facts/KB008.fixture.json", + ]); + } + + [Fact] + public void Comments_are_blanked_and_strings_kept() + { + var source = "a // b\nc /* d\ne */ f \"// g\" @\"h \"\" // i\" \"\"\"raw // j\"\"\" '\"' k"; + + FactCitations.WithoutComments(source).ShouldBe("a \nc \n f \"// g\" @\"h \"\" // i\" \"\"\"raw // j\"\"\" '\"' k"); + } +} diff --git a/test/CodoMetis.TypeKit.DocsQuery.Tests/FactTests.cs b/test/CodoMetis.TypeKit.DocsQuery.Tests/FactTests.cs index ed76c919..360c25ed 100644 --- a/test/CodoMetis.TypeKit.DocsQuery.Tests/FactTests.cs +++ b/test/CodoMetis.TypeKit.DocsQuery.Tests/FactTests.cs @@ -57,6 +57,19 @@ public void A_clean_store_checks_clean() answer.Summary.ShouldEndWith("and 1 fact"); } + /// <summary>A note after the test's path, such as where it runs, leaves the path the test: the fact is tested and the test named.</summary> + [Fact] + public void A_test_named_with_a_note_after_its_path_is_the_fact_s_test() + { + _repo.With("design/knowledge/KB-002-noted.md", Fact(2, " - sdk 10.0.4xx", "test/CodoMetis.TypeKit.Facts/KB002.cs (runs only with A_SERVER set, until a CI service)")) + .With("test/CodoMetis.TypeKit.Facts/KB002.cs", "public sealed class KB002 { }\n"); + + var answer = _repo.Queries().Check(); + + answer.Rows.ShouldBeEmpty(answer.Text()); + answer.Summary.ShouldEndWith("and 2 facts"); + } + [Fact] public void A_fact_answers_to_its_id_in_any_spelling_and_is_cited() { diff --git a/test/CodoMetis.TypeKit.DocsQuery.Tests/RepositoryTests.cs b/test/CodoMetis.TypeKit.DocsQuery.Tests/RepositoryTests.cs index 87616598..fd61d078 100644 --- a/test/CodoMetis.TypeKit.DocsQuery.Tests/RepositoryTests.cs +++ b/test/CodoMetis.TypeKit.DocsQuery.Tests/RepositoryTests.cs @@ -58,6 +58,25 @@ public void The_repository_checks_clean() answer.Rows.ShouldBeEmpty(answer.Text()); } + /// <summary> + /// Floors on the places the citation rule reads and on the citations it finds there (design/adr/0107), so that a + /// scan that found nothing cannot let the check above pass. On 2026-10-17: 3 refused entries, 41 not-promised + /// entries, 11 obligation declarations and 87 harness and probe sources were read, and KB-001 and KB-027 were + /// cited by not-promised entries, KB-037 twice by the harness. + /// </summary> + [Theory] + [InlineData(FactCitations.Refused, 3, 0)] + [InlineData(FactCitations.NotPromised, 30, 2)] + [InlineData(FactCitations.Obligation, 10, 0)] + [InlineData(FactCitations.Harness, 40, 2)] + public void The_corpus_s_citations_of_facts_are_found(string kind, int read, int cited) + { + var scan = FactCitations.Find(RepositoryRoot.Path); + + scan.Read[kind].ShouldBeGreaterThanOrEqualTo(read); + scan.Of(kind).ShouldBeGreaterThanOrEqualTo(cited); + } + [Fact] public void Every_record_is_listed_with_its_status_and_date() => Queries.List(["kind=adr"]).Rows.ShouldAllBe(row => row.Fields.Single(f => f.Name == "status").Value.Length > 0 && row.Fields.Single(f => f.Name == "date").Value.Length == 10); diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ColumnFacetVariantTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ColumnFacetVariantTests.cs new file mode 100644 index 00000000..92d12ffd --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ColumnFacetVariantTests.cs @@ -0,0 +1,149 @@ +using CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests; + +/// <summary> +/// A precision or a column type configured on a value-object property reaches the column, on PostgreSQL, +/// for every kind of the catalogue over <c>decimal</c>, <c>string</c> and <c>DateTime</c>, configured on the +/// property and through <c>ConfigureConventions</c>, each against the same configuration of the wrapped type. +/// </summary> +/// <remarks> +/// The oracle is the README: "A facet you configure on the property, such as a maximum length or a column +/// type, is kept", and "Configure the value object instead: <c>configurationBuilder.Properties<Amount>().HavePrecision(18, 2)</c>". +/// The column type annotation says what was configured, not what the mapping made of it, so the test reads +/// the mapping's store type and the create script. +/// </remarks> +public sealed class ColumnFacetVariantTests +{ + /// <summary>Each wrapped type's facet and the column type PostgreSQL is to get for it.</summary> + private static readonly Dictionary<Type, (Facet Facet, string StoreType)> Facets = new() + { + [typeof(decimal)] = (Facet.Precision18And2, "numeric(18,2)"), + [typeof(string)] = (Facet.Citext, "citext"), + [typeof(DateTime)] = (Facet.TimestampWithoutTimeZone, "timestamp without time zone"), + }; + + private static IEnumerable<WrappedKind> Kinds() => + WrappedKinds.Keys.Concat(WrappedKinds.Queries).Distinct().Where(kind => Facets.ContainsKey(kind.Wrapped)); + + public static TheoryData<string, FacetSite> Cases + { + get + { + var cases = new TheoryData<string, FacetSite>(); + foreach (var kind in Kinds()) + foreach (var site in Enum.GetValues<FacetSite>()) + cases.Add(kind.Name, site); + return cases; + } + } + + [Theory] + [MemberData(nameof(Cases))] + public void A_configured_facet_reaches_the_column_as_for_the_wrapped_type(string kind, FacetSite site) + { + var wrapped = WrappedKinds.Named(kind); + var (facet, storeType) = Facets[wrapped.Wrapped]; + + var plain = Column(wrapped.Wrapped, facet, site, typeKit: false); + var valueObject = Column(wrapped.ValueObject, facet, site, typeKit: true); + + plain.ShouldBe((storeType, storeType), $"the wrapped type's own column, {facet} {site}: the oracle"); + valueObject.ShouldBe(plain, $"{kind} with {facet} configured {site}"); + } + + /// <summary>A guard against a vacuous run: the catalogue's kinds over each wrapped type, string as a struct and as record classes.</summary> + [Fact] + public void The_catalogue_has_kinds_over_each_wrapped_type() => + Kinds().Select(kind => kind.Name).ShouldBe(["string", "decimal", "DateTime", "string, record class", "string, record class, own CompareTo"], ignoreOrder: true); + + /// <summary>The mapping's store type, and the column's type in the create script.</summary> + private static (string Mapping, string Script) Column(Type value, Facet facet, FacetSite site, bool typeKit) + { + using var db = FacetDb.Create(value, facet, site, typeKit); + var property = db.GetService<IDesignTimeModel>().Model.FindEntityType(typeof(Faceted<>).MakeGenericType(value))!.FindProperty("Value")!; + + var script = db.Database.GenerateCreateScript(); + var line = script.Split('\n').Single(l => l.TrimStart().StartsWith("\"Value\" ", StringComparison.Ordinal)).Trim(); + var scripted = line["\"Value\" ".Length..].Split(" NOT NULL")[0].TrimEnd(','); + + return (property.GetRelationalTypeMapping().StoreType, scripted); + } +} + +public enum Facet +{ + Precision18And2, + Citext, + TimestampWithoutTimeZone, +} + +/// <summary>Where the application configures a facet: on the entity's property, or for every property of the type.</summary> +public enum FacetSite +{ + Property, + Conventions, +} + +public class Faceted<TValue> +{ + public int Id { get; set; } + + public TValue Value { get; set; } = default!; +} + +/// <summary>A model of one property over <see cref="Value"/> with one facet, cached per type, facet and site.</summary> +public sealed class FacetDb(DbContextOptions<FacetDb> options, Type value, Facet facet, FacetSite site) : DbContext(options) +{ + public Type Value => value; + + public Facet Facet => facet; + + public FacetSite Site => site; + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + if (site is not FacetSite.Conventions) return; + + var properties = configurationBuilder.Properties(value); + _ = facet switch + { + Facet.Precision18And2 => properties.HavePrecision(18, 2), + Facet.Citext => properties.HaveColumnType("citext"), + Facet.TimestampWithoutTimeZone => properties.HaveColumnType("timestamp without time zone"), + _ => throw new ArgumentOutOfRangeException(nameof(facet), facet, null), + }; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + var property = modelBuilder.Entity(typeof(Faceted<>).MakeGenericType(value)).Property("Value"); + if (site is not FacetSite.Property) return; + + _ = facet switch + { + Facet.Precision18And2 => property.HasPrecision(18, 2), + Facet.Citext => property.HasColumnType("citext"), + Facet.TimestampWithoutTimeZone => property.HasColumnType("timestamp without time zone"), + _ => throw new ArgumentOutOfRangeException(nameof(facet), facet, null), + }; + } + + /// <summary>On Npgsql without a server, as <see cref="TestDb.NpgsqlWithoutServer"/>: the model and the script never open the connection.</summary> + public static FacetDb Create(Type value, Facet facet, FacetSite site, bool typeKit) + { + var builder = new DbContextOptionsBuilder<FacetDb>().ReplaceService<IModelCacheKeyFactory, FacetModelCacheKey>().UseNpgsql("Host=localhost;Database=none"); + if (typeKit) builder = builder.UseTypeKit(); + + return new FacetDb(builder.Options, value, facet, site); + } +} + +public sealed class FacetModelCacheKey : IModelCacheKeyFactory +{ + public object Create(DbContext context, bool designTime) => + context is FacetDb db ? (db.GetType(), db.Value, db.Facet, db.Site, designTime) : (context.GetType(), designTime); +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/HandWrittenOrderingTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/HandWrittenOrderingTests.cs index d5cad7ae..0d662ff2 100644 --- a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/HandWrittenOrderingTests.cs +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/HandWrittenOrderingTests.cs @@ -42,6 +42,26 @@ public sealed class HandWrittenOrderingTests : IDisposable // Boxed, the key still orders by CompareTo in memory, 3, 2, 1; EF dropped the cast and answered 1, 2, 3. ["OrderBy a key cast to object"] = db => db.Ranked.OrderBy(r => (object)r.Rank).ToList(), ["ThenBy a key cast to object"] = db => db.Ranked.OrderBy(r => r.Id % 1).ThenBy(r => (object)r.Rank).ToList(), + + // The database's own GREATEST and LEAST (SQLite's max and min) order by the column: the first answered + // 2, 2, 3 where memory's order answers 1, 1, 2 (measured on PostgreSQL 17 before the refusal). + ["Greatest"] = db => db.Ranked.Select(r => EF.Functions.Greatest(r.Rank, Two)).ToList(), + ["Least"] = db => db.Ranked.Where(r => EF.Functions.Least(r.Optional, Two) == r.Optional).ToList(), + ["Least of a record class"] = db => db.Ranked.Select(r => EF.Functions.Least(r.Label, ProbeReversedLabel.From("2"))).ToList(), + }; + + /// <summary> + /// Npgsql's row-value comparisons, <c>(a, b) > (x, y)</c>, which compare each element by the column's order: + /// <c>GreaterThan((Rank, Id), (2, 0))</c> kept every row from 2 up where memory's order keeps only 1. Translated + /// without a server, since the refusal comes before any SQL. + /// </summary> + private static readonly Dictionary<string, Func<RankedDb, IQueryable>> RefusedOnPostgreSql = new() + { + ["GreaterThan"] = db => db.Ranked.Where(r => EF.Functions.GreaterThan(ValueTuple.Create(r.Rank, r.Id), ValueTuple.Create(Two, 0))), + ["LessThan"] = db => db.Ranked.Where(r => EF.Functions.LessThan(ValueTuple.Create(r.Id, r.Optional), ValueTuple.Create(0, (ProbeDescending?)Two))), + ["GreaterThanOrEqual"] = db => db.Ranked.Where(r => EF.Functions.GreaterThanOrEqual(new ValueTuple<int, ProbeDescending>(r.Id, r.Rank), new ValueTuple<int, ProbeDescending>(0, Two))), + ["LessThanOrEqual"] = db => db.Ranked.Where(r => EF.Functions.LessThanOrEqual(ValueTuple.Create(r.Label, r.Id), ValueTuple.Create(ProbeReversedLabel.From("2"), 0))), + ["Greatest"] = db => db.Ranked.Select(r => EF.Functions.Greatest(r.Rank, Two)), }; private readonly SqliteConnection _connection = new("Data Source=:memory:"); @@ -57,10 +77,42 @@ public void A_query_that_orders_a_value_object_by_its_own_CompareTo_is_refused(s var refusal = Should.Throw<InvalidOperationException>(() => Refused[query](db)).Message; refusal.ShouldContain("its ordering is its own CompareTo, which the database cannot run"); - refusal.ShouldContain(query == "a record class" ? "ProbeReversedLabel" : "ProbeDescending"); + refusal.ShouldContain(query is "a record class" or "Least of a record class" ? "ProbeReversedLabel" : "ProbeDescending"); refusal.ShouldContain("Compare or order by .Value explicitly"); } + public static TheoryData<string> RefusedPostgreSqlQueries => [.. RefusedOnPostgreSql.Keys]; + + [Theory] + [MemberData(nameof(RefusedPostgreSqlQueries))] + public void A_row_value_comparison_by_its_own_CompareTo_is_refused_on_PostgreSQL(string query) + { + using var db = new RankedDb(new DbContextOptionsBuilder<RankedDb>().UseNpgsql("Host=localhost;Database=none").UseTypeKit().Options); + + var refusal = Should.Throw<InvalidOperationException>(() => RefusedOnPostgreSql[query](db).ToQueryString()).Message; + + refusal.ShouldContain("its ordering is its own CompareTo, which the database cannot run"); + refusal.ShouldContain(query == "LessThanOrEqual" ? "ProbeReversedLabel is compared" : "ProbeDescending is compared"); + refusal.ShouldContain($"by {query} in a query"); + } + + /// <summary>The same functions over <c>.Value</c>, or over a value object ordered by what it wraps, still translate.</summary> + [Fact] + public void Greatest_Least_and_row_values_by_Value_still_translate() + { + using (var db = Seeded()) + { + db.Ranked.Select(r => EF.Functions.Greatest(r.Rank.Value, 2)).ToList().Order().ShouldBe([2, 2, 3]); + db.Ranked.Select(r => EF.Functions.Least(r.Count, ProbeCount.From(2))).ToList().Order().ShouldBe([ProbeCount.From(1), ProbeCount.From(2), ProbeCount.From(2)]); + } + + using var postgreSql = new RankedDb(new DbContextOptionsBuilder<RankedDb>().UseNpgsql("Host=localhost;Database=none").UseTypeKit().Options); + postgreSql.Ranked.Where(r => EF.Functions.GreaterThan(ValueTuple.Create(r.Rank.Value, r.Id), ValueTuple.Create(2, 0))).ToQueryString() + .ShouldContain("(r.\"Rank\", r.\"Id\") > ("); + postgreSql.Ranked.Where(r => EF.Functions.LessThan(ValueTuple.Create(r.Count, r.Id), ValueTuple.Create(ProbeCount.From(2), 0))).ToQueryString() + .ShouldContain("(r.\"Count\", r.\"Id\") < ("); + } + /// <summary> /// <c>.Value</c> says the wrapped value's order is meant, equality is no ordering, and a value object /// ordered by what it wraps orders in SQL as in memory. @@ -100,10 +152,11 @@ public void A_comparison_EF_evaluates_in_memory_is_not_refused() /// keeps 1. The filter is refused when the model is built, named or not, naming the entity type. /// </summary> [Theory] - [InlineData(typeof(FilteredByRankDb), "the query filter of FilteredRank")] - [InlineData(typeof(NamedFilterByRankDb), "the query filter \"AboveTwo\" of FilteredRank")] - [InlineData(typeof(FilteredByChildRankDb), "the query filter of RankParent")] - public void A_query_filter_that_orders_by_its_own_CompareTo_is_refused(Type context, string filter) + [InlineData(typeof(FilteredByRankDb), "compared with >", "the query filter of FilteredRank")] + [InlineData(typeof(NamedFilterByRankDb), "compared with >", "the query filter \"AboveTwo\" of FilteredRank")] + [InlineData(typeof(FilteredByChildRankDb), "compared with >", "the query filter of RankParent")] + [InlineData(typeof(FilteredByGreatestDb), "compared by Greatest", "the query filter of FilteredRank")] + public void A_query_filter_that_orders_by_its_own_CompareTo_is_refused(Type context, string how, string filter) { _connection.Open(); using var db = (DbContext)Activator.CreateInstance(context, new DbContextOptionsBuilder().UseSqlite(_connection).UseTypeKit().Options)!; @@ -114,11 +167,23 @@ public void A_query_filter_that_orders_by_its_own_CompareTo_is_refused(Type cont return db.Set<FilteredRank>().Select(r => r.Rank.Value).ToList(); }).Message; - refusal.ShouldContain($"ProbeDescending is compared with > in {filter}"); + refusal.ShouldContain($"ProbeDescending is {how} in {filter}"); refusal.ShouldContain("its ordering is its own CompareTo, which the database cannot run"); refusal.ShouldContain("Compare or order by .Value explicitly"); } + /// <summary>A row-value comparison in a filter is refused when the model is built, on the provider that has one.</summary> + [Fact] + public void A_query_filter_with_a_row_value_comparison_by_its_own_CompareTo_is_refused_on_PostgreSQL() + { + using var db = new FilteredByRowValueDb(new DbContextOptionsBuilder().UseNpgsql("Host=localhost;Database=none").UseTypeKit().Options); + + var refusal = Should.Throw<InvalidOperationException>(() => db.Model).Message; + + refusal.ShouldContain("ProbeDescending is compared in a row value by GreaterThan in the query filter of FilteredRank"); + refusal.ShouldContain("its ordering is its own CompareTo, which the database cannot run"); + } + /// <summary> /// A filter by <c>.Value</c> is the column's order, and a comparison that reads nothing of the entity /// is evaluated in memory, where <c>CompareTo</c> runs: <c>Floor > From(2)</c> is true for the @@ -229,6 +294,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } +/// <summary>A query filter that keeps the rows whose rank is the greater of it and 2, by the database's GREATEST.</summary> +public sealed class FilteredByGreatestDb(DbContextOptions options) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.Entity<FilteredRank>().HasQueryFilter(r => EF.Functions.Greatest(r.Rank, ProbeDescending.From(2)) == r.Rank); +} + +/// <summary>A query filter by Npgsql's row-value comparison.</summary> +public sealed class FilteredByRowValueDb(DbContextOptions options) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.Entity<FilteredRank>().HasQueryFilter(r => EF.Functions.GreaterThan(ValueTuple.Create(r.Rank, r.Id), ValueTuple.Create(ProbeDescending.From(2), 0))); +} + /// <summary>Filters that are allowed: by <c>.Value</c>, and a comparison of the context's own value, evaluated in memory.</summary> public sealed class FilteredByValueDb(DbContextOptions<FilteredByValueDb> options) : DbContext(options) { diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationTests.cs index 915f0188..1bc61232 100644 --- a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationTests.cs +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationTests.cs @@ -264,6 +264,28 @@ public void A_key_that_is_also_a_foreign_key_is_never_generated(string provider, valueObjectKey.ValueGenerated.ShouldBe(ValueGenerated.Never, $"{dependentByValueObject.Name} on {provider}"); } + /// <summary> + /// A compiled model (<c>dotnet ef dbcontext optimize</c>) can name only a value generator factory's type, + /// never a delegate: the factory a Guid key generated on add gets is written out, and makes the provider's + /// version 7 Guids from it. No database is opened: Npgsql generates these at <c>Add</c>. + /// </summary> + [Fact] + public void In_a_compiled_model_a_Guid_key_generated_on_add_gets_the_provider_s_generator() + { + DbContextOptionsBuilder<CompiledGeneratedKeysDb> Options() => new DbContextOptionsBuilder<CompiledGeneratedKeysDb>().UseNpgsql("Host=localhost;Database=none").UseTypeKit(); + + using var design = new CompiledGeneratedKeysDb(Options().Options); + var compiled = DesignTime.CompiledModel(design); + using var db = new CompiledGeneratedKeysDb(Options().UseModel(compiled).Options); + + db.Model.ShouldBeSameAs(compiled); + compiled.FindEntityType(typeof(GeneratedByWrapsGuid))!.FindProperty("Id")!.GetValueGeneratorFactory().ShouldNotBeNull(); + GeneratedByWrapsGuid[] guids = [new(), new()]; + db.AddRange(guids); + + guids.Select(e => e.Id.Value.Version).ShouldBe([7, 7]); + } + public void Dispose() => _connection.Dispose(); private HierarchyKeysDb Create(string provider) => new(Options<HierarchyKeysDb>(provider)); @@ -557,6 +579,32 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } +/// <summary>Keys over a Guid and a string configured generated on add, each beside the plain type's.</summary> +public sealed class PostgreSqlGeneratedKeysDb(DbContextOptions<PostgreSqlGeneratedKeysDb> options) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity<GeneratedByGuid>().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity<GeneratedByWrapsGuid>().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity<GeneratedByString>().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity<GeneratedByWrapsString>().Property(e => e.Id).ValueGeneratedOnAdd(); + } +} + +/// <summary>The Guid half of <see cref="PostgreSqlGeneratedKeysDb"/>, for a compiled model.</summary> +public sealed class CompiledGeneratedKeysDb(DbContextOptions<CompiledGeneratedKeysDb> options) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity<GeneratedByWrapsGuid>().Property(e => e.Id).ValueGeneratedOnAdd(); +} + +public class GeneratedByGuid { public Guid Id { get; set; } } + +public class GeneratedByWrapsGuid { public Variants.WrapsGuid Id { get; set; } } + +public class GeneratedByString { public string Id { get; set; } = null!; } + +public class GeneratedByWrapsString { public Variants.WrapsString Id { get; set; } } + public class SharedKeyPrincipal { public ShipmentNo Id { get; set; } } public class SharedKeyDependent { public ShipmentNo Id { get; set; } } diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationVariantTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationVariantTests.cs new file mode 100644 index 00000000..df1fda2c --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/KeyGenerationVariantTests.cs @@ -0,0 +1,213 @@ +using System.Text.RegularExpressions; +using CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests; + +/// <summary> +/// Key generation across the variant catalogue of where a key sits (<see cref="KeyShapes"/>): every shape +/// keyed by every kind of <see cref="WrappedKinds.Keys"/>, on SQLite, Npgsql and SQL Server, each +/// against the same shape keyed by the wrapped type. +/// </summary> +/// <remarks> +/// The oracle is the README's "Keys" section, written as a differential: a value-object key is generated +/// as a key of the type it wraps is (whether, from what default, by which provider strategy), and on +/// SQLite the database fills in the same keys. Where it departs from parity, the case follows the README +/// (<see cref="Expected"/>): a key over <c>char</c> or <c>Guid</c> is the application's to assign unless +/// configured, and a TPC root's key is generated only from a sequence. A model the wrapped type cannot +/// have is no case. +/// </remarks> +public sealed partial class KeyGenerationVariantTests : IDisposable +{ + private readonly SqliteConnection _connection = new("Data Source=:memory:"); + + public static TheoryData<string, KeyShape, string> Cases + { + get + { + var cases = new TheoryData<string, KeyShape, string>(); + foreach (var (provider, shape, kind) in KeyShapes.All()) cases.Add(provider, shape, kind.Name); + return cases; + } + } + + [Theory] + [MemberData(nameof(Cases))] + public void A_value_object_key_is_generated_as_a_key_of_the_wrapped_type_is(string provider, KeyShape shape, string kind) + { + var wrapped = WrappedKinds.Named(kind); + _connection.Open(); + + var plain = Facts(provider, shape, wrapped.Wrapped, typeKit: false); + var valueObject = Facts(provider, shape, wrapped.ValueObject, typeKit: true); + + if (plain.Refusal is not null) + { + Assert.Skip($"the wrapped type's model is refused: {plain.Refusal}"); + } + + var keys = $"{shape} keyed by {kind} on {provider}: value object [{string.Join("; ", valueObject.Keys)}], wrapped type [{string.Join("; ", plain.Keys)}]"; + valueObject.Refusal.ShouldBeNull($"the value object's model is refused where the wrapped type's is not"); + var expected = Expected(shape, wrapped, plain); + valueObject.Keys.Select(key => key.Generated).ShouldBe(expected, keys); + if (expected.SequenceEqual(plain.Keys.Select(key => key.Generated))) + valueObject.Keys.ShouldBe(plain.Keys, keys); + + // The keys the database fills in; a key the client generates (a Guid, a string) differs on every run. + if (provider == "sqlite" && IsInteger(wrapped.Wrapped) && expected is [ValueGenerated.OnAdd] && plain.Keys is [{ Generated: ValueGenerated.OnAdd }]) + Inserted(shape, wrapped.ValueObject, typeKit: true).ShouldBe(Inserted(shape, wrapped.Wrapped, typeKit: false), $"the keys SQLite filled in for {shape} keyed by {kind}"); + } + + public static TheoryData<string, string> ConfiguredOnAddCases + { + get + { + var cases = new TheoryData<string, string>(); + foreach (var provider in KeyShapes.Providers) + foreach (var kind in WrappedKinds.Keys) + cases.Add(provider, kind.Name); + return cases; + } + } + + /// <summary> + /// A key configured <c>ValueGeneratedOnAdd()</c> is filled in at <c>Add</c> by the generator the provider + /// gives a key of the wrapped type: on PostgreSQL a version 7 Guid where the wrapped key gets one, on SQL + /// Server a Guid sequential in its order. The providers pick it by the property's CLR type, which gave a + /// value object's key EF's random Guid instead. No database is opened: the client generates these. + /// </summary> + [Theory] + [MemberData(nameof(ConfiguredOnAddCases))] + public void A_value_object_key_generated_on_add_gets_values_as_a_key_of_the_wrapped_type_does(string provider, string kind) + { + var wrapped = WrappedKinds.Named(kind); + _connection.Open(); + + var plain = Added(provider, wrapped.Wrapped, typeKit: false); + if (plain is null) Assert.Skip("the wrapped type's key is not generated at Add"); + + Added(provider, wrapped.ValueObject, typeKit: true).ShouldBe(AddedExpected(provider, wrapped, plain), $"{kind} configured OnAdd on {provider}"); + } + + /// <summary> + /// The README's departures at <c>Add</c>: "A default key over <c>ulong</c> throws at <c>Add</c> where the column + /// is a <c>decimal</c> (SQL Server, PostgreSQL)", and on SQL Server a key over <c>char</c> configured + /// <c>ValueGeneratedOnAdd()</c> gets "one character of a GUID string", since EF reaches the generator through the + /// column's <c>nvarchar(1)</c>, so sixteen of them collide. + /// </summary> + private static string? AddedExpected(string provider, WrappedKind kind, string plain) => (provider, kind.Name) switch + { + ("npgsql" or "sqlserver", "ulong") => null, + ("sqlserver", "char") => null, + _ => plain, + }; + + /// <summary>What the values generated at <c>Add</c> are, or null where <c>Add</c> generates none or refuses.</summary> + private string? Added(string provider, Type key, bool typeKit) + { + using var db = KeyShapeDb.Create(provider, KeyShape.ConfiguredOnAdd, key, typeKit, _connection); + var entities = Enumerable.Range(0, 16).Select(_ => Activator.CreateInstance(KeyShapes.Subject(KeyShape.ConfiguredOnAdd, key))!).ToArray(); + try + { + db.AddRange(entities); + } + catch (Exception exception) when (exception is InvalidOperationException or NotSupportedException or OverflowException) + { + return null; + } + + var keys = entities.Select(entity => db.Entry(entity).Property("Id")).ToArray(); + return keys.All(property => property.IsTemporary) ? "temporary" : GeneratedShape([.. keys.Select(property => WrappedKind.Text(property.CurrentValue))]); + } + + /// <summary> + /// What tells the generators apart: whether each value is a Guid (or a Guid's text), its version, and whether + /// the values ascend in SQL Server's order, as its sequential generator's do and random ones do not. + /// </summary> + private static string GeneratedShape(string[] values) + { + if (!values.All(value => System.Guid.TryParse(value, out _))) return $"not Guids: {string.Join(", ", values.Take(2))}"; + + var guids = values.Select(System.Guid.Parse).ToArray(); + var versions = string.Join(",", guids.Select(guid => guid.Version).Distinct()); + var ascending = guids.Zip(guids.Skip(1)).All(pair => new System.Data.SqlTypes.SqlGuid(pair.First).CompareTo(new System.Data.SqlTypes.SqlGuid(pair.Second)) < 0); + return $"version {versions}, {(ascending ? "sequential" : "unordered")} in SQL Server's order"; + } + + /// <summary>A guard against a vacuous run: the catalogue's size, and the kinds and shapes it crosses.</summary> + [Fact] + public void The_catalogue_crosses_every_shape_with_every_key_kind_on_three_providers() => + KeyShapes.All().Count().ShouldBe(Enum.GetValues<KeyShape>().Length * WrappedKinds.Keys.Length * 3); + + public void Dispose() => _connection.Dispose(); + + /// <summary> + /// The README's rule, per key: as a key of the wrapped type, with its two exceptions, a key over + /// <c>char</c> or <c>Guid</c> "is yours to assign" unless configured, and its rule for TPC: "the key is + /// generated only where the provider numbers the whole hierarchy from a sequence". EF leaves a plain key + /// declared generated under TPC where the provider has no sequence for its type (a <c>ulong</c>, a + /// <c>numeric(20,0)</c> on PostgreSQL), and nothing fills it in. + /// </summary> + private static ValueGenerated[] Expected(KeyShape shape, WrappedKind kind, Model plain) => + [ + .. plain.Keys.Select(key => + (kind.Wrapped == typeof(char) || kind.Wrapped == typeof(Guid)) && shape is not (KeyShape.ConfiguredOnAdd or KeyShape.ConfiguredNever) ? ValueGenerated.Never + : shape is KeyShape.TpcRoot or KeyShape.AbstractTpcRoot && key is { Generated: ValueGenerated.OnAdd, DefaultSql: null } ? ValueGenerated.Never + : key.Generated), + ]; + + private static bool IsInteger(Type type) => + type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(sbyte) || type == typeof(byte) + || type == typeof(uint) || type == typeof(ulong) || type == typeof(ushort); + + private Model Facts(string provider, KeyShape shape, Type key, bool typeKit) + { + using var db = KeyShapeDb.Create(provider, shape, key, typeKit, _connection); + try + { + return new Model([.. KeyShapes.KeyOf(db, shape, key).Select(Describe)], null); + } + catch (Exception exception) when (exception is InvalidOperationException or NotSupportedException or ArgumentException) + { + return new Model([], $"{exception.GetType().Name}: {exception.Message}"); + } + } + + private static Key Describe(IProperty property) => new( + property.ValueGenerated, + Normalized(property.GetDefaultValueSql()), + string.Join(", ", property.GetAnnotations().Where(annotation => annotation.Name.EndsWith("ValueGenerationStrategy", StringComparison.Ordinal)) + .Select(annotation => $"{annotation.Name}={annotation.Value}"))); + + /// <summary>A name EF derived from the closed entity type, the same for either key: <c>Root<int>Sequence</c> as <c>Root<K>Sequence</c>.</summary> + private static string? Normalized(string? sql) => sql is null ? null : TypeArguments().Replace(sql, "<K>"); + + /// <summary>Two entities of the shape added without a key, saved, and the keys SQLite gave them.</summary> + private string[] Inserted(KeyShape shape, Type key, bool typeKit) + { + using var db = KeyShapeDb.Create("sqlite", shape, key, typeKit, _connection); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + + Type[] types = shape switch + { + KeyShape.TphRoot or KeyShape.TptRoot => [typeof(Root<>).MakeGenericType(key), typeof(Leaf<>).MakeGenericType(key)], + KeyShape.AbstractTphRoot or KeyShape.AbstractTptRoot => [typeof(LeafA<>).MakeGenericType(key), typeof(LeafB<>).MakeGenericType(key)], + _ => [KeyShapes.Subject(shape, key), KeyShapes.Subject(shape, key)], + }; + var entities = types.Select(type => Activator.CreateInstance(type)!).ToArray(); + db.AddRange(entities); + db.SaveChanges(); + + return [.. entities.Select(entity => WrappedKind.Text(entity.GetType().GetProperty("Id")!.GetValue(entity)))]; + } + + [GeneratedRegex("<[^<>]*>")] + private static partial Regex TypeArguments(); + + private sealed record Model(Key[] Keys, string? Refusal); + + private sealed record Key(ValueGenerated Generated, string? DefaultSql, string Strategy); +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/PostgreSqlRoundTripTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/PostgreSqlRoundTripTests.cs index 01f26594..d65a26b5 100644 --- a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/PostgreSqlRoundTripTests.cs +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/PostgreSqlRoundTripTests.cs @@ -172,6 +172,31 @@ public async Task An_abstract_TPC_root_s_key_is_numbered_from_its_sequence() vehicles.Select(vehicle => vehicle.Id.Value).ShouldBe([1L, 2L, 3L], ignoreOrder: true); } + /// <summary> + /// A key over a Guid or a string configured <c>ValueGeneratedOnAdd()</c> gets a version 7 Guid from Npgsql, as + /// the plain key does, and is stored and read back as generated. Npgsql chooses the generator by the + /// property's CLR type, which gave the value object EF's random version 4 Guid. + /// </summary> + [Fact] + public async Task A_key_generated_on_add_gets_a_version_7_Guid_as_the_plain_key_does() + { + var cancellation = TestContext.Current.CancellationToken; + + await using (var db = new PostgreSqlGeneratedKeysDb(database.Options<PostgreSqlGeneratedKeysDb>("typekit_generated_keys"))) + { + await db.Database.EnsureCreatedAsync(cancellation); + db.AddRange(new GeneratedByGuid(), new GeneratedByGuid(), new GeneratedByWrapsGuid(), new GeneratedByWrapsGuid(), + new GeneratedByString(), new GeneratedByString(), new GeneratedByWrapsString(), new GeneratedByWrapsString()); + await db.SaveChangesAsync(cancellation); + } + + await using var read = new PostgreSqlGeneratedKeysDb(database.Options<PostgreSqlGeneratedKeysDb>("typekit_generated_keys")); + (await read.Set<GeneratedByGuid>().Select(e => e.Id).ToListAsync(cancellation)).Select(id => id.Version).ShouldBe([7, 7]); + (await read.Set<GeneratedByWrapsGuid>().Select(e => e.Id).ToListAsync(cancellation)).Select(id => id.Value.Version).ShouldBe([7, 7]); + (await read.Set<GeneratedByString>().Select(e => e.Id).ToListAsync(cancellation)).Select(id => Guid.Parse(id).Version).ShouldBe([7, 7]); + (await read.Set<GeneratedByWrapsString>().Select(e => e.Id).ToListAsync(cancellation)).Select(id => Guid.Parse(id.Value).Version).ShouldBe([7, 7]); + } + /// <summary>One PostgreSQL container and one schema for the class.</summary> public sealed class Database : IAsyncLifetime { diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryOptionTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryOptionTests.cs index 22709871..58099512 100644 --- a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryOptionTests.cs +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryOptionTests.cs @@ -129,6 +129,19 @@ public async Task SingleOrNone_asks_for_two_rows_and_refuses_more_than_one() (await db.Shipments.Where(s => s.Carrier == "delta").SingleOrNoneAsync(TestContext.Current.CancellationToken)).TryGetValue(out _).ShouldBeFalse(); } + /// <summary> + /// With a condition two rows match, as without one: a query asked for one row would find one and answer + /// it, where the condition has two answers. + /// </summary> + [Fact] + public async Task SingleOrNone_with_a_condition_two_rows_match_refuses_them() + { + await using var db = Db(); + + Should.Throw<InvalidOperationException>(() => db.Shipments.SingleOrNone(s => s.Carrier != "alpha")); + await Should.ThrowAsync<InvalidOperationException>(() => db.Shipments.SingleOrNoneAsync(s => s.Carrier != "alpha", TestContext.Current.CancellationToken)); + } + [Fact] public void ElementAtOrNone_skips_to_the_row_in_the_query() { diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryTranslationVariantTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryTranslationVariantTests.cs new file mode 100644 index 00000000..fdfa4e7a --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/QueryTranslationVariantTests.cs @@ -0,0 +1,383 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests; + +/// <summary> +/// Query translation across the variant catalogue of what a query reads of a value object (<see cref="QueryShapes"/>): +/// every kind, accessor, site, converter and operation, run on SQLite and translated for PostgreSQL, each +/// against the same query over an entity that holds the wrapped type, and against the same query over the +/// objects in memory. +/// </summary> +/// <remarks> +/// <para> +/// The oracle is the package README's "Queries" section and docs/ef-core.md, written per case +/// (<see cref="Expect"/>): +/// </para> +/// <list type="bullet"> +/// <item><description> +/// "<c>.Value</c>, <c>GetValue()</c> and <c>ValueOrNull()</c> translate to the column itself", and "a value +/// object compares to a parameter or a column directly": the query answers the rows the same query over the +/// wrapped type answers, and where the operand is a table's column, which "is re-typed, not cast", with the +/// same SQL. Where EF refuses the wrapped type's query, the value object's may be refused too, and if it +/// answers, it answers as memory does. +/// </description></item> +/// <item><description> +/// "<c>.Value</c> translates only where the column holds the underlying value": on a column a converter of the +/// application's own stores, "a filter, a sort, a grouping or an aggregate" that reads it fails with "could not +/// be translated", "selecting <c>.Value</c> as the query's last step still works", and "comparing the value +/// object itself goes through that converter and still works". How EF orders what such a converter stores is +/// EF's, and claimed nowhere. +/// </description></item> +/// <item><description> +/// "A null passes no <c><</c>, <c><=</c>, <c>></c> or <c>>=</c> in the database": a nullable +/// record-class value object compared with <c><</c> or <c><=</c> answers as the same query over the +/// wrapped type, which leaves out the null rows the generated operators keep in memory. +/// </description></item> +/// <item><description> +/// "Your own <c>CompareTo(TSelf)</c> can't run in the database": a comparison, an ordering, a <c>Min</c> or a +/// <c>Max</c> of such a value object is refused, naming it, in a query and in a query filter alike, and the +/// same by <c>.Value</c> is the column's order. +/// </description></item> +/// </list> +/// </remarks> +public sealed class QueryTranslationVariantTests +{ + /// <summary>One argument per axis: xunit cuts a string argument at 50 characters in a case's name, and the cases would share names.</summary> + public static TheoryData<string, Conversion, Site, Accessor, Op> Cases + { + get + { + var cases = new TheoryData<string, Conversion, Site, Accessor, Op>(); + foreach (var @case in QueryShapes.All()) cases.Add(@case.Kind.Name, @case.Conversion, @case.Site, @case.Accessor, @case.Op); + return cases; + } + } + + [Theory] + [MemberData(nameof(Cases))] + public void A_query_over_a_value_object_answers_as_the_same_query_over_the_wrapped_type(string kind, Conversion conversion, Site site, Accessor accessor, Op op) + { + var @case = QueryShapes.Find(new QueryCase(WrappedKinds.Named(kind), conversion, site, accessor, op).Id); + var expectation = Expect(@case); + if (expectation is Unclaimed unclaimed) Assert.Skip(unclaimed.Reason); + if (expectation is OpenFinding finding) Assert.Skip($"open finding: {finding.Reason}"); + + var valueObject = Run(@case, valueObject: true); + + switch (expectation) + { + case RefusedOrdering: + Refusal(valueObject, "its ordering is its own CompareTo").ShouldContain(@case.Kind.ValueObject.Name); + return; + // The same last step through EF.Functions, which EF takes in memory too, where a database's function cannot run. + case RefusedForeign when @case is { Op: Op.Greatest or Op.Least, Site: Site.Column or Site.Nullable }: + Refusal(valueObject, "is not supported because the query has switched to client-evaluation"); + return; + case RefusedForeign when @case.Op is Op.Select or Op.Distinct: + var memory = Memory(@case); + if (memory.Refusal is { } thrown) + { + // r.N!.Value over the null row, read through the converter and then unwrapped in memory, as memory does. + valueObject.Refusal.ShouldBeOfType(thrown.GetType(), "the query's last step, which throws in memory too"); + return; + } + + valueObject.Refusal.ShouldBeNull("the query's last step, which EF evaluates in memory after reading the value object through its converter"); + valueObject.Rows.ShouldBe(memory.Rows, "the query's last step, evaluated in memory"); + return; + case RefusedForeign: + Refusal(valueObject, "could not be translated"); + return; + case NullsLeftOut: + valueObject.Refusal.ShouldBeNull(); + var nulls = NullRows(@case); + nulls.ShouldNotBeEmpty("the seed holds a row whose value object is null"); + Memory(@case).Rows.ShouldBe([.. valueObject.Rows!.Concat(nulls).Order(StringComparer.Ordinal)], + "the rows memory keeps are the query's and the null rows, which the generated operators sort first"); + + // A string has no < to write the wrapped type's query with; memory is no oracle here. + if (!new QueryBuilder(@case, valueObject: false).CanBuild) return; + break; + } + + var plain = new QueryBuilder(@case, valueObject: false).CanBuild ? Run(@case, valueObject: false) : null; + if (valueObject.Refusal is not null) + { + if (plain is { Refusal: null }) throw new ShouldAssertException($"refused where the same query over the wrapped type answers [{string.Join(", ", plain.Rows!)}]: {valueObject.Refusal}"); + if (plain is null) throw new ShouldAssertException($"refused, where memory answers {Shown(Memory(@case))}: {valueObject.Refusal}"); + return; + } + + if (plain is { Refusal: null }) + { + valueObject.Rows.ShouldBe(plain.Rows, $"the rows of the same query over the wrapped type; memory answers {Shown(Memory(@case))}"); + if (ClaimsTheColumn(@case) && plain.Sql is not null) + valueObject.Sql.ShouldBe(plain.Sql, "the SQL of the same query over the wrapped type"); + } + else + { + valueObject.Rows.ShouldBe(Memory(@case).Rows, $"the rows memory answers, where the wrapped type's query is {(plain is null ? "not written" : "refused")}"); + } + + // PostgreSQL translates where it would refuse what SQLite runs: an element column re-typed to the + // wrapped type, for one, threw there alone. + if (plain is null || PostgreSql(@case, valueObject: false) is not { Refusal: null } wrapped) return; + var translated = PostgreSql(@case, valueObject: true)!; + if (translated.Refusal is not null) + throw new ShouldAssertException($"PostgreSQL refuses what it translates over the wrapped type: {translated.Refusal}"); + if (ClaimsTheColumn(@case)) + translated.Sql.ShouldBe(wrapped.Sql, "PostgreSQL's SQL of the same query over the wrapped type"); + } + + /// <summary>A guard against a vacuous run: the catalogue's floor, every kind, site and accessor in it, and the refusals it expects.</summary> + [Fact] + public void The_catalogue_reaches_every_kind_site_accessor_and_converter() + { + var all = QueryShapes.All().ToList(); + + all.Count.ShouldBeGreaterThan(2500); + all.Select(@case => @case.Kind).Distinct().Count().ShouldBe(WrappedKinds.Queries.Length); + all.Select(@case => @case.Site).Distinct().Count().ShouldBe(Enum.GetValues<Site>().Length); + all.Select(@case => @case.Accessor).Distinct().Count().ShouldBe(Enum.GetValues<Accessor>().Length); + all.Select(@case => @case.Conversion).Distinct().Count().ShouldBe(Enum.GetValues<Conversion>().Length); + all.Count(@case => Expect(@case) is RefusedOrdering).ShouldBeGreaterThan(100); + all.Count(@case => Expect(@case) is RefusedForeign).ShouldBeGreaterThan(50); + all.Count(@case => Expect(@case) is NullsLeftOut).ShouldBeGreaterThan(5); + } + + private abstract record Expectation; + + /// <summary>Answers as the same query over the wrapped type.</summary> + private sealed record Same : Expectation; + + /// <summary>Refused, naming the value object whose own <c>CompareTo</c> the database cannot run.</summary> + private sealed record RefusedOrdering : Expectation; + + /// <summary> + /// Refused by EF, "could not be translated": the column holds what a converter of the application's own + /// stores. A final <c>Select</c> or <c>Distinct</c>, which EF evaluates in memory after reading the value + /// object through that converter, answers as memory does. + /// </summary> + private sealed record RefusedForeign : Expectation; + + /// <summary> + /// Answers as the same query over the wrapped type, where a null passes no comparison, which is fewer rows + /// than memory's: a record class's generated operators sort null first (decision 98). + /// </summary> + private sealed record NullsLeftOut : Expectation; + + /// <summary>A divergence the catalogue found and no decision settles yet: named here, never dropped from the catalogue.</summary> + private sealed record OpenFinding(string Reason) : Expectation; + + /// <summary>No sentence of the docs says what the case answers.</summary> + private sealed record Unclaimed(string Reason) : Expectation; + + private static Expectation Expect(QueryCase @case) + { + if (@case.IsForeign) + { + if (@case.ReadsWrapped) return new RefusedForeign(); + if (!@case.Orders) return new Same(); + return new Unclaimed("EF compares and orders what a converter of the application's own stores; the docs claim only that comparing the value object itself works"); + } + + if (@case.Kind.Ordering == Ordering.HandWritten && @case is { ReadsWrapped: false, Orders: true }) return new RefusedOrdering(); + + // EF refuses the wrapped type's Greatest and Least over a primitive collection's element ("The LINQ expression + // 'p' could not be translated"), so memory is the only oracle there, and SQLite compares these elements as the + // JSON text EF stores: "1:2:00:00" before "2:00:00", "09:30:00.0000000" against a parameter '09:30:00'. The + // wrapped type's own max over a TimeSpan column answers the same text order (measured on 2026-10-16). + if (@case is { Site: Site.CollectionElement, Op: Op.Greatest or Op.Least, ReadsWrapped: true } && (@case.Kind.Wrapped == typeof(TimeSpan) || @case.Kind.Wrapped == typeof(TimeOnly))) + return new Unclaimed("SQLite orders a primitive collection's TimeSpan and TimeOnly elements as the text EF stores, and the wrapped type's query is refused there"); + + // The generated operators of a record class sort null first, so in memory null < x is true; SQL's + // "N" < @x is unknown for a null N, as it is for the wrapped type's lifted operator. + if (!@case.Kind.ValueObject.IsValueType && @case is { Site: Site.Nullable, Accessor: Accessor.Itself, Op: Op.LessThan or Op.LessOrEqual }) + return new NullsLeftOut(); + + return new Same(); + } + + /// <summary>A table's column read through an accessor is "re-typed, not cast": the same SQL as the wrapped type's column.</summary> + private static bool ClaimsTheColumn(QueryCase @case) => + @case.ReadsWrapped && @case.Conversion is Conversion.TypeKit or Conversion.ExplicitTypeKit + && @case.Site is Site.Column or Site.Nullable or Site.Navigation or Site.QueryFilter or Site.NamedQueryFilter; + + /// <summary>The rows of the case's seed whose nullable value object is null, as a query over the table answers them.</summary> + private static string[] NullRows(QueryCase @case) => + [ + .. Seed.Entities(new QueryBuilder(@case, valueObject: true), @case.Kind, valueObject: true) + .Cast<object>() + .Where(row => row.GetType().GetProperty("N")!.GetValue(row) is null) + .Select(row => Text(row.GetType().GetProperty("Id")!.GetValue(row))), + ]; + + private static string Refusal(Answer answer, string expected) + { + answer.Refusal.ShouldNotBeNull($"answered [{string.Join(", ", answer.Rows ?? [])}] where the docs say the query is refused"); + answer.Refusal.ShouldBeOfType<InvalidOperationException>(); + answer.Refusal.Message.ShouldContain(expected); + return answer.Refusal.Message; + } + + private sealed record Answer(string[]? Rows, string? Sql, Exception? Refusal); + + /// <summary>One in-memory SQLite database per model, seeded once and only read.</summary> + private static readonly ConcurrentDictionary<string, Lazy<(SqliteConnection Connection, Exception? Refusal)>> Databases = new(); + + private static Answer Run(QueryCase @case, bool valueObject) + { + var builder = new QueryBuilder(@case, valueObject); + var key = @case.IsFilter + ? $"{@case.Id} | {valueObject}" + : $"{@case.Kind.Name} | {(valueObject ? @case.Conversion : Conversion.TypeKit)} | {builder.Source.Name} | {valueObject}"; + var model = new QueryModel(key, @case, valueObject); + var (connection, refusal) = Databases.GetOrAdd(key, _ => new Lazy<(SqliteConnection, Exception?)>(() => Seeded(model, builder))).Value; + if (refusal is not null) return new Answer(null, null, refusal); + + using var db = QueryDb.Create(model, connection); + try + { + var set = (IQueryable)typeof(DbContext).GetMethod(nameof(DbContext.Set), Type.EmptyTypes)!.MakeGenericMethod(builder.Source).Invoke(db, null)!; + var (query, ordered) = builder.Build(set.Expression); + return Execute(set.Provider, query, ordered, sql: true); + } + catch (Exception exception) when (exception is not ShouldAssertException) + { + return new Answer(null, null, Unwrapped(exception)); + } + } + + /// <summary> + /// The query translated for PostgreSQL, without a server; <see langword="null"/> where no SQL text is + /// written (a filter, which needs the model's database, and a <c>Min</c> or <c>Max</c>, which runs at once). + /// </summary> + private static Answer? PostgreSql(QueryCase @case, bool valueObject) + { + if (@case.IsFilter) return null; + + var builder = new QueryBuilder(@case, valueObject); + var model = new QueryModel($"{@case.Kind.Name} | {(valueObject ? @case.Conversion : Conversion.TypeKit)} | {builder.Source.Name} | {valueObject} | npgsql", @case, valueObject); + using var db = QueryDb.OnPostgreSql(model); + try + { + var set = (IQueryable)typeof(DbContext).GetMethod(nameof(DbContext.Set), Type.EmptyTypes)!.MakeGenericMethod(builder.Source).Invoke(db, null)!; + var (query, _) = builder.Build(set.Expression); + return typeof(IQueryable).IsAssignableFrom(query.Type) ? new Answer(null, set.Provider.CreateQuery(query).ToQueryString(), null) : null; + } + catch (Exception exception) when (exception is not ShouldAssertException) + { + return new Answer(null, null, Unwrapped(exception)); + } + } + + /// <summary> + /// The same query over the value objects in memory, with the query filter applied as EF would, and + /// <c>EF.Functions</c> answered as <see cref="InMemoryFunctions"/> does. + /// </summary> + private static Answer Memory(QueryCase @case) + { + var builder = new QueryBuilder(@case, valueObject: true); + var source = Queryable.AsQueryable(Seed.Entities(builder, @case.Kind, valueObject: true)); + var root = @case.IsFilter + ? Expression.Call(typeof(Queryable), nameof(Queryable.Where), [builder.Source], source.Expression, Expression.Quote(builder.Filter())) + : source.Expression; + var (query, ordered) = builder.Build(root); + try + { + return Execute(source.Provider, new InMemoryFunctions().Visit(query), ordered, sql: false); + } + catch (Exception exception) when (exception is not ShouldAssertException) + { + return new Answer(null, null, Unwrapped(exception)); + } + } + + /// <summary> + /// <c>EF.Functions.Greatest</c>, <c>Least</c> and Npgsql's row-value comparisons, which throw outside a query, as + /// SQLite answers them: by each type's default comparer, which is the wrapped type's order for a value object + /// that declares none of its own, and a null wherever <c>Greatest</c> or <c>Least</c> meets one. + /// </summary> + private sealed class InMemoryFunctions : ExpressionVisitor + { + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Method.GetParameters() is not [{ ParameterType: var functions }, ..] || functions != typeof(DbFunctions)) + return base.VisitMethodCall(node); + + Expression[] arguments = [.. node.Arguments.Skip(1).Select(argument => Visit(argument))]; + return node.Method.Name is nameof(Greatest) or nameof(Least) + ? Expression.Call(typeof(InMemoryFunctions), node.Method.Name, node.Method.GetGenericArguments(), arguments) + : Expression.Call(typeof(InMemoryFunctions), nameof(RowValue), null, [Expression.Constant(node.Method.Name), .. arguments]); + } + + public static T Greatest<T>(T[] values) => Pick(values, order => order > 0); + + public static T Least<T>(T[] values) => Pick(values, order => order < 0); + + private static T Pick<T>(T[] values, Func<int, bool> keeps) => + values.Any(value => value is null) ? default! : values.Aggregate((kept, value) => keeps(Comparer<T>.Default.Compare(value, kept)) ? value : kept); + + /// <summary>A tuple's own order is its elements', one after the other, each by its default comparer.</summary> + public static bool RowValue(string comparison, ITuple left, ITuple right) + { + var order = ((IComparable)left).CompareTo(right); + return comparison switch + { + "GreaterThan" => order > 0, + "GreaterThanOrEqual" => order >= 0, + "LessThan" => order < 0, + _ => order <= 0, + }; + } + } + + /// <summary>An answer as a message shows it: its rows, or its refusal.</summary> + private static string Shown(Answer answer) => + answer.Refusal is { } refusal ? $"refused ({refusal.Message})" : $"[{string.Join(", ", answer.Rows!)}]"; + + private static (SqliteConnection, Exception?) Seeded(QueryModel model, QueryBuilder builder) + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + try + { + using var db = QueryDb.Create(model, connection); + db.Database.EnsureCreated(); + db.AddRange(Seed.Stored(builder, model.Case.Kind, model.ValueObject)); + db.SaveChanges(); + return (connection, null); + } + catch (Exception exception) when (exception is not ShouldAssertException) + { + return (connection, Unwrapped(exception)); + } + } + + private static Answer Execute(IQueryProvider provider, Expression query, bool ordered, bool sql) + { + if (!typeof(IQueryable).IsAssignableFrom(query.Type)) + return new Answer([Text(provider.Execute(query))], null, null); + + var queryable = provider.CreateQuery(query); + var rows = new List<string>(); + foreach (var row in (System.Collections.IEnumerable)queryable) rows.Add(Text(row)); + if (!ordered) rows.Sort(StringComparer.Ordinal); + + return new Answer([.. rows], sql ? queryable.ToQueryString() : null, null); + } + + /// <summary>An entity as its key, anything else as <see cref="WrappedKind.Text"/> writes it.</summary> + private static string Text(object? value) => + value?.GetType() is { IsGenericType: true } type && type.GetProperty("Id") is { } id && type.Namespace == typeof(Seed).Namespace && type.Name != "Box`1" + ? $"#{id.GetValue(value)}" + : WrappedKind.Text(value); + + private static Exception Unwrapped(Exception exception) => exception is TargetInvocationException { InnerException: { } inner } ? Unwrapped(inner) : exception; +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ValueThroughTypeParameterTests.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ValueThroughTypeParameterTests.cs new file mode 100644 index 00000000..a5669d8c --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/ValueThroughTypeParameterTests.cs @@ -0,0 +1,154 @@ +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests; + +/// <summary> +/// <c>.Value</c> read in a generic repository or base class, <c>where T : IValueObject<T, string></c>, as C# +/// writes it: the tree holds <c>((IValueObject<X, string>)r.Name).Value</c>, the interface's member. It +/// translates to the column, as the value object's own <c>.Value</c> does, in a query and in a query filter, for +/// a record class and a record struct alike. <see cref="Variants.QueryShapes"/> holds the same shape over every +/// kind and site (<see cref="Variants.Accessor.ValueThroughTypeParameter"/>); these tests hold the tree the compiler +/// writes, which the catalogue builds by hand. +/// </summary> +public sealed class ValueThroughTypeParameterTests +{ + [Fact] + public void A_record_class_read_through_a_type_parameter_is_the_column() => + StartsWithB<TypeParameterName>(db => db.Names).ShouldAnswer([2], "WHERE n.\"Name\" LIKE 'B%'"); + + [Fact] + public void A_record_struct_read_through_a_type_parameter_is_the_column() => + StartsWithB<TypeParameterCode>(db => db.Codes).ShouldAnswer([2], "WHERE c.\"Name\" LIKE 'B%'"); + + [Fact] + public void A_record_class_compared_through_a_type_parameter_is_the_column() => + Named<TypeParameterName>(db => db.Names, "Charlie").ShouldAnswer([3], "WHERE n.\"Name\" = @name"); + + [Fact] + public void A_record_struct_compared_through_a_type_parameter_is_the_column() => + Named<TypeParameterCode>(db => db.Codes, "Charlie").ShouldAnswer([3], "WHERE c.\"Name\" = @name"); + + /// <summary>EF adds a query filter after the query's own tree is intercepted: the filter is rewritten when the model is built.</summary> + [Fact] + public void A_query_filter_written_in_a_generic_base_reads_the_column() + { + using var connection = Seeded(); + using var db = TypeParameterDb.Create(connection); + + db.Filtered.Select(row => row.Id).Order().ToList().ShouldBe([1, 2, 3]); + db.Filtered.ToQueryString().ShouldContain("WHERE \"f\".\"Name\" <> 'hidden'"); + } + + private static Answered StartsWithB<T>(Func<TypeParameterDb, IQueryable<NamedRow<T>>> set) + where T : IValueObject<T, string> => + Answer(set, query => query.Where(row => row.Name.Value.StartsWith("B"))); + + private static Answered Named<T>(Func<TypeParameterDb, IQueryable<NamedRow<T>>> set, string name) + where T : IValueObject<T, string> => + Answer(set, query => query.Where(row => row.Name.Value == name)); + + private static Answered Answer<T>(Func<TypeParameterDb, IQueryable<NamedRow<T>>> set, Func<IQueryable<NamedRow<T>>, IQueryable<NamedRow<T>>> query) + where T : IValueObject<T, string> + { + using var connection = Seeded(); + using var db = TypeParameterDb.Create(connection); + var rows = query(set(db)).Select(row => row.Id).Order().ToArray(); + + using var postgreSql = TypeParameterDb.OnPostgreSql(); + var sql = query(set(postgreSql)).ToQueryString(); + + return new Answered(rows, sql[sql.IndexOf("WHERE", StringComparison.Ordinal)..].Trim()); + } + + /// <summary>The rows SQLite answers, and the <c>WHERE</c> clause PostgreSQL's SQL ends with.</summary> + private sealed record Answered(int[] Rows, string Sql) + { + public void ShouldAnswer(int[] rows, string where) + { + Rows.ShouldBe(rows); + Sql.ShouldBe(where); + } + } + + private static SqliteConnection Seeded() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + using var db = TypeParameterDb.Create(connection); + db.Database.EnsureCreated(); + string[] names = ["Alpha", "Bravo", "Charlie"]; + for (var i = 0; i < names.Length; i++) + { + db.Names.Add(new NamedRow<TypeParameterName> { Id = i + 1, Name = TypeParameterName.From(names[i]) }); + db.Codes.Add(new NamedRow<TypeParameterCode> { Id = i + 1, Name = TypeParameterCode.From(names[i]) }); + db.Filtered.Add(new FilteredRow { Id = i + 1, Name = TypeParameterName.From(names[i]) }); + } + + db.Filtered.Add(new FilteredRow { Id = 4, Name = TypeParameterName.From("hidden") }); + db.SaveChanges(); + db.ChangeTracker.Clear(); + + return connection; + } +} + +public sealed partial record TypeParameterName : IValue<string>; + +public readonly partial record struct TypeParameterCode : IValue<string>; + +public class NamedRow<T> + where T : IValueObject<T, string> +{ + public int Id { get; set; } + + public required T Name { get; set; } +} + +/// <summary>A generic base outside the model, so that its one row type is a root of its own and carries the filter.</summary> +public class FilterableRow<T> + where T : IValueObject<T, string> +{ + public int Id { get; set; } + + public required T Name { get; set; } +} + +public sealed class FilteredRow : FilterableRow<TypeParameterName>; + +public sealed class TypeParameterDb(DbContextOptions<TypeParameterDb> options) : DbContext(options) +{ + public DbSet<NamedRow<TypeParameterName>> Names => Set<NamedRow<TypeParameterName>>(); + + public DbSet<NamedRow<TypeParameterCode>> Codes => Set<NamedRow<TypeParameterCode>>(); + + public DbSet<FilteredRow> Filtered => Set<FilteredRow>(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity<NamedRow<TypeParameterName>>().ToTable("Names").Property(row => row.Id).ValueGeneratedNever(); + modelBuilder.Entity<NamedRow<TypeParameterCode>>().ToTable("Codes").Property(row => row.Id).ValueGeneratedNever(); + + var filtered = modelBuilder.Entity<FilteredRow>(); + filtered.ToTable("Filtered").Property(row => row.Id).ValueGeneratedNever(); + HidesTheHidden(filtered); + } + + /// <summary>A filter a generic base class configures, for every row type it is given.</summary> + private static void HidesTheHidden<TRow, T>(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<TRow> entity) + where TRow : FilterableRow<T> + where T : IValueObject<T, string> => + entity.HasQueryFilter(row => row.Name.Value != "hidden"); + + private static void HidesTheHidden(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<FilteredRow> entity) => + HidesTheHidden<FilteredRow, TypeParameterName>(entity); + + public static TypeParameterDb Create(SqliteConnection connection) => + new(new DbContextOptionsBuilder<TypeParameterDb>().UseSqlite(connection).UseTypeKit().Options); + + /// <summary>For SQL text only: <c>ToQueryString</c> translates without opening the connection.</summary> + public static TypeParameterDb OnPostgreSql() => + new(new DbContextOptionsBuilder<TypeParameterDb>().UseNpgsql("Host=localhost;Database=none").UseTypeKit().Options); +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/KeyShapes.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/KeyShapes.cs new file mode 100644 index 00000000..61135a9a --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/KeyShapes.cs @@ -0,0 +1,219 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; + +/// <summary>Where a key sits in a model: the shapes EF decides a key's generation again for.</summary> +public enum KeyShape +{ + /// <summary>An entity type of its own.</summary> + Alone, + + /// <summary>The root of a hierarchy with a derived type, under each mapping strategy; TPH is EF's default.</summary> + TphRoot, + TptRoot, + TpcRoot, + + /// <summary>An abstract root with two derived types, mapped to no table of its own under TPC.</summary> + AbstractTphRoot, + AbstractTptRoot, + AbstractTpcRoot, + + /// <summary>A one-to-one dependent whose key is the foreign key to its principal's.</summary> + SharedKeyDependent, + + /// <summary>A one-to-one dependent sharing its principal's table.</summary> + TableSplitDependent, + + /// <summary>An owned type, whose key is its owner's.</summary> + OwnedDependent, + + /// <summary>A key of two properties of the kind.</summary> + Composite, + + /// <summary>The application's own configuration, which wins over every default.</summary> + ConfiguredNever, + ConfiguredOnAdd, +} + +/// <summary> +/// The key-generation catalogue (chain H of design/fix-history.md): every <see cref="KeyShape"/> keyed by +/// every kind of <see cref="WrappedKinds.Keys"/>, on SQLite, Npgsql and SQL Server. Each is built twice, +/// keyed by the value object under <c>UseTypeKit()</c> and keyed by the wrapped type under plain EF. +/// </summary> +public static class KeyShapes +{ + public static readonly string[] Providers = ["sqlite", "npgsql", "sqlserver"]; + + public static IEnumerable<(string Provider, KeyShape Shape, WrappedKind Kind)> All() => + from provider in Providers + from shape in Enum.GetValues<KeyShape>() + from kind in WrappedKinds.Keys + select (provider, shape, kind); + + /// <summary>The entity types whose key the shape is about, closed over <paramref name="key"/>.</summary> + public static Type Subject(KeyShape shape, Type key) => shape switch + { + KeyShape.Alone or KeyShape.ConfiguredNever or KeyShape.ConfiguredOnAdd => typeof(Alone<>).MakeGenericType(key), + KeyShape.TphRoot or KeyShape.TptRoot or KeyShape.TpcRoot => typeof(Root<>).MakeGenericType(key), + KeyShape.AbstractTphRoot or KeyShape.AbstractTptRoot or KeyShape.AbstractTpcRoot => typeof(AbstractRoot<>).MakeGenericType(key), + KeyShape.SharedKeyDependent or KeyShape.TableSplitDependent => typeof(Dependent<>).MakeGenericType(key), + KeyShape.OwnedDependent => typeof(OwnedNote), + KeyShape.Composite => typeof(Composite<>).MakeGenericType(key), + _ => throw new ArgumentOutOfRangeException(nameof(shape), shape, null), + }; + + /// <summary>The shape's model, keyed by <paramref name="key"/>.</summary> + public static void Configure(ModelBuilder model, KeyShape shape, Type key) + { + Type Closed(Type definition) => definition.MakeGenericType(key); + + switch (shape) + { + case KeyShape.Alone: + model.Entity(Closed(typeof(Alone<>))); + break; + case KeyShape.ConfiguredNever: + model.Entity(Closed(typeof(Alone<>))).Property("Id").ValueGeneratedNever(); + break; + case KeyShape.ConfiguredOnAdd: + model.Entity(Closed(typeof(Alone<>))).Property("Id").ValueGeneratedOnAdd(); + break; + case KeyShape.TphRoot or KeyShape.TptRoot or KeyShape.TpcRoot: + Strategy(model.Entity(Closed(typeof(Root<>))), shape); + model.Entity(Closed(typeof(Leaf<>))); + break; + case KeyShape.AbstractTphRoot or KeyShape.AbstractTptRoot or KeyShape.AbstractTpcRoot: + Strategy(model.Entity(Closed(typeof(AbstractRoot<>))), shape); + model.Entity(Closed(typeof(LeafA<>))); + model.Entity(Closed(typeof(LeafB<>))); + break; + case KeyShape.SharedKeyDependent: + model.Entity(Closed(typeof(Principal<>))).HasOne(Closed(typeof(Dependent<>))).WithOne().HasForeignKey(Closed(typeof(Dependent<>)), "Id"); + break; + case KeyShape.TableSplitDependent: + model.Entity(Closed(typeof(Principal<>))).ToTable("Split").HasOne(Closed(typeof(Dependent<>))).WithOne().HasForeignKey(Closed(typeof(Dependent<>)), "Id"); + model.Entity(Closed(typeof(Dependent<>))).ToTable("Split"); + break; + case KeyShape.OwnedDependent: + model.Entity(Closed(typeof(Owner<>))).OwnsOne(typeof(OwnedNote), "Part"); + break; + case KeyShape.Composite: + model.Entity(Closed(typeof(Composite<>))).HasKey("Id", "Line"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(shape), shape, null); + } + } + + private static void Strategy(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder root, KeyShape shape) + { + if (shape is KeyShape.TptRoot or KeyShape.AbstractTptRoot) root.UseTptMappingStrategy(); + if (shape is KeyShape.TpcRoot or KeyShape.AbstractTpcRoot) root.UseTpcMappingStrategy(); + } + + /// <summary>The finished model's key properties of the shape's subject.</summary> + public static IReadOnlyList<IProperty> KeyOf(DbContext db, KeyShape shape, Type key) => + db.GetService<IDesignTimeModel>().Model.FindEntityType(Subject(shape, key))!.FindPrimaryKey()!.Properties; +} + +/// <summary>A model of one key shape, cached per shape, key type and side.</summary> +public sealed class KeyShapeDb(DbContextOptions<KeyShapeDb> options, KeyShape shape, Type key) : DbContext(options) +{ + public KeyShape Shape => shape; + + public Type Key => key; + + protected override void OnModelCreating(ModelBuilder modelBuilder) => KeyShapes.Configure(modelBuilder, shape, key); + + public static KeyShapeDb Create(string provider, KeyShape shape, Type key, bool typeKit, Microsoft.Data.Sqlite.SqliteConnection connection) + { + var builder = new DbContextOptionsBuilder<KeyShapeDb>().ReplaceService<IModelCacheKeyFactory, KeyShapeModelCacheKey>(); + builder = provider switch + { + "sqlite" => builder.UseSqlite(connection), + "npgsql" => builder.UseNpgsql("Host=localhost;Database=none"), + "sqlserver" => builder.UseSqlServer("Server=localhost;Database=none"), + _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null), + }; + if (typeKit) builder = builder.UseTypeKit(); + + return new KeyShapeDb(builder.Options, shape, key); + } +} + +public sealed class KeyShapeModelCacheKey : IModelCacheKeyFactory +{ + public object Create(DbContext context, bool designTime) => + context is KeyShapeDb db ? (db.GetType(), db.Shape, db.Key, designTime) : (context.GetType(), designTime); +} + +public class Alone<TKey> +{ + public TKey Id { get; set; } = default!; + + public string Name { get; set; } = ""; +} + +public class Root<TKey> +{ + public TKey Id { get; set; } = default!; + + public string Name { get; set; } = ""; +} + +public class Leaf<TKey> : Root<TKey> +{ + public string Extra { get; set; } = ""; +} + +public abstract class AbstractRoot<TKey> +{ + public TKey Id { get; set; } = default!; + + public string Name { get; set; } = ""; +} + +public class LeafA<TKey> : AbstractRoot<TKey> +{ + public int Doors { get; set; } +} + +public class LeafB<TKey> : AbstractRoot<TKey> +{ + public int Gears { get; set; } +} + +public class Principal<TKey> +{ + public TKey Id { get; set; } = default!; + + public string Name { get; set; } = ""; +} + +public class Dependent<TKey> +{ + public TKey Id { get; set; } = default!; + + public string Note { get; set; } = ""; +} + +public class Owner<TKey> +{ + public TKey Id { get; set; } = default!; + + public OwnedNote Part { get; set; } = new(); +} + +public class OwnedNote +{ + public string Note { get; set; } = ""; +} + +public class Composite<TKey> +{ + public TKey Id { get; set; } = default!; + + public TKey Line { get; set; } = default!; +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/QueryShapes.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/QueryShapes.cs new file mode 100644 index 00000000..a99b6e6f --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/QueryShapes.cs @@ -0,0 +1,780 @@ +using System.Globalization; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; + +/// <summary>Where the value object a query reads is.</summary> +public enum Site +{ + /// <summary>A table column, <c>r.P</c>.</summary> + Column, + + /// <summary>A nullable column, <c>r.N</c>, read with <c>ValueOrNull()</c>.</summary> + Nullable, + + /// <summary>The element of a primitive collection, <c>r.Tags.Any(t => …)</c>.</summary> + CollectionElement, + + /// <summary>The element of a list the query captures, <c>ids.Any(i => … r.P …)</c>.</summary> + ParameterElement, + + /// <summary>A column reached over a collection navigation, <c>b.Rows.Max(x => …)</c>.</summary> + Navigation, + + /// <summary>A property of an owned type mapped to a JSON column, <c>r.Doc.P</c>.</summary> + JsonProperty, + + /// <summary>A query filter of the model, which EF adds after the query's own tree is seen.</summary> + QueryFilter, + + /// <summary>A named query filter.</summary> + NamedQueryFilter, +} + +/// <summary>How the query reaches what the value object wraps.</summary> +public enum Accessor +{ + Value, + GetValue, + ValueOrNull, + + /// <summary>Not at all: the value object itself, compared, ordered or projected.</summary> + Itself, + + /// <summary> + /// <c>.Value</c> in a generic method or base class, <c>where T : IValueObject<T, TValue></c>: the tree + /// C# writes holds <c>((IValueObject<X, W>)r.P).Value</c>, the interface's member, not the value object's + /// (<c>r.N!.Value.Value</c> through a <c>Nullable</c>). + /// </summary> + ValueThroughTypeParameter, +} + +/// <summary>Who stores the value object's column.</summary> +public enum Conversion +{ + /// <summary>The converter <c>UseTypeKit()</c> composes.</summary> + TypeKit, + + /// <summary><c>HasConversion(typeof(ValueObjectConverter<,>))</c>, TypeKit's own, named.</summary> + ExplicitTypeKit, + + /// <summary>A converter of the application's own, built from lambdas: stores <c>"C:" + value</c>.</summary> + ForeignLambda, + + /// <summary>The same as a converter type, <c>HasConversion(typeof(TConverter))</c>.</summary> + ForeignConverterType, + + /// <summary>The same from <c>ConfigureConventions</c>.</summary> + ForeignConvention, +} + +/// <summary>What the query does with the value it reads.</summary> +public enum Op +{ + EqualsLiteral, + EqualsCaptured, + NotEquals, + GreaterOrEqual, + LessThan, + LessOrEqual, + + /// <summary><c>r.N == null || r.N < x</c>: the null rows kept in SQL, as the generated operators of a record class keep them in memory.</summary> + LessThanOrNull, + + CompareTo, + OrderBy, + OrderByDescending, + ThenBy, + ThenByDescending, + OrderByBoxed, + ThenByBoxed, + Order, + OrderDescending, + Min, + Max, + MinBy, + MaxBy, + Contains, + GroupBy, + Select, + Distinct, + AnyEquals, + AnyGreaterOrEqual, + CountGreaterOrEqual, + ElementContains, + ElementMax, + ElementOrderFirst, + NavigationMin, + NavigationMax, + NavigationOrderFirst, + NavigationOrderDescendingFirst, + + /// <summary><c>EF.Functions.Greatest(r.P, x)</c>, relational: SQLite's <c>max</c>, PostgreSQL's <c>GREATEST</c>.</summary> + Greatest, + + /// <summary><c>EF.Functions.Least(r.P, x)</c>.</summary> + Least, + + /// <summary>Npgsql's row value, <c>EF.Functions.GreaterThan((r.P, r.Id), (x, 0))</c>; SQLite translates none of the four.</summary> + RowGreaterThan, + + RowLessThan, + RowGreaterOrEqual, + RowLessOrEqual, +} + +/// <summary>One query of the catalogue: a kind, who stores it, where the query reads it, how, and what it does.</summary> +public sealed record QueryCase(WrappedKind Kind, Conversion Conversion, Site Site, Accessor Accessor, Op Op) +{ + public string Id => $"{Kind.Name} | {Conversion} | {Site} | {Accessor} | {Op}"; + + public override string ToString() => Id; + + public bool ReadsWrapped => Accessor != Accessor.Itself; + + /// <summary>A comparison, an ordering, or a minimum or maximum: what a value object's own <c>CompareTo</c> decides in memory.</summary> + public bool Orders => Op is Op.GreaterOrEqual or Op.LessThan or Op.LessOrEqual or Op.LessThanOrNull or Op.CompareTo or Op.OrderBy or Op.OrderByDescending or Op.ThenBy or Op.ThenByDescending + or Op.OrderByBoxed or Op.ThenByBoxed or Op.Order or Op.OrderDescending or Op.Min or Op.Max or Op.MinBy or Op.MaxBy + or Op.AnyGreaterOrEqual or Op.CountGreaterOrEqual or Op.ElementMax or Op.ElementOrderFirst + or Op.NavigationMin or Op.NavigationMax or Op.NavigationOrderFirst or Op.NavigationOrderDescendingFirst + or Op.Greatest or Op.Least or Op.RowGreaterThan or Op.RowLessThan or Op.RowGreaterOrEqual or Op.RowLessOrEqual; + + public bool IsFilter => Site is Site.QueryFilter or Site.NamedQueryFilter; + + public bool IsForeign => Conversion is Conversion.ForeignLambda or Conversion.ForeignConverterType or Conversion.ForeignConvention; +} + +/// <summary> +/// The query-translation catalogue (chain I of design/fix-history.md): what a query reads of a value object, +/// where, through which accessor and converter, and what it does with it, over every kind of +/// <see cref="WrappedKinds.Queries"/>. +/// </summary> +public static class QueryShapes +{ + /// <summary>The database's own orderings, called through <c>EF.Functions</c>; declared first, as <see cref="ColumnOps"/> reads it.</summary> + private static readonly Op[] FunctionOps = [Op.Greatest, Op.Least, Op.RowGreaterThan, Op.RowLessThan, Op.RowGreaterOrEqual, Op.RowLessOrEqual]; + + private static readonly Op[] ColumnOps = + [ + Op.EqualsLiteral, Op.EqualsCaptured, Op.NotEquals, Op.GreaterOrEqual, Op.LessThan, Op.LessOrEqual, Op.CompareTo, + Op.OrderBy, Op.OrderByDescending, Op.ThenBy, Op.ThenByDescending, Op.OrderByBoxed, Op.ThenByBoxed, Op.Order, Op.OrderDescending, + Op.Min, Op.Max, Op.MinBy, Op.MaxBy, Op.Contains, Op.GroupBy, Op.Select, Op.Distinct, .. FunctionOps, + ]; + + private static readonly Dictionary<Site, Op[]> Ops = new() + { + [Site.Column] = ColumnOps, + [Site.Nullable] = [.. ColumnOps.Where(op => op != Op.CompareTo), Op.LessThanOrNull], + [Site.CollectionElement] = [Op.AnyEquals, Op.AnyGreaterOrEqual, Op.ElementContains, Op.ElementMax, Op.ElementOrderFirst, .. FunctionOps], + [Site.ParameterElement] = [Op.AnyEquals, Op.AnyGreaterOrEqual], + [Site.Navigation] = [Op.AnyEquals, Op.CountGreaterOrEqual, Op.NavigationMin, Op.NavigationMax, Op.NavigationOrderFirst, Op.NavigationOrderDescendingFirst], + [Site.JsonProperty] = [Op.EqualsCaptured, Op.GreaterOrEqual, Op.OrderBy, Op.Max, Op.Select], + [Site.QueryFilter] = [Op.EqualsLiteral, Op.GreaterOrEqual, Op.CompareTo, Op.NavigationMax, .. FunctionOps], + [Site.NamedQueryFilter] = [Op.GreaterOrEqual], + }; + + private static Accessor[] Accessors(Site site) => site switch + { + Site.Nullable => [Accessor.ValueOrNull, Accessor.ValueThroughTypeParameter, Accessor.Itself], + Site.QueryFilter or Site.NamedQueryFilter => [Accessor.Value, Accessor.ValueThroughTypeParameter, Accessor.Itself], + _ => [Accessor.Value, Accessor.GetValue, Accessor.ValueThroughTypeParameter, Accessor.Itself], + }; + + /// <summary>The kinds a converter of the application's own is tried on, and the sites it reaches: the table's columns.</summary> + private static readonly WrappedKind[] ForeignKinds = [WrappedKinds.Int32, WrappedKinds.String, WrappedKinds.StringRecordClass]; + + private static readonly Site[] ConvertedSites = [Site.Column, Site.Nullable, Site.ParameterElement, Site.Navigation]; + + private static readonly Lazy<Dictionary<string, QueryCase>> Catalogue = new(() => Enumerate().ToDictionary(c => c.Id)); + + public static IEnumerable<QueryCase> All() => Catalogue.Value.Values; + + public static QueryCase Find(string id) => Catalogue.Value[id]; + + /// <summary>Every combination C# can write: one whose query over the value object does not build (no <c>>=</c> on a <c>Guid</c>) is no case.</summary> + private static IEnumerable<QueryCase> Enumerate() + { + foreach (var kind in WrappedKinds.Queries) + foreach (var conversion in Enum.GetValues<Conversion>()) + { + if (conversion != Conversion.TypeKit && !ForeignKinds.Contains(kind)) continue; + + foreach (var (site, ops) in Ops) + { + if (conversion != Conversion.TypeKit && !ConvertedSites.Contains(site)) continue; + + foreach (var accessor in Accessors(site)) + foreach (var op in ops) + { + var candidate = new QueryCase(kind, conversion, site, accessor, op); + if (new QueryBuilder(candidate, valueObject: true).CanBuild) yield return candidate; + } + } + } + } +} + +/// <summary>Holds a captured value, so that a query reads it as a parameter, as a closure does.</summary> +public sealed class Box<T>(T value) +{ + public T Value = value; +} + +public class Row<T, TN> +{ + public int Id { get; set; } + + public T P { get; set; } = default!; + + public TN? N { get; set; } + + public int BagId { get; set; } +} + +public class Bag<T, TN> +{ + public int Id { get; set; } + + public List<Row<T, TN>> Rows { get; set; } = []; +} + +public class ElementRow<T> +{ + public int Id { get; set; } + + public List<T> Tags { get; set; } = []; +} + +public class DocRow<T> +{ + public int Id { get; set; } + + public Doc<T> Doc { get; set; } = new(); +} + +public class Doc<T> +{ + public T P { get; set; } = default!; +} + +/// <summary> +/// One query of the catalogue, built over the value object or over the wrapped type: the same expression +/// tree either way, but for the accessor, so the two can be compared, and run against a database or +/// against the objects in memory. +/// </summary> +public sealed class QueryBuilder(QueryCase @case, bool valueObject) +{ + private WrappedKind Kind => @case.Kind; + + public Type Element => valueObject ? Kind.ValueObject : Kind.Wrapped; + + public Type Optional => Element.IsValueType ? typeof(Nullable<>).MakeGenericType(Element) : Element; + + public Type Row => typeof(Row<,>).MakeGenericType(Element, Optional); + + public Type Bag => typeof(Bag<,>).MakeGenericType(Element, Optional); + + public Type ElementRow => typeof(ElementRow<>).MakeGenericType(Element); + + public Type DocRow => typeof(DocRow<>).MakeGenericType(Element); + + /// <summary>The entity type the query starts from.</summary> + public Type Source => @case.Site switch + { + Site.Navigation => Bag, + Site.QueryFilter when @case.Op == Op.NavigationMax => Bag, + Site.CollectionElement => ElementRow, + Site.JsonProperty => DocRow, + _ => Row, + }; + + public bool CanBuild + { + get + { + try + { + if (@case.IsFilter) Filter(); + else Build(Expression.Constant(Array.CreateInstance(Source, 0).AsQueryable())); + return true; + } + catch (InvalidOperationException) + { + return false; + } + catch (ArgumentException) + { + return false; + } + } + } + + /// <summary>The query, and whether its rows come in an order the query states.</summary> + public (Expression Query, bool Ordered) Build(Expression source) + { + if (@case.IsFilter) + { + var entity = Expression.Parameter(Source, "e"); + return (Q("Select", [Source, typeof(int)], source, Expression.Lambda(Expression.Property(entity, "Id"), entity)), false); + } + + return @case.Site switch + { + Site.Column or Site.Nullable or Site.JsonProperty => OverColumn(source), + Site.CollectionElement => OverElements(source), + Site.ParameterElement => OverParameter(source), + Site.Navigation => OverNavigation(source), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + } + + /// <summary>The query filter of a <see cref="Site.QueryFilter"/> case, over <see cref="Source"/>.</summary> + public LambdaExpression Filter() + { + var entity = Expression.Parameter(Source, "e"); + if (@case.Op == Op.NavigationMax) + { + var row = Expression.Parameter(Row, "x"); + var max = Read(Expression.Property(row, "P")); + var aggregate = E("Max", [Row, max.Type], Expression.Property(entity, "Rows"), Expression.Lambda(max, row)); + return Expression.Lambda(Expression.GreaterThanOrEqual(aggregate, Literal(aggregate.Type)), entity); + } + + var read = Read(Expression.Property(entity, "P")); + Expression body = @case.Op switch + { + Op.EqualsLiteral => Expression.Equal(read, Literal(read.Type)), + Op.GreaterOrEqual => Expression.GreaterThanOrEqual(read, Literal(read.Type)), + Op.CompareTo => Expression.GreaterThan(CompareTo(read, Literal(read.Type)), Expression.Constant(0)), + Op.Greatest or Op.Least => Expression.Equal(Function(@case.Op, read, Literal(read.Type)), read), + Op.RowGreaterThan or Op.RowLessThan or Op.RowGreaterOrEqual or Op.RowLessOrEqual + => RowValue(@case.Op, read, Expression.Property(entity, "Id"), Literal(read.Type), Expression.Constant(0)), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + return Expression.Lambda(body, entity); + } + + /// <summary> + /// The value the query reads, through the case's accessor; the wrapped type is read as it is, but for a + /// <c>Nullable</c> read through a type parameter: <c>r.N!.Value</c> over the value object is <c>r.N!.Value</c> + /// over an <c>int?</c>, and both throw on a null they project. + /// </summary> + private Expression Read(Expression operand) => !valueObject || @case.Accessor == Accessor.Itself + ? !valueObject && @case.Accessor == Accessor.ValueThroughTypeParameter && Nullable.GetUnderlyingType(operand.Type) is not null + ? Expression.Property(operand, "Value") + : operand + : @case.Accessor switch + { + Accessor.Value => Expression.Property(operand, "Value"), + Accessor.GetValue => Expression.Call(Kind.Companion("GetValue"), operand), + Accessor.ValueOrNull => Expression.Call(Kind.Companion("ValueOrNull"), operand), + Accessor.ValueThroughTypeParameter => ThroughTypeParameter(operand), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + + /// <summary>What C# writes for <c>.Value</c> on a <c>T</c> constrained to the interface: a conversion to it, and its member.</summary> + private Expression ThroughTypeParameter(Expression operand) + { + var contract = typeof(IValueObject<,>).MakeGenericType(Kind.ValueObject, Kind.Wrapped); + var instance = Nullable.GetUnderlyingType(operand.Type) is null ? operand : Expression.Property(operand, "Value"); + return Expression.Property(Expression.Convert(instance, contract), contract.GetProperty("Value")!); + } + + /// <summary>The middle value, as the type the query reads it as.</summary> + private object Middle => valueObject && @case.Accessor == Accessor.Itself ? Kind.Wrap(Kind.Values[1]) : Kind.Values[1]; + + private Expression Literal(Type type) => Expression.Constant(Middle, type); + + private Expression Captured(Type type) => + Expression.Field(Expression.Constant(Activator.CreateInstance(typeof(Box<>).MakeGenericType(type), Middle)), "Value"); + + /// <summary><c>EF.Functions</c>, as C# writes it in an expression tree.</summary> + private static readonly Expression Functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions))!); + + /// <summary><c>EF.Functions.Greatest(left, right)</c> or <c>Least</c>, typed as <paramref name="left"/>.</summary> + private static Expression Function(Op op, Expression left, Expression right) => + Expression.Call(typeof(RelationalDbFunctionsExtensions), op.ToString(), [left.Type], Functions, Expression.NewArrayInit(left.Type, left, right)); + + /// <summary>Npgsql's row-value comparison of <c>(left, leftKey)</c> with <c>(right, rightKey)</c>, each tuple passed as an <see cref="ITuple"/>.</summary> + private static Expression RowValue(Op op, Expression left, Expression leftKey, Expression right, Expression rightKey) + { + var name = op switch + { + Op.RowGreaterThan => nameof(NpgsqlDbFunctionsExtensions.GreaterThan), + Op.RowLessThan => nameof(NpgsqlDbFunctionsExtensions.LessThan), + Op.RowGreaterOrEqual => nameof(NpgsqlDbFunctionsExtensions.GreaterThanOrEqual), + _ => nameof(NpgsqlDbFunctionsExtensions.LessThanOrEqual), + }; + Expression Tuple(Expression item1, Expression item2) => + Expression.Convert(Expression.Call(typeof(ValueTuple), nameof(ValueTuple.Create), [item1.Type, item2.Type], item1, item2), typeof(ITuple)); + + return Expression.Call(typeof(NpgsqlDbFunctionsExtensions), name, null, Functions, Tuple(left, leftKey), Tuple(right, rightKey)); + } + + private static Expression CompareTo(Expression left, Expression right) => + Expression.Call(left, left.Type.GetMethod("CompareTo", [left.Type]) ?? throw new InvalidOperationException($"{left.Type} has no CompareTo({left.Type})"), right); + + private (Expression, bool) OverColumn(Expression source) + { + var row = Expression.Parameter(Source, "r"); + Expression operand = @case.Site switch + { + Site.Column => Expression.Property(row, "P"), + Site.Nullable => Expression.Property(row, "N"), + Site.JsonProperty => Expression.Property(Expression.Property(row, "Doc"), "P"), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + var read = Read(operand); + var type = read.Type; + var key = Expression.Lambda(read, row); + + Expression Ids(Expression query) => Q("Select", [Source, typeof(int)], query, Expression.Lambda(Expression.Property(row, "Id"), row)); + Expression Where(Expression predicate) => Ids(Q("Where", [Source], source, Expression.Lambda(predicate, row))); + Expression Values(Expression ordered) => Q("Select", [Source, type], ordered, key); + Expression Unordered() => Q("OrderBy", [Source, typeof(int)], source, Expression.Lambda(Expression.Modulo(Expression.Property(row, "Id"), Expression.Constant(1)), row)); + var boxed = Expression.Lambda(Expression.Convert(read, typeof(object)), row); + + return @case.Op switch + { + Op.EqualsLiteral => (Where(Expression.Equal(read, Literal(type))), false), + Op.EqualsCaptured => (Where(Expression.Equal(read, Captured(type))), false), + Op.NotEquals => (Where(Expression.NotEqual(read, Captured(type))), false), + Op.GreaterOrEqual => (Where(Expression.GreaterThanOrEqual(read, Captured(type))), false), + Op.LessThan => (Where(Expression.LessThan(read, Captured(type))), false), + Op.LessOrEqual => (Where(Expression.LessThanOrEqual(read, Captured(type))), false), + Op.LessThanOrNull => (Where(Expression.OrElse(Expression.Equal(read, Expression.Constant(null, type)), Expression.LessThan(read, Captured(type)))), false), + Op.CompareTo => (Where(Expression.GreaterThan(CompareTo(read, Captured(type)), Expression.Constant(0))), false), + Op.OrderBy => (Values(Q("OrderBy", [Source, type], source, key)), true), + Op.OrderByDescending => (Values(Q("OrderByDescending", [Source, type], source, key)), true), + Op.ThenBy => (Values(Q("ThenBy", [Source, type], Unordered(), key)), true), + Op.ThenByDescending => (Values(Q("ThenByDescending", [Source, type], Unordered(), key)), true), + Op.OrderByBoxed => (Values(Q("OrderBy", [Source, typeof(object)], source, boxed)), true), + Op.ThenByBoxed => (Values(Q("ThenBy", [Source, typeof(object)], Unordered(), boxed)), true), + Op.Order => (Q("Order", [type], Values(source)), true), + Op.OrderDescending => (Q("OrderDescending", [type], Values(source)), true), + Op.Min => (Q("Min", [Source, type], source, key), true), + Op.Max => (Q("Max", [Source, type], source, key), true), + Op.MinBy => (Q("MinBy", [Source, type], source, key), true), + Op.MaxBy => (Q("MaxBy", [Source, type], source, key), true), + Op.Contains => (Where(E("Contains", [type], Captured(typeof(List<>).MakeGenericType(type), List(type, Middle)), read)), false), + Op.GroupBy => (Grouped(source, key, type), false), + Op.Select => (Values(source), false), + Op.Distinct => (Q("Distinct", [type], Values(source)), false), + Op.Greatest or Op.Least => (Q("Select", [Source, type], source, Expression.Lambda(Function(@case.Op, read, Captured(type)), row)), false), + Op.RowGreaterThan or Op.RowLessThan or Op.RowGreaterOrEqual or Op.RowLessOrEqual + => (Where(RowValue(@case.Op, read, Expression.Property(row, "Id"), Captured(type), Captured(typeof(int), 0))), false), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + } + + private Expression Grouped(Expression source, LambdaExpression key, Type type) + { + var grouping = typeof(IGrouping<,>).MakeGenericType(type, Source); + var group = Expression.Parameter(grouping, "g"); + return Q("Select", [grouping, type], Q("GroupBy", [Source, type], source, key), Expression.Lambda(Expression.Property(group, "Key"), group)); + } + + private (Expression, bool) OverElements(Expression source) + { + var row = Expression.Parameter(Source, "r"); + var tag = Expression.Parameter(Element, "t"); + var tags = Expression.Property(row, "Tags"); + var read = Read(tag); + var type = read.Type; + var key = Expression.Lambda(read, tag); + + Expression Where(Expression predicate) => + Q("Select", [Source, typeof(int)], Q("Where", [Source], source, Expression.Lambda(predicate, row)), Expression.Lambda(Expression.Property(row, "Id"), row)); + Expression Select(Expression value) => Q("Select", [Source, type], source, Expression.Lambda(value, row)); + + return @case.Op switch + { + Op.AnyEquals => (Where(E("Any", [Element], tags, Expression.Lambda(Expression.Equal(read, Captured(type)), tag))), false), + Op.AnyGreaterOrEqual => (Where(E("Any", [Element], tags, Expression.Lambda(Expression.GreaterThanOrEqual(read, Captured(type)), tag))), false), + Op.ElementContains => (Where(E("Contains", [type], E("Select", [Element, type], tags, key), Captured(type))), false), + Op.ElementMax => (Select(E("Max", [Element, type], tags, key)), false), + Op.ElementOrderFirst => (Select(E("First", [type], E("Select", [Element, type], E("OrderBy", [Element, type], tags, key), key))), false), + Op.Greatest or Op.Least => (Where(E("Any", [Element], tags, Expression.Lambda(Expression.Equal(Function(@case.Op, read, Captured(type)), read), tag))), false), + Op.RowGreaterThan or Op.RowLessThan or Op.RowGreaterOrEqual or Op.RowLessOrEqual + => (Where(E("Any", [Element], tags, Expression.Lambda(RowValue(@case.Op, read, Expression.Property(row, "Id"), Captured(type), Captured(typeof(int), 0)), tag))), false), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + } + + private (Expression, bool) OverParameter(Expression source) + { + var row = Expression.Parameter(Source, "r"); + var item = Expression.Parameter(Element, "i"); + var list = Captured(typeof(List<>).MakeGenericType(Element), List(Element, valueObject ? Kind.Wrap(Kind.Values[1]) : Kind.Values[1])); + var column = Read(Expression.Property(row, "P")); + var read = Read(item); + + Expression comparison = @case.Op switch + { + Op.AnyEquals => Expression.Equal(read, column), + Op.AnyGreaterOrEqual => Expression.GreaterThanOrEqual(read, column), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + var where = Q("Where", [Source], source, Expression.Lambda(E("Any", [Element], list, Expression.Lambda(comparison, item)), row)); + return (Q("Select", [Source, typeof(int)], where, Expression.Lambda(Expression.Property(row, "Id"), row)), false); + } + + private (Expression, bool) OverNavigation(Expression source) + { + var bag = Expression.Parameter(Source, "b"); + var row = Expression.Parameter(Row, "x"); + var rows = Expression.Property(bag, "Rows"); + var read = Read(Expression.Property(row, "P")); + var type = read.Type; + var key = Expression.Lambda(read, row); + + Expression Select(Expression value) => Q("Select", [Source, value.Type], source, Expression.Lambda(value, bag)); + + return @case.Op switch + { + Op.AnyEquals => + (Q("Select", [Source, typeof(int)], Q("Where", [Source], source, Expression.Lambda(E("Any", [Row], rows, Expression.Lambda(Expression.Equal(read, Captured(type)), row)), bag)), + Expression.Lambda(Expression.Property(bag, "Id"), bag)), false), + Op.CountGreaterOrEqual => (Select(E("Count", [Row], rows, Expression.Lambda(Expression.GreaterThanOrEqual(read, Captured(type)), row))), false), + Op.NavigationMin => (Select(E("Min", [Row, type], rows, key)), false), + Op.NavigationMax => (Select(E("Max", [Row, type], rows, key)), false), + Op.NavigationOrderFirst => (Select(E("First", [type], E("Select", [Row, type], E("OrderBy", [Row, type], rows, key), key))), false), + Op.NavigationOrderDescendingFirst => (Select(E("First", [type], E("Select", [Row, type], E("OrderByDescending", [Row, type], rows, key), key))), false), + _ => throw new ArgumentOutOfRangeException(nameof(@case), @case, null), + }; + } + + /// <summary>A list of one value, as <paramref name="type"/>.</summary> + private static object List(Type type, object value) + { + var list = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type))!; + list.Add(value); + return list; + } + + private static Expression Captured(Type type, object value) => + Expression.Field(Expression.Constant(Activator.CreateInstance(typeof(Box<>).MakeGenericType(type), value)), "Value"); + + private static Expression Q(string name, Type[] types, params Expression[] arguments) => + Expression.Call(typeof(Queryable), name, types, [.. arguments.Select(argument => argument is LambdaExpression lambda ? Expression.Quote(lambda) : argument)]); + + private static Expression E(string name, Type[] types, params Expression[] arguments) => Expression.Call(typeof(Enumerable), name, types, arguments); +} + +/// <summary>The rows every query of a kind reads: three, in an order no query states, one with no optional value.</summary> +public static class Seed +{ + private static readonly int[] Order = [1, 2, 0]; + + private static object Value(WrappedKind kind, bool valueObject, int index) + { + var wrapped = kind.Values[index % kind.Values.Length]; + return valueObject ? kind.Wrap(wrapped) : wrapped; + } + + /// <summary>The entities of the case's source, with their navigations, as objects.</summary> + public static System.Collections.IList Entities(QueryBuilder builder, WrappedKind kind, bool valueObject) + { + var entities = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(builder.Source))!; + if (builder.Source == builder.Row) + { + foreach (var bag in Graph(builder, kind, valueObject)) + foreach (var row in (System.Collections.IList)bag.GetType().GetProperty("Rows")!.GetValue(bag)!) + entities.Add(row); + var ordered = entities.Cast<object>().OrderBy(row => (int)row.GetType().GetProperty("Id")!.GetValue(row)!).ToList(); + entities.Clear(); + foreach (var row in ordered) entities.Add(row); + } + else if (builder.Source == builder.Bag) + { + foreach (var bag in Graph(builder, kind, valueObject)) entities.Add(bag); + } + else if (builder.Source == builder.ElementRow) + { + int[][] tags = [[1, 2], [0], [2, 0, 1]]; + for (var i = 0; i < tags.Length; i++) + { + var entity = Activator.CreateInstance(builder.ElementRow)!; + Set(entity, "Id", i + 1); + var list = (System.Collections.IList)entity.GetType().GetProperty("Tags")!.GetValue(entity)!; + foreach (var index in tags[i]) list.Add(Value(kind, valueObject, index)); + entities.Add(entity); + } + } + else + { + for (var i = 0; i < Order.Length; i++) + { + var entity = Activator.CreateInstance(builder.DocRow)!; + Set(entity, "Id", i + 1); + Set(entity.GetType().GetProperty("Doc")!.GetValue(entity)!, "P", Value(kind, valueObject, Order[i])); + entities.Add(entity); + } + } + return entities; + } + + /// <summary>What a database is seeded with: the case's own entities, or the bags that hold the rows.</summary> + public static IEnumerable<object> Stored(QueryBuilder builder, WrappedKind kind, bool valueObject) => + builder.Source == builder.Row || builder.Source == builder.Bag ? Graph(builder, kind, valueObject) : Entities(builder, kind, valueObject).Cast<object>(); + + private static List<object> Graph(QueryBuilder builder, WrappedKind kind, bool valueObject) + { + var rows = Rows(builder, kind, valueObject); + var bags = new List<object>(); + foreach (var id in new[] { 1, 2 }) + { + var bag = Activator.CreateInstance(builder.Bag)!; + Set(bag, "Id", id); + var children = (System.Collections.IList)bag.GetType().GetProperty("Rows")!.GetValue(bag)!; + foreach (var row in rows.Where(row => (int)row.GetType().GetProperty("BagId")!.GetValue(row)! == id)) children.Add(row); + bags.Add(bag); + } + return bags; + } + + private static List<object> Rows(QueryBuilder builder, WrappedKind kind, bool valueObject) + { + int?[] optional = [2, null, 0]; + var rows = new List<object>(); + for (var i = 0; i < Order.Length; i++) + { + var row = Activator.CreateInstance(builder.Row)!; + Set(row, "Id", i + 1); + Set(row, "P", Value(kind, valueObject, Order[i])); + Set(row, "N", optional[i] is { } index ? Value(kind, valueObject, index) : null); + Set(row, "BagId", i == 1 ? 2 : 1); + rows.Add(row); + } + return rows; + } + + private static void Set(object target, string property, object? value) => target.GetType().GetProperty(property)!.SetValue(target, value); +} + +/// <summary>A model of one kind's entities for the catalogue, cached by <see cref="Key"/>.</summary> +public sealed record QueryModel(string Key, QueryCase Case, bool ValueObject); + +public sealed class QueryDb(DbContextOptions<QueryDb> options, QueryModel model) : DbContext(options) +{ + public QueryModel Descriptor => model; + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + if (model.ValueObject && model.Case.Conversion == Conversion.ForeignConvention) + configurationBuilder.Properties(model.Case.Kind.ValueObject).HaveConversion(PrefixedText.ConverterType(model.Case.Kind)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + var builder = new QueryBuilder(model.Case, model.ValueObject); + + if (builder.Source == builder.ElementRow) + { + modelBuilder.Entity(builder.ElementRow).ToTable("ElementRow").Property("Id").ValueGeneratedNever(); + return; + } + + if (builder.Source == builder.DocRow) + { + var doc = modelBuilder.Entity(builder.DocRow).ToTable("DocRow"); + doc.Property("Id").ValueGeneratedNever(); + doc.OwnsOne(typeof(Doc<>).MakeGenericType(builder.Element), "Doc", owned => owned.ToJson().Property("P").IsRequired()); + return; + } + + // Required on both sides: EF reads a generic T as nullable where T is a class, and a struct value object never is. + var row = modelBuilder.Entity(builder.Row).ToTable("Row"); + row.Property("Id").ValueGeneratedNever(); + row.Property("P").IsRequired(); + var bag = modelBuilder.Entity(builder.Bag).ToTable("Bag"); + bag.Property("Id").ValueGeneratedNever(); + bag.HasMany(builder.Row, "Rows").WithOne().HasForeignKey("BagId"); + + if (model.ValueObject) + foreach (var property in new[] { "P", "N" }) + switch (model.Case.Conversion) + { + case Conversion.ExplicitTypeKit: + row.Property(property).HasConversion(typeof(ValueObjectConverter<,>).MakeGenericType(builder.Element, model.Case.Kind.Wrapped)); + break; + case Conversion.ForeignLambda: + row.Property(property).HasConversion(PrefixedText.Converter(model.Case.Kind)); + break; + case Conversion.ForeignConverterType: + row.Property(property).HasConversion(PrefixedText.ConverterType(model.Case.Kind)); + break; + } + + if (!model.Case.IsFilter) return; + + var filtered = modelBuilder.Entity(builder.Source); + if (model.Case.Site == Site.NamedQueryFilter) filtered.HasQueryFilter("Variant", builder.Filter()); + else filtered.HasQueryFilter(builder.Filter()); + } + + public static QueryDb Create(QueryModel model, Microsoft.Data.Sqlite.SqliteConnection connection) => + Create(model, new DbContextOptionsBuilder<QueryDb>().UseSqlite(connection)); + + /// <summary>For SQL text only: <c>ToQueryString</c> translates without opening the connection.</summary> + public static QueryDb OnPostgreSql(QueryModel model) => + Create(model, new DbContextOptionsBuilder<QueryDb>().UseNpgsql("Host=localhost;Database=none")); + + private static QueryDb Create(QueryModel model, DbContextOptionsBuilder<QueryDb> provider) + { + var builder = provider.ReplaceService<IModelCacheKeyFactory, QueryModelCacheKey>(); + if (model.ValueObject) builder = builder.UseTypeKit(); + + return new QueryDb(builder.Options, model); + } +} + +public sealed class QueryModelCacheKey : IModelCacheKeyFactory +{ + public object Create(DbContext context, bool designTime) => + context is QueryDb db ? (db.GetType(), db.Descriptor.Key, designTime) : (context.GetType(), designTime); +} + +/// <summary>A converter of the application's own: it stores <c>"C:" + value</c>, which is not what <c>.Value</c> answers.</summary> +public static class PrefixedText +{ + public static Type ConverterType(WrappedKind kind) => typeof(PrefixedTextConverter<,>).MakeGenericType(kind.ValueObject, kind.Wrapped); + + public static ValueConverter Converter(WrappedKind kind) => + (ValueConverter)Activator.CreateInstance(typeof(ValueConverter<,>).MakeGenericType(kind.ValueObject, typeof(string)), + Expressions(kind.ValueObject, kind.Wrapped, out var fromProvider), fromProvider, null)!; + + internal static LambdaExpression Expressions(Type valueObject, Type wrapped, out LambdaExpression fromProvider) + { + var instance = Expression.Parameter(valueObject, "v"); + var text = Expression.Parameter(typeof(string), "s"); + var write = typeof(PrefixedText).GetMethod(nameof(Write))!.MakeGenericMethod(wrapped); + var read = typeof(PrefixedText).GetMethod(nameof(Read))!.MakeGenericMethod(wrapped); + var from = valueObject.GetMethod("From", [wrapped])!; + fromProvider = Expression.Lambda(Expression.Call(from, Expression.Call(read, text)), text); + return Expression.Lambda(Expression.Call(write, Expression.Property(instance, "Value")), instance); + } + + public static string Write<T>(T value) => "C:" + Convert.ToString(value, CultureInfo.InvariantCulture); + + public static T Read<T>(string text) => (T)Convert.ChangeType(text[2..], typeof(T), CultureInfo.InvariantCulture); +} + +public sealed class PrefixedTextConverter<TValueObject, TWrapped>() : ValueConverter<TValueObject, string>(ToProvider, FromProvider) +{ + private static readonly Expression<Func<TValueObject, string>> ToProvider = + (Expression<Func<TValueObject, string>>)PrefixedText.Expressions(typeof(TValueObject), typeof(TWrapped), out var _); + + private static readonly Expression<Func<string, TValueObject>> FromProvider = Build(); + + private static Expression<Func<string, TValueObject>> Build() + { + PrefixedText.Expressions(typeof(TValueObject), typeof(TWrapped), out var fromProvider); + return (Expression<Func<string, TValueObject>>)fromProvider; + } +} diff --git a/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/WrappedKinds.cs b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/WrappedKinds.cs new file mode 100644 index 00000000..63583190 --- /dev/null +++ b/test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/Variants/WrappedKinds.cs @@ -0,0 +1,185 @@ +using System.Globalization; +using System.Reflection; +using CodoMetis.TypeKit.ValueObjects; + +namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests.Variants; + +/// <summary>How a value object orders: as what it wraps, or by a <c>CompareTo(TSelf)</c> of its own.</summary> +public enum Ordering +{ + Wrapped, + + /// <summary>Declares the comparison seam, descending, so a database that orders the column instead is seen.</summary> + HandWritten, +} + +/// <summary> +/// One type a value object wraps, as the EF satellite meets it: the value object, the type itself, how +/// the value object orders, and three distinct values in ascending order. +/// </summary> +/// <remarks> +/// The values are chosen so that a column read as text orders differently from the type (<c>10</c> +/// before <c>2.25</c>, <c>1.02:00:00</c> before <c>10:00:00</c>): a converter that stores another +/// representation, or a provider that orders the stored text, cannot answer like memory by accident. +/// </remarks> +public sealed record WrappedKind(string Name, Type ValueObject, Type Wrapped, Ordering Ordering, object[] Values) +{ + public override string ToString() => Name; + + /// <summary>The value object over <paramref name="wrapped"/>, through its generated <c>From</c>.</summary> + public object Wrap(object wrapped) => ValueObject.GetMethod("From", BindingFlags.Public | BindingFlags.Static, [Wrapped])!.Invoke(null, [wrapped])!; + + /// <summary>The generated <c>GetValue()</c> or <c>ValueOrNull()</c> of the value object's companion class.</summary> + public MethodInfo Companion(string name) => + ValueObject.Assembly.GetType($"{ValueObject.Namespace}.{ValueObject.Name}Extensions")?.GetMethod(name, BindingFlags.Public | BindingFlags.Static) + ?? throw new InvalidOperationException($"{ValueObject.Name} has no generated {name}()"); + + /// <summary>Whether C# compares two values of the wrapped type with <c><</c> and <c>></c>.</summary> + public bool HasComparisonOperators + { + get + { + try + { + System.Linq.Expressions.Expression.GreaterThanOrEqual(System.Linq.Expressions.Expression.Default(Wrapped), System.Linq.Expressions.Expression.Default(Wrapped)); + return true; + } + catch (InvalidOperationException) + { + return false; + } + } + } + + /// <summary>A key or a value as comparable text: a value object by what it wraps.</summary> + public static string Text(object? value) => value switch + { + null => "<null>", + DateTime dateTime => dateTime.ToString("O", CultureInfo.InvariantCulture), + DateTimeOffset dateTimeOffset => dateTimeOffset.ToString("O", CultureInfo.InvariantCulture), + _ when IsValueObject(value.GetType()) is { } valueProperty => Text(valueProperty.GetValue(value)), + _ => Convert.ToString(value, CultureInfo.InvariantCulture) ?? "<null>", + }; + + private static PropertyInfo? IsValueObject(Type type) => + type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IValue<>)) ? type.GetProperty("Value") : null; +} + +/// <summary>The wrapped types of the EF catalogue, one value object each.</summary> +/// <remarks> +/// Every integer type EF generates a key of, and <c>byte</c>, which it does not; <c>char</c> and <c>Guid</c>, +/// the two documented exceptions to key parity; the types a key can be and is never generated; and the +/// families a query reads: text, numbers, a flag, dates and times, an enum, and a record class over text and +/// over a number, whose generated operators sort null first. A value object ordered by a +/// <c>CompareTo</c> of its own is a kind too, as a struct over a number and a date and as a record class. +/// </remarks> +public static class WrappedKinds +{ + public static readonly WrappedKind Int32 = new("int", typeof(WrapsInt32), typeof(int), Ordering.Wrapped, [2, 7, 10]); + public static readonly WrappedKind Int64 = new("long", typeof(WrapsInt64), typeof(long), Ordering.Wrapped, [2L, 7L, 10L]); + public static readonly WrappedKind Int16 = new("short", typeof(WrapsInt16), typeof(short), Ordering.Wrapped, [(short)2, (short)7, (short)10]); + public static readonly WrappedKind SByte = new("sbyte", typeof(WrapsSByte), typeof(sbyte), Ordering.Wrapped, [(sbyte)-3, (sbyte)2, (sbyte)10]); + public static readonly WrappedKind Byte = new("byte", typeof(WrapsByte), typeof(byte), Ordering.Wrapped, [(byte)2, (byte)7, (byte)10]); + public static readonly WrappedKind UInt16 = new("ushort", typeof(WrapsUInt16), typeof(ushort), Ordering.Wrapped, [(ushort)2, (ushort)7, (ushort)10]); + public static readonly WrappedKind UInt32 = new("uint", typeof(WrapsUInt32), typeof(uint), Ordering.Wrapped, [2u, 7u, 10u]); + public static readonly WrappedKind UInt64 = new("ulong", typeof(WrapsUInt64), typeof(ulong), Ordering.Wrapped, [2ul, 7ul, 10ul]); + public static readonly WrappedKind Char = new("char", typeof(WrapsChar), typeof(char), Ordering.Wrapped, ['a', 'b', 'c']); + public static readonly WrappedKind Guid = new("Guid", typeof(WrapsGuid), typeof(Guid), Ordering.Wrapped, + [System.Guid.Parse("00000000-0000-0000-0000-000000000002"), System.Guid.Parse("00000000-0000-0000-0000-000000000007"), System.Guid.Parse("00000000-0000-0000-0000-000000000010")]); + public static readonly WrappedKind String = new("string", typeof(WrapsString), typeof(string), Ordering.Wrapped, ["alpha", "bravo", "charlie"]); + public static readonly WrappedKind StringRecordClass = new("string, record class", typeof(WrapsStringRecord), typeof(string), Ordering.Wrapped, ["alpha", "bravo", "charlie"]); + public static readonly WrappedKind Int32RecordClass = new("int, record class", typeof(WrapsInt32Record), typeof(int), Ordering.Wrapped, [2, 7, 10]); + public static readonly WrappedKind Decimal = new("decimal", typeof(WrapsDecimal), typeof(decimal), Ordering.Wrapped, [1.5m, 2.25m, 10m]); + public static readonly WrappedKind Double = new("double", typeof(WrapsDouble), typeof(double), Ordering.Wrapped, [1.5, 2.25, 10.0]); + public static readonly WrappedKind Boolean = new("bool", typeof(WrapsBoolean), typeof(bool), Ordering.Wrapped, [false, true]); + public static readonly WrappedKind DateTime = new("DateTime", typeof(WrapsDateTime), typeof(DateTime), Ordering.Wrapped, + [new DateTime(2026, 1, 2), new DateTime(2026, 1, 10), new DateTime(2026, 11, 1)]); + public static readonly WrappedKind DateOnly = new("DateOnly", typeof(WrapsDateOnly), typeof(DateOnly), Ordering.Wrapped, + [new DateOnly(2026, 1, 2), new DateOnly(2026, 1, 10), new DateOnly(2026, 11, 1)]); + public static readonly WrappedKind TimeOnly = new("TimeOnly", typeof(WrapsTimeOnly), typeof(TimeOnly), Ordering.Wrapped, + [new TimeOnly(2, 0), new TimeOnly(9, 30), new TimeOnly(10, 0)]); + public static readonly WrappedKind DateTimeOffset = new("DateTimeOffset", typeof(WrapsDateTimeOffset), typeof(DateTimeOffset), Ordering.Wrapped, + [new DateTimeOffset(2026, 1, 2, 0, 0, 0, System.TimeSpan.Zero), new DateTimeOffset(2026, 1, 10, 0, 0, 0, System.TimeSpan.Zero), new DateTimeOffset(2026, 11, 1, 0, 0, 0, System.TimeSpan.Zero)]); + public static readonly WrappedKind TimeSpan = new("TimeSpan", typeof(WrapsTimeSpan), typeof(TimeSpan), Ordering.Wrapped, + [System.TimeSpan.FromHours(2), System.TimeSpan.FromHours(10), System.TimeSpan.FromHours(26)]); + public static readonly WrappedKind Enum = new("enum", typeof(WrapsEnum), typeof(DayOfWeek), Ordering.Wrapped, [DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday]); + + public static readonly WrappedKind DescendingInt32 = new("int, own CompareTo", typeof(DescendingInt32), typeof(int), Ordering.HandWritten, [2, 7, 10]); + public static readonly WrappedKind DescendingDateOnly = new("DateOnly, own CompareTo", typeof(DescendingDateOnly), typeof(DateOnly), Ordering.HandWritten, + [new DateOnly(2026, 1, 2), new DateOnly(2026, 1, 10), new DateOnly(2026, 11, 1)]); + public static readonly WrappedKind DescendingStringRecordClass = new("string, record class, own CompareTo", typeof(DescendingStringRecord), typeof(string), Ordering.HandWritten, + ["alpha", "bravo", "charlie"]); + + /// <summary>What a key can wrap: every integer type, the two exceptions, and types EF never generates a key of.</summary> + public static readonly WrappedKind[] Keys = [Int32, Int64, Int16, SByte, Byte, UInt16, UInt32, UInt64, Char, Guid, String, Decimal, DateTime]; + + /// <summary>What a query reads: one kind per family, the unsigned and the text-stored ones included, and the three own orderings.</summary> + public static readonly WrappedKind[] Queries = + [ + Int32, Int64, Int16, Byte, UInt32, UInt64, Char, Guid, String, StringRecordClass, Int32RecordClass, Decimal, Double, Boolean, + DateTime, DateOnly, TimeOnly, DateTimeOffset, TimeSpan, Enum, + DescendingInt32, DescendingDateOnly, DescendingStringRecordClass, + ]; + + public static WrappedKind Named(string name) => Keys.Concat(Queries).First(kind => kind.Name == name); +} + +public readonly partial record struct WrapsInt32 : IValue<int>; + +public readonly partial record struct WrapsInt64 : IValue<long>; + +public readonly partial record struct WrapsInt16 : IValue<short>; + +public readonly partial record struct WrapsSByte : IValue<sbyte>; + +public readonly partial record struct WrapsByte : IValue<byte>; + +public readonly partial record struct WrapsUInt16 : IValue<ushort>; + +public readonly partial record struct WrapsUInt32 : IValue<uint>; + +public readonly partial record struct WrapsUInt64 : IValue<ulong>; + +public readonly partial record struct WrapsChar : IValue<char>; + +public readonly partial record struct WrapsGuid : IValue<Guid>; + +public readonly partial record struct WrapsString : IValue<string>; + +public sealed partial record WrapsStringRecord : IValue<string>; + +public sealed partial record WrapsInt32Record : IValue<int>; + +public readonly partial record struct WrapsDecimal : IValue<decimal>; + +public readonly partial record struct WrapsDouble : IValue<double>; + +public readonly partial record struct WrapsBoolean : IValue<bool>; + +public readonly partial record struct WrapsDateTime : IValue<DateTime>; + +public readonly partial record struct WrapsDateOnly : IValue<DateOnly>; + +public readonly partial record struct WrapsTimeOnly : IValue<TimeOnly>; + +public readonly partial record struct WrapsDateTimeOffset : IValue<DateTimeOffset>; + +public readonly partial record struct WrapsTimeSpan : IValue<TimeSpan>; + +public readonly partial record struct WrapsEnum : IValue<DayOfWeek>; + +/// <summary>Orders descending by its own <c>CompareTo</c>, as <c>ProbeDescending</c> does.</summary> +public readonly partial record struct DescendingInt32 : IValue<int> +{ + public int CompareTo(DescendingInt32 other) => other.Value.CompareTo(Value); +} + +public readonly partial record struct DescendingDateOnly : IValue<DateOnly> +{ + public int CompareTo(DescendingDateOnly other) => other.Value.CompareTo(Value); +} + +public sealed partial record DescendingStringRecord : IValue<string> +{ + public int CompareTo(DescendingStringRecord? other) => other is null ? 1 : string.CompareOrdinal(other.Value, Value); +} diff --git a/test/CodoMetis.TypeKit.Facts/CodoMetis.TypeKit.Facts.csproj b/test/CodoMetis.TypeKit.Facts/CodoMetis.TypeKit.Facts.csproj index fee66b44..20de5a52 100644 --- a/test/CodoMetis.TypeKit.Facts/CodoMetis.TypeKit.Facts.csproj +++ b/test/CodoMetis.TypeKit.Facts/CodoMetis.TypeKit.Facts.csproj @@ -18,6 +18,18 @@ <FrameworkReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" /> + <!-- + The PostgreSQL facts, with plain EF Core and Npgsql; they run where TYPEKIT_PG names a server (PgFactHost). + Relational is named so that EF Core is the version the facts pin, not the older floor the provider brings. + --> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> + </ItemGroup> + + <!-- A fact's measured answers as data, beside its test (FactFixture, design/adr/0107), read from the test output. --> + <ItemGroup> + <None Update="KB*.fixture.json" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> <ItemGroup> diff --git a/test/CodoMetis.TypeKit.Facts/FactFixture.cs b/test/CodoMetis.TypeKit.Facts/FactFixture.cs new file mode 100644 index 00000000..e3bbe587 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/FactFixture.cs @@ -0,0 +1,191 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// A fact's measured answers as data (design/adr/0107): <c>KBNNN.fixture.json</c> beside the fact's test <c>KBNNN.cs</c>, +/// named by the fact's <c>fixture:</c> line. The test measures what the framework does and asserts it is still each +/// answer of the fixture; the probe harness reads the same answers as a baseline, <c>Baseline.Fact("KB-NNN")</c>, in +/// place of starting a host for the framework's side. +/// +/// <para> +/// The rule the harness follows when it reads a fixture as <c>Baseline.Fact</c>: +/// </para> +/// <list type="number"> +/// <item>It reads only a fixture that a fact's <c>fixture:</c> line names, which <c>docs-query check</c> allows only for +/// a fact with a test (<c>fact-fixture</c>, <c>fact-not-executable-cited</c>), and only while the check is clean, so a +/// fact whose pins fell behind what is installed (<c>fact-behind</c>) is no baseline until its test passed again.</item> +/// <item>It takes the one answer that <see cref="FactAnswer.Covers"/> the row: every setting the answer's <c>when</c> +/// names is the row's, axis for axis (<c>openapi.OpenApiVersion</c>, <c>http.binding</c>, ...). A setting the row does +/// not have is not covered. No answer covers it: the fact says nothing about this row, and the harness measures the +/// framework's side itself. More than one: the fixture is ambiguous, and the row ends as a harness error.</item> +/// <item>It compares by the answer's kind: a <c>document</c> at its <c>at</c> (a JSON pointer) byte for byte in its +/// compact form, a <c>status</c> and, where the answer has one, its body, or an <c>exception</c> by its type and a part +/// of its message, at the operation and stage the answer names (<see cref="FactAnswer.Mismatch(JsonNode)"/> and its +/// overloads). A mismatch is the product's difference from the framework, as a twin's answer would be.</item> +/// <item>It never writes a fixture. An answer is changed by changing the fact: its test, its fixture and its entry, in +/// one commit.</item> +/// </list> +/// +/// BCL only and nothing of the test framework, so that the harness can compile this file as it is. +/// </summary> +public sealed class FactFixture +{ + public const string Suffix = ".fixture.json"; + + private FactFixture(string fact, IReadOnlyList<FactAnswer> answers) => (Fact, Answers) = (fact, answers); + + /// <summary>The fact's id, <c>KB-001</c>.</summary> + public string Fact { get; } + + public IReadOnlyList<FactAnswer> Answers { get; } + + /// <summary>The answer of that id; an id the fixture does not have throws.</summary> + public FactAnswer this[string id] => + Answers.FirstOrDefault(answer => answer.Id == id) ?? throw new KeyNotFoundException($"{Fact}'s fixture has no answer '{id}'"); + + /// <summary>The file of a fact's fixture: <c>KB001.fixture.json</c> for <c>KB-001</c>.</summary> + public static string FileName(string fact) => fact.Replace("-", "", StringComparison.Ordinal) + Suffix; + + /// <summary>The fixture as the facts project's build copies it beside its tests.</summary> + public static FactFixture Beside(string fact) => Load(Path.Combine(AppContext.BaseDirectory, FileName(fact))); + + /// <summary>The one answer that covers a row's settings, or null where none does; more than one throws.</summary> + public FactAnswer? For(IReadOnlyDictionary<string, string> settings) + { + var covering = Answers.Where(answer => answer.Covers(settings)).ToList(); + return covering.Count <= 1 ? covering.SingleOrDefault() + : throw new InvalidOperationException($"{Fact}'s fixture is ambiguous for these settings: {string.Join(", ", covering.Select(answer => answer.Id))} each cover them"); + } + + /// <summary>Reads and checks a fixture: its fact is the one its file is named for, and each answer has an id of its own and the fields its kind needs.</summary> + public static FactFixture Load(string path) + { + var name = Path.GetFileName(path); + if (JsonNode.Parse(File.ReadAllText(path)) is not JsonObject root) throw Invalid(name, "is not a JSON object"); + + var fact = (string?)root["fact"] ?? throw Invalid(name, "names no \"fact\""); + if (FileName(fact) != name) throw Invalid(name, $"names the fact {fact}, whose fixture is {FileName(fact)}"); + + var answers = new List<FactAnswer>(); + foreach (var node in root["answers"] as JsonArray ?? throw Invalid(name, "has no \"answers\" array")) + { + if (node is not JsonObject entry) throw Invalid(name, "has an answer that is not an object"); + + var id = (string?)entry["id"] ?? throw Invalid(name, "has an answer without an \"id\""); + var kind = (string?)entry["kind"] ?? throw Invalid(name, $"answer {id} has no \"kind\""); + var when = (entry["when"] as JsonObject ?? []).ToDictionary(kv => kv.Key, kv => (string?)kv.Value ?? throw Invalid(name, $"answer {id}'s when {kv.Key} is no string"), StringComparer.Ordinal); + var at = (string?)entry["at"]; + var value = entry["value"] ?? throw Invalid(name, $"answer {id} has no \"value\""); + + if (answers.Any(answer => answer.Id == id)) throw Invalid(name, $"lists the answer {id} twice"); + switch (kind) + { + case FactAnswer.Document when at is null || (at.Length > 0 && at[0] != '/'): + throw Invalid(name, $"answer {id} is a document and needs \"at\", a JSON pointer such as /paths/~1a/get"); + case FactAnswer.Status when value["status"] is not JsonValue status || !status.TryGetValue<int>(out _): + throw Invalid(name, $"answer {id} is a status and needs a \"status\" number"); + case FactAnswer.Exception when (string?)value["type"] is null: + throw Invalid(name, $"answer {id} is an exception and needs its \"type\""); + case FactAnswer.Document or FactAnswer.Status or FactAnswer.Exception: + break; + default: + throw Invalid(name, $"answer {id}'s kind '{kind}' is none of document, status and exception"); + } + + answers.Add(new FactAnswer(id, kind, when, at, value.DeepClone(), (string?)entry["note"])); + } + + return new FactFixture(fact, answers); + } + + private static InvalidDataException Invalid(string name, string why) => new($"{name} {why}"); +} + +/// <summary> +/// One measured answer of a fact: what the framework gave (<see cref="Value"/>) for the settings in <see cref="When"/>, +/// of one kind, <c>document</c>, <c>status</c> or <c>exception</c>, and a note saying what to read in it. +/// </summary> +public sealed class FactAnswer +{ + public const string Document = "document", Status = "status", Exception = "exception"; + + internal FactAnswer(string id, string kind, IReadOnlyDictionary<string, string> when, string? at, JsonNode value, string? note) => + (Id, Kind, When, At, Value, Note) = (id, kind, when, at, value, note); + + public string Id { get; } + + public string Kind { get; } + + /// <summary>The settings the answer was measured under, axis to value, as a row names them.</summary> + public IReadOnlyDictionary<string, string> When { get; } + + /// <summary>For a document, the JSON pointer of the part the answer is (RFC 6901: <c>~1</c> for <c>/</c>, <c>~0</c> for <c>~</c>).</summary> + public string? At { get; } + + public JsonNode Value { get; } + + public string? Note { get; } + + /// <summary>Every setting the answer names is the row's.</summary> + public bool Covers(IReadOnlyDictionary<string, string> settings) => + When.All(setting => settings.TryGetValue(setting.Key, out var value) && value == setting.Value); + + /// <summary>A document's difference from the answer at its pointer, or null where it is the same, byte for byte compact.</summary> + public string? Mismatch(JsonNode document) + { + Expect(Document); + var part = Resolve(document, At!); + var measured = part?.ToJsonString() ?? "(nothing)"; + var expected = Value.ToJsonString(); + return measured == expected ? null : $"{Id} at {At}: measured {measured}, the fixture says {expected}{Noted}"; + } + + /// <summary>A response's difference from the answer, or null: its status, and its body where the answer has one.</summary> + public string? Mismatch(int status, string body) + { + Expect(Status); + var expected = (int)Value["status"]!; + if (status != expected) return $"{Id}: measured status {status}, the fixture says {expected}{Noted}"; + return (string?)Value["body"] is { } text && text != body ? $"{Id}: measured body {body}, the fixture says {text}{Noted}" : null; + } + + /// <summary>A thrown exception's difference from the answer, or null: its type's full name, and a part of its message where the answer has one.</summary> + public string? Mismatch(System.Exception? thrown) + { + Expect(Exception); + var type = (string)Value["type"]!; + if (thrown is null) return $"{Id}: nothing was thrown, the fixture says {type} at {Value["operation"]}, {Value["stage"]}{Noted}"; + if (thrown.GetType().FullName != type) return $"{Id}: measured {thrown.GetType().FullName}, the fixture says {type}{Noted}"; + return (string?)Value["message"] is { } part && !thrown.Message.Contains(part, StringComparison.Ordinal) + ? $"{Id}: the message '{thrown.Message}' does not say '{part}'{Noted}" + : null; + } + + /// <summary>The node a JSON pointer names, or null where the document has none.</summary> + public static JsonNode? Resolve(JsonNode document, string pointer) + { + var node = (JsonNode?)document; + foreach (var token in pointer.Length == 0 ? [] : pointer[1..].Split('/')) + { + var key = token.Replace("~1", "/", StringComparison.Ordinal).Replace("~0", "~", StringComparison.Ordinal); + node = node switch + { + JsonObject o => o[key], + JsonArray a when int.TryParse(key, System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out var i) && i < a.Count => a[i], + _ => null, + }; + if (node is null) return null; + } + + return node; + } + + private string Noted => Note is null ? "" : $" ({Note})"; + + private void Expect(string kind) + { + if (Kind != kind) throw new InvalidOperationException($"{Id} is a {Kind}, not a {kind}"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/FactHost.cs b/test/CodoMetis.TypeKit.Facts/FactHost.cs index 3b2a9d68..120c4a32 100644 --- a/test/CodoMetis.TypeKit.Facts/FactHost.cs +++ b/test/CodoMetis.TypeKit.Facts/FactHost.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -17,8 +18,12 @@ namespace CodoMetis.TypeKit.Facts; /// </summary> internal static class FactHost { - /// <summary>The document of a host whose only controllers are <paramref name="controllers"/>.</summary> - public static async Task<JsonNode> DocumentAsync(OpenApiSpecVersion version, Type[] controllers, Action<MvcOptions>? mvc = null) + /// <summary> + /// The document of a host whose only controllers are <paramref name="controllers"/>, and whose only minimal + /// endpoints are those <paramref name="minimal"/> maps. + /// </summary> + public static async Task<JsonNode> DocumentAsync( + OpenApiSpecVersion version, Type[] controllers, Action<MvcOptions>? mvc = null, Action<IEndpointRouteBuilder>? minimal = null) { var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = Environments.Production }); builder.WebHost.UseTestServer(); @@ -33,6 +38,7 @@ public static async Task<JsonNode> DocumentAsync(OpenApiSpecVersion version, Typ await using var app = builder.Build(); app.MapControllers(); + minimal?.Invoke(app); app.MapOpenApi(); await app.StartAsync(TestContext.Current.CancellationToken); diff --git a/test/CodoMetis.TypeKit.Facts/FixtureTests.cs b/test/CodoMetis.TypeKit.Facts/FixtureTests.cs new file mode 100644 index 00000000..c5b146aa --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/FixtureTests.cs @@ -0,0 +1,97 @@ +using System.Reflection; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// The fixtures beside the facts' tests (design/adr/0107): each one the build copies loads, names the fact its file is +/// named for, and has that fact's test class here; the reader the harness will use covers, compares and refuses as +/// <see cref="FactFixture"/> says. +/// </summary> +public sealed class FixtureTests +{ + public static TheoryData<string> Fixtures() => + [.. Directory.EnumerateFiles(AppContext.BaseDirectory, "KB*" + FactFixture.Suffix).Select(path => Path.GetFileName(path)).Order(StringComparer.Ordinal)]; + + /// <summary>A floor, so the theory below cannot pass on a build that copied no fixture.</summary> + [Fact] + public void The_fixtures_are_copied_beside_the_tests() => Fixtures().Count.ShouldBeGreaterThanOrEqualTo(4); + + [Theory, MemberData(nameof(Fixtures))] + public void A_fixture_loads_and_its_fact_has_a_test_here(string file) + { + var fixture = FactFixture.Load(Path.Combine(AppContext.BaseDirectory, file)); + + fixture.Answers.ShouldNotBeEmpty(); + typeof(FixtureTests).Assembly.GetType($"{typeof(FixtureTests).Namespace}.{fixture.Fact.Replace("-", "", StringComparison.Ordinal)}").ShouldNotBeNull(); + } + + private static FactFixture Write(string json) + { + var path = Path.Combine(Path.GetTempPath(), $"fixture-{Guid.NewGuid():N}", "KB099.fixture.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, json); + try { return FactFixture.Load(path); } + finally { Directory.Delete(Path.GetDirectoryName(path)!, recursive: true); } + } + + private const string Two = """ + { "fact": "KB-099", "answers": [ + { "id": "a", "kind": "document", "when": { "x": "1" }, "at": "/p/~1q/0", "value": { "v": [1, null] }, "note": "the note" }, + { "id": "b", "kind": "status", "when": { "x": "2", "y": "1" }, "value": { "status": 200, "body": "0" } }, + { "id": "c", "kind": "exception", "when": { "x": "2" }, "value": { "type": "System.InvalidOperationException", "message": "depth '1'" } } + ] } + """; + + [Fact] + public void An_answer_covers_a_row_that_holds_every_setting_it_names() + { + var fixture = Write(Two); + + fixture.For(new Dictionary<string, string> { ["x"] = "1", ["z"] = "9" })!.Id.ShouldBe("a"); + fixture.For(new Dictionary<string, string> { ["x"] = "3" }).ShouldBeNull("no answer covers it: the harness measures the framework itself"); + Should.Throw<InvalidOperationException>(() => fixture.For(new Dictionary<string, string> { ["x"] = "2", ["y"] = "1" })) + .Message.ShouldBe("KB-099's fixture is ambiguous for these settings: b, c each cover them"); + } + + [Fact] + public void Each_kind_compares_what_it_holds() + { + var fixture = Write(Two); + + fixture["a"].Mismatch(JsonNode.Parse("""{ "p": { "/q": [{ "v": [1, null] }] } }""")!).ShouldBeNull(); + fixture["a"].Mismatch(JsonNode.Parse("""{ "p": { "/q": [{ "v": [1] }] } }""")!).ShouldBe("""a at /p/~1q/0: measured {"v":[1]}, the fixture says {"v":[1,null]} (the note)"""); + fixture["a"].Mismatch(JsonNode.Parse("{}")!).ShouldBe("""a at /p/~1q/0: measured (nothing), the fixture says {"v":[1,null]} (the note)"""); + fixture["b"].Mismatch(200, "0").ShouldBeNull(); + fixture["b"].Mismatch(400, "0").ShouldBe("b: measured status 400, the fixture says 200"); + fixture["b"].Mismatch(200, "5").ShouldBe("b: measured body 5, the fixture says 0"); + fixture["c"].Mismatch(new InvalidOperationException("exceeded depth '1' at V")).ShouldBeNull(); + fixture["c"].Mismatch(new ArgumentException("depth '1'")).ShouldBe("c: measured System.ArgumentException, the fixture says System.InvalidOperationException"); + fixture["c"].Mismatch((Exception?)null).ShouldStartWith("c: nothing was thrown"); + Should.Throw<InvalidOperationException>(() => fixture["b"].Mismatch(JsonNode.Parse("{}")!)); + } + + [Theory] + [InlineData("""{ "fact": "KB-098", "answers": [] }""", "KB099.fixture.json names the fact KB-098, whose fixture is KB098.fixture.json")] + [InlineData("""{ "fact": "KB-099", "answers": [{ "id": "a", "kind": "document", "value": 1 }] }""", "KB099.fixture.json answer a is a document and needs \"at\", a JSON pointer such as /paths/~1a/get")] + [InlineData("""{ "fact": "KB-099", "answers": [{ "id": "a", "kind": "status", "value": { "status": "200" } }] }""", "KB099.fixture.json answer a is a status and needs a \"status\" number")] + [InlineData("""{ "fact": "KB-099", "answers": [{ "id": "a", "kind": "exception", "value": {} }] }""", "KB099.fixture.json answer a is an exception and needs its \"type\"")] + [InlineData("""{ "fact": "KB-099", "answers": [{ "id": "a", "kind": "guess", "value": 1 }] }""", "KB099.fixture.json answer a's kind 'guess' is none of document, status and exception")] + [InlineData("""{ "fact": "KB-099", "answers": [{ "id": "a", "kind": "status", "value": { "status": 1 } }, { "id": "a", "kind": "status", "value": { "status": 1 } }] }""", "KB099.fixture.json lists the answer a twice")] + public void A_fixture_that_says_less_than_its_kind_needs_is_refused(string json, string message) => + Should.Throw<InvalidDataException>(() => Write(json)).Message.ShouldBe(message); + + /// <summary>The reader is meant to be compiled into the probe harness as it is: nothing of the test framework in it.</summary> + [Fact] + public void The_reader_needs_only_the_base_class_library() + { + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "CodoMetis.TypeKit.slnx"))) root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("no CodoMetis.TypeKit.slnx above the test output"); + + File.ReadAllLines(Path.Combine(root, "test", "CodoMetis.TypeKit.Facts", "FactFixture.cs")) + .Where(line => line.StartsWith("using ", StringComparison.Ordinal)) + .ShouldAllBe(line => line.StartsWith("using System", StringComparison.Ordinal)); + typeof(FactFixture).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) + .SelectMany(method => method.GetParameters().Select(p => p.ParameterType).Append(method.ReturnType)) + .ShouldAllBe(type => type.Namespace!.StartsWith("System", StringComparison.Ordinal) || type.Namespace == typeof(FactFixture).Namespace); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB001.cs b/test/CodoMetis.TypeKit.Facts/KB001.cs index c21d17ab..b0085cdf 100644 --- a/test/CodoMetis.TypeKit.Facts/KB001.cs +++ b/test/CodoMetis.TypeKit.Facts/KB001.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi; @@ -5,11 +7,14 @@ namespace CodoMetis.TypeKit.Facts; /// <summary> /// design/knowledge/KB-001: under OpenAPI 2.0, ASP.NET writes a nullable request body as -/// <c>allOf: [{x-nullable: true}]</c>, without the <c>pattern</c> the same type's non-nullable body has. +/// <c>allOf: [{x-nullable: true}]</c>, without the <c>pattern</c> the same type's non-nullable body has, and without +/// its <c>type</c>, in MVC and in minimal APIs alike. /// </summary> public sealed class KB001 { - private const string Int32Pattern = @"""pattern"":""^-?(?:0|[1-9]\\d*)$"""; + private static readonly FactFixture Fixture = FactFixture.Beside("KB-001"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; [ApiController] [Route("kb001")] @@ -22,31 +27,29 @@ public sealed class BodyController : ControllerBase public IActionResult Nullable([FromBody] int? value) => Ok(); } - private static async Task<JsonNode> BodyAsync(OpenApiSpecVersion version, string path) - { - var document = await FactHost.DocumentAsync(version, [typeof(BodyController)]); - var operation = document["paths"]![$"/kb001/{path}"]!["post"]!; + public enum Shade { Light, Dark } - return version == OpenApiSpecVersion.OpenApi2_0 - ? operation["parameters"]!.AsArray().Single(parameter => (string?)parameter!["in"] == "body")! - : operation["requestBody"]!["content"]!["application/json"]!; - } - - [Fact] - public async Task Under_OpenApi_2_0_a_nullable_int_body_is_allOf_x_nullable_without_its_pattern() + /// <summary> + /// The document an answer was measured in: the MVC host's, with a plain and a nullable <c>int</c> body, or the minimal + /// API's, with a nullable enum body, under the answer's OpenAPI version. + /// </summary> + private static Task<JsonNode> DocumentAsync(FactAnswer answer) { - var plain = await BodyAsync(OpenApiSpecVersion.OpenApi2_0, "plain"); - var nullable = await BodyAsync(OpenApiSpecVersion.OpenApi2_0, "nullable"); - - plain.ToJsonString().ShouldBe("""{"in":"body","name":"body","required":true,"schema":{"format":"int32",""" + Int32Pattern + "}}"); - nullable.ToJsonString().ShouldBe("""{"in":"body","name":"body","schema":{"format":"int32","allOf":[{"x-nullable":true}]}}""", "optional, and no pattern"); + var version = Enum.Parse<OpenApiSpecVersion>(answer.When["openapi.OpenApiVersion"]); + return answer.When["http.host"] == "minimal" + ? FactHost.DocumentAsync(version, [], minimal: app => app.MapPost("/kb001/minimal", ([FromBody] Shade? value) => Results.Ok())) + : FactHost.DocumentAsync(version, [typeof(BodyController)]); } - [Fact] - public async Task Under_OpenApi_3_1_the_nullable_body_keeps_its_pattern_inside_oneOf() + /// <summary> + /// Each answer of KB001.fixture.json: under OpenAPI 2.0 a nullable body is <c>allOf: [{x-nullable: true}]</c>, without + /// the pattern and type its non-nullable form has, in MVC and in minimal APIs alike; under 3.1 it keeps its pattern. + /// </summary> + [Theory, MemberData(nameof(Answers))] + public async Task The_framework_writes_the_body_the_fixture_holds(string id) { - var nullable = await BodyAsync(OpenApiSpecVersion.OpenApi3_1, "nullable"); + var answer = Fixture[id]; - nullable["schema"]!.ToJsonString().ShouldBe("""{"oneOf":[{"type":"null"},{""" + Int32Pattern + ""","type":["null","integer","string"],"format":"int32"}]}"""); + answer.Mismatch(await DocumentAsync(answer)).ShouldBeNull(); } } diff --git a/test/CodoMetis.TypeKit.Facts/KB001.fixture.json b/test/CodoMetis.TypeKit.Facts/KB001.fixture.json new file mode 100644 index 00000000..e8bc42b3 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB001.fixture.json @@ -0,0 +1,105 @@ +{ + "fact": "KB-001", + "answers": [ + { + "id": "openapi-2-0-mvc-int-body", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi2_0", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "as-declared", + "shape.wrapped": "int" + }, + "at": "/paths/~1kb001~1plain/post/parameters/0", + "value": { + "in": "body", + "name": "body", + "required": true, + "schema": { + "format": "int32", + "pattern": "^-?(?:0|[1-9]\\d*)$" + } + }, + "note": "required, with its pattern" + }, + { + "id": "openapi-2-0-mvc-nullable-int-body", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi2_0", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "nullable", + "shape.wrapped": "int" + }, + "at": "/paths/~1kb001~1nullable/post/parameters/0", + "value": { + "in": "body", + "name": "body", + "schema": { + "format": "int32", + "allOf": [ + { + "x-nullable": true + } + ] + } + }, + "note": "optional, and no pattern" + }, + { + "id": "openapi-2-0-minimal-nullable-enum-body", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi2_0", + "http.host": "minimal", + "http.binding": "body", + "http.nullability": "nullable", + "shape.wrapped": "enum" + }, + "at": "/paths/~1kb001~1minimal/post/parameters/0", + "value": { + "in": "body", + "name": "body", + "schema": { + "allOf": [ + { + "x-nullable": true + } + ] + } + }, + "note": "no type, nothing but the null" + }, + { + "id": "openapi-3-1-mvc-nullable-int-body", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_1", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "nullable", + "shape.wrapped": "int" + }, + "at": "/paths/~1kb001~1nullable/post/requestBody/content/application~1json/schema", + "value": { + "oneOf": [ + { + "type": "null" + }, + { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "null", + "integer", + "string" + ], + "format": "int32" + } + ] + }, + "note": "3.1 keeps the pattern inside oneOf" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB026.cs b/test/CodoMetis.TypeKit.Facts/KB026.cs new file mode 100644 index 00000000..391f36c9 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB026.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-026: under OpenAPI 2.0, ASP.NET writes a nullable property that refers to a component as +/// <c>allOf: [{x-nullable: true}]</c>, without the reference; 3.1 writes <c>oneOf: [null, $ref]</c>. +/// </summary> +public sealed class KB026 +{ + public readonly record struct Spot(int X, int Y); + + public sealed record Holder(Spot Always, Spot? Maybe); + + [ApiController] + [Route("kb026")] + public sealed class HolderController : ControllerBase + { + [HttpPost] + public IActionResult Post([FromBody] Holder holder) => Ok(); + } + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-026"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + /// <summary> + /// Each answer of KB026.fixture.json: under OpenAPI 2.0 the nullable property is <c>allOf: [{x-nullable: true}]</c> + /// where its non-nullable twin is the reference; under 3.1 it is null or the reference. + /// </summary> + [Theory, MemberData(nameof(Answers))] + public async Task The_framework_writes_the_property_the_fixture_holds(string id) + { + var answer = Fixture[id]; + var document = await FactHost.DocumentAsync(Enum.Parse<OpenApiSpecVersion>(answer.When["openapi.OpenApiVersion"]), [typeof(HolderController)]); + + answer.Mismatch(document).ShouldBeNull(); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB026.fixture.json b/test/CodoMetis.TypeKit.Facts/KB026.fixture.json new file mode 100644 index 00000000..4bea1214 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB026.fixture.json @@ -0,0 +1,61 @@ +{ + "fact": "KB-026", + "answers": [ + { + "id": "openapi-2-0-component-property", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi2_0", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "as-declared" + }, + "at": "/definitions/Holder/properties/always", + "value": { + "$ref": "#/definitions/Spot" + }, + "note": "the reference" + }, + { + "id": "openapi-2-0-nullable-component-property", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi2_0", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "nullable" + }, + "at": "/definitions/Holder/properties/maybe", + "value": { + "allOf": [ + { + "x-nullable": true + } + ] + }, + "note": "no reference, no type" + }, + { + "id": "openapi-3-1-nullable-component-property", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_1", + "http.host": "mvc", + "http.binding": "body", + "http.nullability": "nullable" + }, + "at": "/components/schemas/Holder/properties/maybe", + "value": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Spot" + } + ] + }, + "note": "null or the reference" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB027.cs b/test/CodoMetis.TypeKit.Facts/KB027.cs new file mode 100644 index 00000000..c14be0e8 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB027.cs @@ -0,0 +1,57 @@ +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-027: the component ASP.NET makes for an enum written by name is the schema of the first use +/// it meets. Met first as a nullable parameter, it lists null among its values, for its non-nullable uses too; met +/// first as itself, it does not, for its nullable uses too. +/// </summary> +public sealed class KB027 +{ + [JsonConverter(typeof(JsonStringEnumConverter<MetNullableFirst>))] + public enum MetNullableFirst { X, Y } + + [JsonConverter(typeof(JsonStringEnumConverter<MetPlainFirst>))] + public enum MetPlainFirst { X, Y } + + private static async Task<JsonNode> DocumentAsync(OpenApiSpecVersion version) => + await FactHost.DocumentAsync(version, [], minimal: app => + { + app.MapGet("/nullable-first/a", ([FromHeader(Name = "v")] MetNullableFirst? v) => 0); + app.MapGet("/nullable-first/b", ([FromHeader(Name = "v")] MetNullableFirst v) => 0); + app.MapGet("/plain-first/a", ([FromHeader(Name = "v")] MetPlainFirst v) => 0); + app.MapGet("/plain-first/b", ([FromHeader(Name = "v")] MetPlainFirst? v) => 0); + }); + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-027"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + /// <summary>Each answer of KB027.fixture.json: the enum's values in its component, null among them only where the nullable use came first.</summary> + [Theory, MemberData(nameof(Answers))] + public async Task An_enum_component_is_the_schema_of_its_first_use(string id) + { + var answer = Fixture[id]; + + answer.Mismatch(await DocumentAsync(Enum.Parse<OpenApiSpecVersion>(answer.When["openapi.OpenApiVersion"]))).ShouldBeNull(); + } + + /// <summary>Both uses of each enum refer to the one component, so the null goes with the component, not with the use.</summary> + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + public async Task Both_uses_refer_to_the_one_component(OpenApiSpecVersion version) + { + var document = await DocumentAsync(version); + + foreach (var path in new[] { "/nullable-first/a", "/nullable-first/b" }) + document["paths"]![path]!["get"]!["parameters"]![0]!["schema"]!["$ref"]!.GetValue<string>().ShouldEndWith(nameof(MetNullableFirst)); + foreach (var path in new[] { "/plain-first/a", "/plain-first/b" }) + document["paths"]![path]!["get"]!["parameters"]![0]!["schema"]!["$ref"]!.GetValue<string>().ShouldEndWith(nameof(MetPlainFirst)); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB027.fixture.json b/test/CodoMetis.TypeKit.Facts/KB027.fixture.json new file mode 100644 index 00000000..6494e737 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB027.fixture.json @@ -0,0 +1,79 @@ +{ + "fact": "KB-027", + "answers": [ + { + "id": "openapi-3-1-met-nullable-first", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_1", + "http.host": "minimal", + "http.binding": "header", + "json.Converters": "JsonStringEnumConverter", + "shape.wrapped": "enum", + "first-use": "nullable" + }, + "at": "/components/schemas/MetNullableFirst/enum", + "value": [ + "X", + "Y", + null + ], + "note": "met first as nullable: null among its values, for the plain use too" + }, + { + "id": "openapi-3-1-met-plain-first", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_1", + "http.host": "minimal", + "http.binding": "header", + "json.Converters": "JsonStringEnumConverter", + "shape.wrapped": "enum", + "first-use": "as-declared" + }, + "at": "/components/schemas/MetPlainFirst/enum", + "value": [ + "X", + "Y" + ], + "note": "met first as itself: no null, for the nullable use too" + }, + { + "id": "openapi-3-0-met-nullable-first", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_0", + "http.host": "minimal", + "http.binding": "header", + "json.Converters": "JsonStringEnumConverter", + "shape.wrapped": "enum", + "first-use": "nullable" + }, + "at": "/components/schemas/MetNullableFirst/enum", + "value": [ + "X", + "Y", + null + ], + "note": "met first as nullable: null among its values, for the plain use too" + }, + { + "id": "openapi-3-0-met-plain-first", + "kind": "document", + "when": { + "openapi.OpenApiVersion": "OpenApi3_0", + "http.host": "minimal", + "http.binding": "header", + "json.Converters": "JsonStringEnumConverter", + "shape.wrapped": "enum", + "first-use": "as-declared" + }, + "at": "/components/schemas/MetPlainFirst/enum", + "value": [ + "X", + "Y" + ], + "note": "met first as itself: no null, for the nullable use too" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB028.cs b/test/CodoMetis.TypeKit.Facts/KB028.cs new file mode 100644 index 00000000..0cc384a6 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB028.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-028: PostgreSQL treats NaN as greater than every number, so <c>> 5</c> keeps a NaN row +/// and ascending order puts NaN after Infinity. In .NET every comparison with NaN is false. +/// </summary> +public sealed class KB028 +{ + public sealed class Ratio + { + public int Id { get; set; } + + public double R { get; set; } + } + + [Fact] + public async Task NaN_is_greater_than_every_number_and_sorts_after_Infinity() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Ratio>()); + db.AddRange(new Ratio { R = 1.5 }, new Ratio { R = double.NaN }, new Ratio { R = 10 }, new Ratio { R = double.PositiveInfinity }); + await db.SaveChangesAsync(cancellation); + + (await db.Set<Ratio>().Where(r => r.R > 5).OrderBy(r => r.Id).Select(r => r.Id).ToListAsync(cancellation)).ShouldBe([2, 3, 4]); + (await db.Set<Ratio>().OrderBy(r => r.R).Select(r => r.R).ToListAsync(cancellation)).ShouldBe([1.5, 10, double.PositiveInfinity, double.NaN]); + (double.NaN > 5).ShouldBeFalse("in memory"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB029.cs b/test/CodoMetis.TypeKit.Facts/KB029.cs new file mode 100644 index 00000000..08899ae0 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB029.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-029: Npgsql truncates a value's ticks below a microsecond, for <see cref="DateTime"/>, +/// <see cref="DateTimeOffset"/>, <see cref="TimeOnly"/> and <see cref="TimeSpan"/> alike, and does not round them. +/// </summary> +public sealed class KB029 +{ + public sealed class Ticks + { + public int Id { get; set; } + + public DateTime At { get; set; } + + public DateTimeOffset On { get; set; } + + public TimeOnly T { get; set; } + + public TimeSpan S { get; set; } + } + + [Fact] + public async Task Ticks_below_a_microsecond_are_truncated_on_write() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Ticks>()); + var at = new DateTime(2026, 3, 4, 5, 6, 7, DateTimeKind.Utc).AddTicks(1234567); + db.Add(new Ticks + { + At = at, + On = new DateTimeOffset(2026, 1, 2, 3, 4, 5, TimeSpan.Zero).AddTicks(9), + T = new TimeOnly(2, 0).Add(TimeSpan.FromTicks(5)), + S = TimeSpan.FromHours(26).Add(TimeSpan.FromTicks(3)), + }); + await db.SaveChangesAsync(cancellation); + db.ChangeTracker.Clear(); + + var back = await db.Set<Ticks>().SingleAsync(cancellation); + + back.At.ToString("o").ShouldBe("2026-03-04T05:06:07.1234560Z", "truncated, where rounding would give .1234570"); + back.At.Kind.ShouldBe(DateTimeKind.Utc); + back.On.Ticks.ShouldBe(new DateTimeOffset(2026, 1, 2, 3, 4, 5, TimeSpan.Zero).Ticks); + back.T.Ticks.ShouldBe(72_000_000_000); + back.S.Ticks.ShouldBe(936_000_000_000); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB030.cs b/test/CodoMetis.TypeKit.Facts/KB030.cs new file mode 100644 index 00000000..c78d96aa --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB030.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-030: <see cref="DateTime.MaxValue"/> written to a <c>timestamptz</c> as UTC reads back as +/// <see cref="DateTime.MaxValue"/> with <see cref="DateTimeKind.Unspecified"/>: Npgsql stores it as infinity. +/// </summary> +public sealed class KB030 +{ + public sealed class Stamped + { + public int Id { get; set; } + + public DateTime At { get; set; } + + public DateTimeOffset On { get; set; } + } + + [Fact] + public async Task DateTime_MaxValue_as_UTC_reads_back_Unspecified() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Stamped>()); + db.Add(new Stamped { At = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc), On = DateTimeOffset.UtcNow }); + await db.SaveChangesAsync(cancellation); + db.ChangeTracker.Clear(); + + var at = (await db.Set<Stamped>().SingleAsync(cancellation)).At; + + at.ShouldBe(DateTime.MaxValue); + at.Kind.ShouldBe(DateTimeKind.Unspecified); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB031.cs b/test/CodoMetis.TypeKit.Facts/KB031.cs new file mode 100644 index 00000000..9fea7564 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB031.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-031: a <see cref="char"/> is a <c>character(1)</c> on PostgreSQL, and a space in it is +/// removed when SQL converts it to text, as in a concatenation; the column itself reads back as the space. +/// </summary> +public sealed class KB031 +{ + public sealed class Letter + { + public int Id { get; set; } + + public char C { get; set; } + } + + [Fact] + public async Task A_space_in_character_1_is_removed_when_SQL_converts_it_to_text() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Letter>()); + db.Add(new Letter { C = ' ' }); + await db.SaveChangesAsync(cancellation); + db.ChangeTracker.Clear(); + + (await db.Set<Letter>().Select(l => l.C).SingleAsync(cancellation)).ShouldBe(' '); + (await db.Set<Letter>().Select(l => "[" + l.C + "]").SingleAsync(cancellation)).ShouldBe("[]"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB032.cs b/test/CodoMetis.TypeKit.Facts/KB032.cs new file mode 100644 index 00000000..fe38c834 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB032.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-032: PostgreSQL sorts NULL last in ascending order and first in descending order, and EF +/// adds no <c>NULLS FIRST</c> or <c>NULLS LAST</c>. LINQ to Objects puts null first ascending. +/// </summary> +public sealed class KB032 +{ + public sealed class Ranked + { + public int Id { get; set; } + + public int? Rank { get; set; } + } + + [Fact] + public async Task Null_sorts_last_ascending_and_first_descending() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Ranked>()); + db.AddRange(new Ranked { Rank = null }, new Ranked { Rank = 1 }, new Ranked { Rank = 3 }, new Ranked { Rank = 2 }); + await db.SaveChangesAsync(cancellation); + + db.Set<Ranked>().OrderBy(r => r.Rank).ToQueryString().ShouldNotContain("NULLS"); + (await db.Set<Ranked>().OrderBy(r => r.Rank).Select(r => r.Rank).ToListAsync(cancellation)).ShouldBe([1, 2, 3, null]); + (await db.Set<Ranked>().OrderByDescending(r => r.Rank).Select(r => r.Rank).ToListAsync(cancellation)).ShouldBe([null, 3, 2, 1]); + new int?[] { null, 1, 3, 2 }.Order().ToList().ShouldBe([null, 1, 2, 3], "in memory"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB033.cs b/test/CodoMetis.TypeKit.Facts/KB033.cs new file mode 100644 index 00000000..019b6941 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB033.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-033: on PostgreSQL <c>StartsWith</c> is case-sensitive (<c>LIKE 'b%'</c>), and ordering +/// follows the database's collation, <c>en_US.utf8</c> in the <c>postgres:17</c> image, not ordinal order, which +/// the <c>C</c> collation gives. +/// </summary> +public sealed class KB033 +{ + public sealed class Named + { + public int Id { get; set; } + + public string Name { get; set; } = ""; + } + + [Fact] + public async Task StartsWith_is_case_sensitive_and_order_follows_the_collation() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Named>()); + db.AddRange(new Named { Name = "alpha" }, new Named { Name = "Bravo" }, new Named { Name = "charlie" }, new Named { Name = "Äpfel" }); + await db.SaveChangesAsync(cancellation); + + (await db.Set<Named>().Where(n => n.Name.StartsWith("b")).ToListAsync(cancellation)).ShouldBeEmpty(); + (await db.Set<Named>().OrderBy(n => n.Name).Select(n => n.Name).ToListAsync(cancellation)).ShouldBe(["alpha", "Äpfel", "Bravo", "charlie"]); + (await db.Set<Named>().OrderBy(n => EF.Functions.Collate(n.Name, "C")).Select(n => n.Name).ToListAsync(cancellation)).ShouldBe(["Bravo", "alpha", "charlie", "Äpfel"]); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB034.cs b/test/CodoMetis.TypeKit.Facts/KB034.cs new file mode 100644 index 00000000..ba9c9c0d --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB034.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-034: a <c>numeric(18,2)</c> column rounds a value with more fractional digits when it is +/// written, half away from zero. +/// </summary> +public sealed class KB034 +{ + public sealed class Money + { + public int Id { get; set; } + + public decimal Amount { get; set; } + } + + [Fact] + public async Task Numeric_p_s_rounds_more_fractional_digits_on_write() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Money>().Property(m => m.Amount).HasPrecision(18, 2)); + db.AddRange(new Money { Amount = 1.005m }, new Money { Amount = -0.004m }); + await db.SaveChangesAsync(cancellation); + db.ChangeTracker.Clear(); + + (await db.Set<Money>().OrderBy(m => m.Id).Select(m => m.Amount).ToListAsync(cancellation)).ShouldBe([1.01m, 0.00m]); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB035.cs b/test/CodoMetis.TypeKit.Facts/KB035.cs new file mode 100644 index 00000000..ac1f766f --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB035.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-035: Npgsql refuses a <see cref="DateTime"/> that is not UTC and a <see cref="DateTimeOffset"/> +/// whose offset is not zero for a <c>timestamptz</c>, written or sent as a parameter. +/// </summary> +public sealed class KB035 +{ + public sealed class Stamped + { + public int Id { get; set; } + + public DateTime At { get; set; } + + public DateTimeOffset On { get; set; } + } + + [Fact] + public async Task A_non_UTC_DateTime_and_a_non_zero_offset_are_refused_for_timestamptz() + { + var cancellation = TestContext.Current.CancellationToken; + await using var db = await Pg.ContextAsync(model => model.Entity<Stamped>()); + + db.Add(new Stamped { At = DateTime.UtcNow, On = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.FromHours(2)) }); + (await Should.ThrowAsync<DbUpdateException>(() => db.SaveChangesAsync(cancellation))).InnerException.ShouldBeOfType<ArgumentException>() + .Message.ShouldContain("only offset 0 (UTC) is supported"); + db.ChangeTracker.Clear(); + + db.Add(new Stamped { At = new DateTime(2026, 1, 1), On = DateTimeOffset.UtcNow }); + (await Should.ThrowAsync<DbUpdateException>(() => db.SaveChangesAsync(cancellation))).InnerException!.Message.ShouldContain("Kind=Unspecified"); + db.ChangeTracker.Clear(); + + var local = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Local); + (await Should.ThrowAsync<ArgumentException>(() => db.Set<Stamped>().Where(s => s.At > local).ToListAsync(cancellation))).Message.ShouldContain("Kind=Local"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB036.cs b/test/CodoMetis.TypeKit.Facts/KB036.cs new file mode 100644 index 00000000..ef47170e --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB036.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; +using Npgsql; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-036: Npgsql's EF provider declares a plain <see cref="char"/> key an identity column of +/// <c>character(1)</c>, which PostgreSQL refuses: an identity column's type must be <c>smallint</c>, <c>integer</c> +/// or <c>bigint</c>. +/// </summary> +public sealed class KB036 +{ + public sealed class CharKey + { + public char Id { get; set; } + + public string N { get; set; } = ""; + } + + [Fact] + public async Task A_char_key_gets_an_identity_column_PostgreSQL_refuses() + { + await using var db = await Pg.ContextAsync(model => model.Entity<CharKey>(), create: false); + + db.Database.GenerateCreateScript().ShouldContain("\"Id\" character(1) GENERATED BY DEFAULT AS IDENTITY"); + var refused = await Should.ThrowAsync<PostgresException>(() => db.Database.EnsureCreatedAsync(TestContext.Current.CancellationToken)); + refused.SqlState.ShouldBe("22023", "identity column type must be smallint, integer, or bigint"); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB037.cs b/test/CodoMetis.TypeKit.Facts/KB037.cs new file mode 100644 index 00000000..597b4164 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB037.cs @@ -0,0 +1,96 @@ +using System.Net; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-037: with <c>MvcOptions.MaxValidationDepth = 1</c>, an <c>[ApiController]</c> action whose simple +/// parameter MVC cannot bind (route, query, header, form, body) runs anyway, with the parameter left at its default, and +/// answers 200; a class bound from the query makes the validator throw its depth refusal at its property, whether the +/// property was bound or not. With the default depth every unbindable request is a 400. +/// </summary> +public sealed class KB037 +{ + public sealed class Q { public int V { get; set; } } + + [ApiController] + public sealed class BindController : ControllerBase + { + [HttpGet("kb037/route/{v}")] public IActionResult Route([FromRoute(Name = "v")] int v) => Ok(v); + [HttpGet("kb037/query")] public IActionResult Query([FromQuery(Name = "v")] int v) => Ok(v); + [HttpGet("kb037/header")] public IActionResult Header([FromHeader(Name = "v")] int v) => Ok(v); + [HttpPost("kb037/form")] public IActionResult Form([FromForm(Name = "v")] int v) => Ok(v); + [HttpPost("kb037/body")] public IActionResult Body([FromBody] int v) => Ok(v); + [HttpGet("kb037/params")] public IActionResult Params([FromQuery] Q q) => Ok(q.V); + } + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-037"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + private static async Task<(HttpStatusCode Status, string Body)> SendAsync(int? maxValidationDepth, string source, string text = "abc") + { + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = Environments.Production }); + builder.WebHost.UseTestServer(); + builder.Logging.ClearProviders(); + builder.Services.AddControllers(options => { if (maxValidationDepth is { } depth) options.MaxValidationDepth = depth; }) + .ConfigureApplicationPartManager(parts => + { + parts.ApplicationParts.Clear(); + parts.FeatureProviders.Add(new Only()); + }); + + await using var app = builder.Build(); + app.MapControllers(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var request = source switch + { + "route" => new HttpRequestMessage(HttpMethod.Get, $"/kb037/route/{text}"), + "query" => new HttpRequestMessage(HttpMethod.Get, $"/kb037/query?v={text}"), + "header" => new HttpRequestMessage(HttpMethod.Get, "/kb037/header") { Headers = { { "v", text } } }, + "form" => new HttpRequestMessage(HttpMethod.Post, "/kb037/form") { Content = new FormUrlEncodedContent([new("v", text)]) }, + "body" => new HttpRequestMessage(HttpMethod.Post, "/kb037/body") { Content = new StringContent(int.TryParse(text, out _) ? text : $"\"{text}\"", System.Text.Encoding.UTF8, "application/json") }, + _ => new HttpRequestMessage(HttpMethod.Get, $"/kb037/params?V={text}"), + }; + var response = await app.GetTestClient().SendAsync(request, TestContext.Current.CancellationToken); + return (response.StatusCode, await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + } + + /// <summary> + /// Each answer of KB037.fixture.json, sent as its settings say: the binding source (<c>as-parameters</c> is a class + /// bound from the query), the text, and <c>MaxValidationDepth</c> 1 or the default. + /// </summary> + [Theory, MemberData(nameof(Answers))] + public async Task MVC_answers_as_the_fixture_holds(string id) + { + var answer = Fixture[id]; + var depth = answer.When["mvc.MaxValidationDepth"] == "default" ? (int?)null : int.Parse(answer.When["mvc.MaxValidationDepth"], System.Globalization.CultureInfo.InvariantCulture); + var source = answer.When["http.binding"] == "as-parameters" ? "params" : answer.When["http.binding"]; + + if (answer.Kind == FactAnswer.Exception) + { + var thrown = await Should.ThrowAsync<Exception>(() => SendAsync(depth, source, answer.When["text"])); + answer.Mismatch(thrown).ShouldBeNull(); + } + else + { + var (status, body) = await SendAsync(depth, source, answer.When["text"]); + answer.Mismatch((int)status, body).ShouldBeNull(); + } + } + + private sealed class Only : IApplicationFeatureProvider<ControllerFeature> + { + public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature) => + feature.Controllers.Add(typeof(BindController).GetTypeInfo()); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB037.fixture.json b/test/CodoMetis.TypeKit.Facts/KB037.fixture.json new file mode 100644 index 00000000..c916bd68 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB037.fixture.json @@ -0,0 +1,291 @@ +{ + "fact": "KB-037", + "answers": [ + { + "id": "depth-1-route-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "route", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 200, + "body": "0" + }, + "note": "the action runs, the int left at its default" + }, + { + "id": "depth-1-query-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "query", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 200, + "body": "0" + }, + "note": "the action runs, the int left at its default" + }, + { + "id": "depth-1-header-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "header", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 200, + "body": "0" + }, + "note": "the action runs, the int left at its default" + }, + { + "id": "depth-1-form-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "form", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 200, + "body": "0" + }, + "note": "the action runs, the int left at its default" + }, + { + "id": "depth-1-body-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "body", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 200, + "body": "0" + }, + "note": "the action runs, the int left at its default" + }, + { + "id": "depth-1-route-readable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "route", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "status": 200, + "body": "5" + }, + "note": "a text the int reads binds as usual" + }, + { + "id": "depth-1-query-readable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "query", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "status": 200, + "body": "5" + }, + "note": "a text the int reads binds as usual" + }, + { + "id": "depth-1-header-readable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "header", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "status": 200, + "body": "5" + }, + "note": "a text the int reads binds as usual" + }, + { + "id": "depth-1-form-readable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "form", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "status": 200, + "body": "5" + }, + "note": "a text the int reads binds as usual" + }, + { + "id": "depth-1-body-readable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "body", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "status": 200, + "body": "5" + }, + "note": "a text the int reads binds as usual" + }, + { + "id": "depth-1-as-parameters-unreadable", + "kind": "exception", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "as-parameters", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "type": "System.InvalidOperationException", + "operation": "GET /kb037/params?V=abc", + "stage": "model validation, at the class's property V", + "message": "ValidationVisitor exceeded the maximum configured validation depth '1' when validating property 'V'" + }, + "note": "a class bound from the query: the validator throws at its property, bound or not" + }, + { + "id": "depth-1-as-parameters-readable", + "kind": "exception", + "when": { + "mvc.MaxValidationDepth": "1", + "http.host": "mvc", + "http.binding": "as-parameters", + "shape.wrapped": "int", + "text": "5" + }, + "value": { + "type": "System.InvalidOperationException", + "operation": "GET /kb037/params?V=5", + "stage": "model validation, at the class's property V", + "message": "ValidationVisitor exceeded the maximum configured validation depth '1' when validating property 'V'" + }, + "note": "a class bound from the query: the validator throws at its property, bound or not" + }, + { + "id": "default-depth-route-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "route", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + }, + { + "id": "default-depth-query-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "query", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + }, + { + "id": "default-depth-header-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "header", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + }, + { + "id": "default-depth-form-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "form", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + }, + { + "id": "default-depth-body-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "body", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + }, + { + "id": "default-depth-as-parameters-unreadable", + "kind": "status", + "when": { + "mvc.MaxValidationDepth": "default", + "http.host": "mvc", + "http.binding": "as-parameters", + "shape.wrapped": "int", + "text": "abc" + }, + "value": { + "status": 400 + }, + "note": "with the default depth every unbindable request is a 400" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB038.cs b/test/CodoMetis.TypeKit.Facts/KB038.cs new file mode 100644 index 00000000..e022b751 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB038.cs @@ -0,0 +1,31 @@ +using System.Text.Json; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-038: System.Text.Json writes a <c>byte[]</c> as one base64 string and reads it back from one, where +/// every other collection of bytes, and an array of any other element, is a JSON array. +/// </summary> +public sealed class KB038 +{ + public readonly record struct Octet(byte Value); + + [Fact] + public void A_byte_array_is_a_base64_string() => + JsonSerializer.Serialize(new byte[] { 7, 255 }).ShouldBe("\"B/8=\""); + + [Fact] + public void A_byte_array_reads_back_from_base64_and_not_from_a_JSON_array() + { + JsonSerializer.Deserialize<byte[]>("\"B/8=\"").ShouldBe(new byte[] { 7, 255 }); + Should.Throw<JsonException>(() => JsonSerializer.Deserialize<byte[]>("[7,255]")); + } + + [Fact] + public void A_list_of_bytes_is_a_JSON_array_of_numbers() => + JsonSerializer.Serialize(new List<byte> { 7, 255 }).ShouldBe("[7,255]"); + + [Fact] + public void An_array_of_another_element_over_a_byte_is_a_JSON_array() => + JsonSerializer.Serialize(new[] { new Octet(7) }).ShouldBe("""[{"Value":7}]"""); +} diff --git a/test/CodoMetis.TypeKit.Facts/KB039.cs b/test/CodoMetis.TypeKit.Facts/KB039.cs new file mode 100644 index 00000000..7c6552c2 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB039.cs @@ -0,0 +1,106 @@ +using System.Linq.Expressions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-039: EF Core's SQLite provider translates no comparison and no <c>ORDER BY</c> of a +/// <see cref="DateTimeOffset"/>, a <see cref="TimeSpan"/> or a <see cref="ulong"/> column: <c>>=</c> throws "could not be +/// translated", and an ordering throws <see cref="NotSupportedException"/>. Equality is translated. A <see cref="decimal"/>, +/// which EF's page lists beside them, is compared and ordered. +/// </summary> +public sealed class KB039 +{ + public sealed class Row<T> + { + public int Id { get; set; } + + public T P { get; set; } = default!; + } + + private sealed class Db<T>(DbContextOptions options) : DbContext(options) + { + protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity<Row<T>>().ToTable("t"); + } + + private sealed class PerType : IModelCacheKeyFactory + { + public object Create(DbContext context, bool designTime) => (context.GetType(), designTime); + } + + private static async Task<(SqliteConnection Connection, Db<T> Db)> Open<T>(T low, T high) + { + var connection = new SqliteConnection("Data Source=:memory:"); + await connection.OpenAsync(TestContext.Current.CancellationToken); + var db = new Db<T>(new DbContextOptionsBuilder().UseSqlite(connection).ReplaceService<IModelCacheKeyFactory, PerType>().Options); + await db.Database.EnsureCreatedAsync(TestContext.Current.CancellationToken); + db.AddRange(new Row<T> { P = high }, new Row<T> { P = low }); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); + db.ChangeTracker.Clear(); + return (connection, db); + } + + public static TheoryData<string> Refused => ["DateTimeOffset", "TimeSpan", "ulong"]; + + [Theory, MemberData(nameof(Refused))] + public Task A_comparison_is_not_translated(string type) => type switch + { + "DateTimeOffset" => NotTranslated(DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddDays(1), r => r.P >= DateTimeOffset.UnixEpoch.AddDays(1)), + "TimeSpan" => NotTranslated(TimeSpan.FromHours(1), TimeSpan.FromHours(2), r => r.P >= TimeSpan.FromHours(2)), + _ => NotTranslated(1UL, 2UL, r => r.P >= 2UL), + }; + + [Theory, MemberData(nameof(Refused))] + public Task An_ordering_is_not_supported(string type) => type switch + { + "DateTimeOffset" => NotOrdered(DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddDays(1), "DateTimeOffset"), + "TimeSpan" => NotOrdered(TimeSpan.FromHours(1), TimeSpan.FromHours(2), "TimeSpan"), + _ => NotOrdered(1UL, 2UL, "ulong"), + }; + + [Theory, MemberData(nameof(Refused))] + public Task Equality_is_translated(string type) => type switch + { + "DateTimeOffset" => Equal(DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddDays(1), r => r.P == DateTimeOffset.UnixEpoch), + "TimeSpan" => Equal(TimeSpan.FromHours(1), TimeSpan.FromHours(2), r => r.P == TimeSpan.FromHours(1)), + _ => Equal(1UL, 2UL, r => r.P == 1UL), + }; + + [Fact] + public async Task A_decimal_is_compared_and_ordered() + { + var (connection, db) = await Open(1.5m, 2.5m); + await using var _ = connection; + await using var __ = db; + (await db.Set<Row<decimal>>().Where(r => r.P >= 2m).Select(r => r.P).ToListAsync(TestContext.Current.CancellationToken)).ShouldBe([2.5m]); + (await db.Set<Row<decimal>>().OrderBy(r => r.P).Select(r => r.P).ToListAsync(TestContext.Current.CancellationToken)).ShouldBe([1.5m, 2.5m]); + } + + private static async Task NotTranslated<T>(T low, T high, Expression<Func<Row<T>, bool>> atLeastHigh) + { + var (connection, db) = await Open(low, high); + await using var _ = connection; + await using var __ = db; + var refused = await Should.ThrowAsync<InvalidOperationException>(() => db.Set<Row<T>>().Where(atLeastHigh).ToListAsync(TestContext.Current.CancellationToken)); + refused.Message.ShouldContain("could not be translated"); + } + + private static async Task NotOrdered<T>(T low, T high, string name) + { + var (connection, db) = await Open(low, high); + await using var _ = connection; + await using var __ = db; + var refused = await Should.ThrowAsync<NotSupportedException>(() => db.Set<Row<T>>().OrderBy(r => r.P).ToListAsync(TestContext.Current.CancellationToken)); + refused.Message.ShouldContain($"SQLite does not support expressions of type '{name}' in ORDER BY clauses"); + } + + private static async Task Equal<T>(T low, T high, Expression<Func<Row<T>, bool>> isLow) + { + var (connection, db) = await Open(low, high); + await using var _ = connection; + await using var __ = db; + (await db.Set<Row<T>>().Where(isLow).Select(r => r.P).ToListAsync(TestContext.Current.CancellationToken)).ShouldBe([low]); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB040.cs b/test/CodoMetis.TypeKit.Facts/KB040.cs new file mode 100644 index 00000000..0743f633 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB040.cs @@ -0,0 +1,73 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-040: under <c>JsonSerializerOptions.MaxDepth</c>, System.Text.Json refuses a value that a converter of one's own +/// writes at the writer's depth equal to the limit, with a <c>JsonException</c> at that value, where its own converter for the same type +/// writes it; one level less deep, the converter's value is written. Reading is not charged: the converter reads its value at the limit. +/// </summary> +public sealed class KB040 +{ + public sealed class A<T> + { + public T P { get; set; } = default!; + } + + /// <summary>A converter of the application's for <c>int</c>, which reads and writes the number as the serializer does.</summary> + public sealed class IntConverter : JsonConverter<int> + { + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetInt32(); + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => writer.WriteNumberValue(value); + } + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-040"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + private static JsonSerializerOptions Options(IReadOnlyDictionary<string, string> when) + { + var options = new JsonSerializerOptions { MaxDepth = int.Parse(when["json.MaxDepth"], CultureInfo.InvariantCulture) }; + if (when["written-by"] == "a-converter") options.Converters.Add(new IntConverter()); + return options; + } + + /// <summary>A list of one <c>int</c> whose element the writer is at the given depth for: inside the root object, then one more object per level.</summary> + private static object At(string depth) => depth switch + { + "2" => new A<List<int>> { P = [1] }, + "3" => new A<A<List<int>>> { P = new() { P = [1] } }, + _ => throw new ArgumentOutOfRangeException(nameof(depth), depth, "the fixture's depths are 2 and 3"), + }; + + /// <summary>Each answer of KB040.fixture.json: the element written by the converter or the serializer, at the depth the answer names.</summary> + [Theory, MemberData(nameof(Answers))] + public void The_serializer_writes_or_refuses_as_the_fixture_holds(string id) + { + var answer = Fixture[id]; + var value = At(answer.When["writer-depth"]); + + if (answer.Kind == FactAnswer.Exception) + { + var thrown = Should.Throw<Exception>(() => JsonSerializer.Serialize(value, value.GetType(), Options(answer.When))); + answer.Mismatch(thrown).ShouldBeNull(); + ((JsonException)thrown).Path.ShouldBe("$.P.P"); + } + else + { + answer.Mismatch(JsonNode.Parse(JsonSerializer.Serialize(value, value.GetType(), Options(answer.When)))!).ShouldBeNull(); + } + } + + /// <summary>Reading is not charged: the converter reads the element at the limit, as the serializer's own does.</summary> + [Fact] + public void A_converter_reads_its_value_at_the_limit() + { + var options = Options(new Dictionary<string, string> { ["json.MaxDepth"] = "3", ["written-by"] = "a-converter" }); + + JsonSerializer.Deserialize<A<A<List<int>>>>("""{"P":{"P":[1]}}""", options)!.P.P.ShouldBe([1]); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB040.fixture.json b/test/CodoMetis.TypeKit.Facts/KB040.fixture.json new file mode 100644 index 00000000..2bb6b935 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB040.fixture.json @@ -0,0 +1,44 @@ +{ + "fact": "KB-040", + "answers": [ + { + "id": "a-converter-at-the-limit", + "kind": "exception", + "when": { + "json.MaxDepth": "3", + "written-by": "a-converter", + "writer-depth": "3" + }, + "value": { + "type": "System.Text.Json.JsonException", + "operation": "write", + "stage": "the value the converter is handed, before it writes anything" + }, + "note": "a list element at the writer's depth 3, written by a converter of the application's: refused, with the path of the element" + }, + { + "id": "the-serializer-at-the-limit", + "kind": "document", + "when": { + "json.MaxDepth": "3", + "written-by": "the-serializer", + "writer-depth": "3" + }, + "at": "/P/P", + "value": [1], + "note": "the same element, written by the serializer's own converter for int: written" + }, + { + "id": "a-converter-below-the-limit", + "kind": "document", + "when": { + "json.MaxDepth": "3", + "written-by": "a-converter", + "writer-depth": "2" + }, + "at": "/P", + "value": [1], + "note": "a list element at the writer's depth 2, written by a converter of the application's: written" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB041.cs b/test/CodoMetis.TypeKit.Facts/KB041.cs new file mode 100644 index 00000000..91bbbcae --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB041.cs @@ -0,0 +1,81 @@ +using System.Net; +using System.Reflection; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary>A source-generated context that lists <c>int</c> and nothing of MVC's: no <c>ValidationProblemDetails</c>.</summary> +[JsonSerializable(typeof(int))] +internal sealed partial class KB041Context : JsonSerializerContext; + +/// <summary> +/// design/knowledge/KB-041: an <c>[ApiController]</c> action whose parameter MVC cannot bind answers its automatic 400 as a +/// <c>ValidationProblemDetails</c>, which MVC writes with its JSON options; where their resolver is a source-generated context that does +/// not list that type, the 400 is not written and the request fails with the serializer's exception, for any parameter type. +/// </summary> +public sealed class KB041 +{ + [ApiController] + public sealed class BindController : ControllerBase + { + [HttpGet("kb041/{v}")] + public IActionResult Route([FromRoute(Name = "v")] int v) => Ok(v); + } + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-041"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + private static async Task<HttpResponseMessage> SendAsync(bool sourceGenerated) + { + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = Environments.Production }); + builder.WebHost.UseTestServer(); + builder.Logging.ClearProviders(); + builder.Services.AddControllers() + .AddJsonOptions(options => { if (sourceGenerated) options.JsonSerializerOptions.TypeInfoResolver = KB041Context.Default; }) + .ConfigureApplicationPartManager(parts => + { + parts.ApplicationParts.Clear(); + parts.FeatureProviders.Add(new Only()); + }); + + await using var app = builder.Build(); + app.MapControllers(); + await app.StartAsync(TestContext.Current.CancellationToken); + + return await app.GetTestClient().GetAsync(new Uri("/kb041/abc", UriKind.Relative), TestContext.Current.CancellationToken); + } + + /// <summary>Each answer of KB041.fixture.json: <c>abc</c> sent to the action's <c>int</c>, under the resolver the answer names.</summary> + [Theory, MemberData(nameof(Answers))] + public async Task MVC_answers_a_refused_binding_as_the_fixture_holds(string id) + { + var answer = Fixture[id]; + var sourceGenerated = answer.When["json.TypeInfoResolver"] == "source-generated-context"; + + if (answer.Kind == FactAnswer.Exception) + { + var thrown = await Should.ThrowAsync<Exception>(() => SendAsync(sourceGenerated)); + answer.Mismatch(thrown).ShouldBeNull(); + } + else + { + using var response = await SendAsync(sourceGenerated); + answer.Mismatch((int)response.StatusCode, "").ShouldBeNull(); + } + } + + private sealed class Only : IApplicationFeatureProvider<ControllerFeature> + { + public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature) => + feature.Controllers.Add(typeof(BindController).GetTypeInfo()); + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB041.fixture.json b/test/CodoMetis.TypeKit.Facts/KB041.fixture.json new file mode 100644 index 00000000..5c4129de --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB041.fixture.json @@ -0,0 +1,33 @@ +{ + "fact": "KB-041", + "answers": [ + { + "id": "source-generated-context-refused-binding", + "kind": "exception", + "when": { + "json.TypeInfoResolver": "source-generated-context", + "endpoint": "mvc-api-controller", + "binding": "fails" + }, + "value": { + "type": "System.NotSupportedException", + "operation": "bind", + "stage": "writing the automatic 400, a ValidationProblemDetails the context does not list" + }, + "note": "an [ApiController] action whose int parameter is sent abc, under MVC JSON options whose resolver is a source-generated context listing int alone: the 400 is not written, the request fails" + }, + { + "id": "default-resolver-refused-binding", + "kind": "status", + "when": { + "json.TypeInfoResolver": "default", + "endpoint": "mvc-api-controller", + "binding": "fails" + }, + "value": { + "status": 400 + }, + "note": "the same request under the serializer's default resolver: MVC's automatic 400" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/KB042.cs b/test/CodoMetis.TypeKit.Facts/KB042.cs new file mode 100644 index 00000000..df2524ba --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB042.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// design/knowledge/KB-042: under <c>JsonSerializerOptions.NumberHandling</c> with <c>WriteAsString</c>, ASP.NET refuses the OpenAPI document of +/// an endpoint whose schema holds a <c>char</c>, with a <c>JsonException</c> reading the schema back as its <c>OpenApiJsonSchema</c>: the +/// exporter's <c>minLength</c> and <c>maxLength</c> of a <c>char</c> are written as strings. A number's schema is written, and so is a +/// <c>char</c>'s under the default number handling. +/// </summary> +public sealed class KB042 +{ + public sealed class Holder<T> + { + public T P { get; set; } = default!; + } + + private static readonly FactFixture Fixture = FactFixture.Beside("KB-042"); + + public static TheoryData<string> Answers() => [.. Fixture.Answers.Select(answer => answer.Id)]; + + /// <summary>The document of a minimal API whose one endpoint takes a body of <c>Holder<T></c> for the answer's type.</summary> + private static async Task<JsonNode> DocumentAsync(IReadOnlyDictionary<string, string> when) + { + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = Environments.Production }); + builder.WebHost.UseTestServer(); + builder.Logging.ClearProviders(); + builder.Services.ConfigureHttpJsonOptions(options => + { + if (when["json.NumberHandling"] == "WriteAsString") options.SerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString; + }); + builder.Services.AddOpenApi(); + + await using var app = builder.Build(); + _ = when["shape.wrapped"] switch + { + "char" => app.MapPost("/kb042", ([FromBody] Holder<char> body) => Results.Ok()), + "int" => app.MapPost("/kb042", ([FromBody] Holder<int> body) => Results.Ok()), + var other => throw new ArgumentOutOfRangeException(nameof(when), other, "the fixture's types are char and int"), + }; + app.MapOpenApi(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var text = await app.GetTestClient().GetStringAsync("/openapi/v1.json", TestContext.Current.CancellationToken); + return JsonNode.Parse(text)!; + } + + /// <summary>Each answer of KB042.fixture.json: the document of a body holding the answer's type, under the number handling it names.</summary> + [Theory, MemberData(nameof(Answers))] + public async Task ASP_NET_writes_or_refuses_the_document_as_the_fixture_holds(string id) + { + var answer = Fixture[id]; + + if (answer.Kind == FactAnswer.Exception) + { + var thrown = await Should.ThrowAsync<Exception>(() => DocumentAsync(answer.When)); + answer.Mismatch(thrown).ShouldBeNull(); + } + else + { + answer.Mismatch(await DocumentAsync(answer.When)).ShouldBeNull(); + } + } +} diff --git a/test/CodoMetis.TypeKit.Facts/KB042.fixture.json b/test/CodoMetis.TypeKit.Facts/KB042.fixture.json new file mode 100644 index 00000000..f24316c7 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/KB042.fixture.json @@ -0,0 +1,54 @@ +{ + "fact": "KB-042", + "answers": [ + { + "id": "char-under-write-as-string", + "kind": "exception", + "when": { + "json.NumberHandling": "WriteAsString", + "shape.wrapped": "char" + }, + "value": { + "type": "System.Text.Json.JsonException", + "operation": "document", + "stage": "reading the char's schema back as ASP.NET's OpenApiJsonSchema", + "message": "could not be converted to OpenApiJsonSchema" + }, + "note": "a minimal API's body holding a char, under NumberHandling WriteAsString: the document is refused" + }, + { + "id": "char-by-default", + "kind": "document", + "when": { + "json.NumberHandling": "default", + "shape.wrapped": "char" + }, + "at": "/components/schemas/HolderOfchar/properties/p", + "value": { + "maxLength": 1, + "minLength": 1, + "type": "string", + "format": "char" + }, + "note": "the same body under the default number handling: written" + }, + { + "id": "int-under-write-as-string", + "kind": "document", + "when": { + "json.NumberHandling": "WriteAsString", + "shape.wrapped": "int" + }, + "at": "/components/schemas/HolderOfint/properties/p", + "value": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "note": "a body holding an int under NumberHandling WriteAsString: written" + } + ] +} diff --git a/test/CodoMetis.TypeKit.Facts/PgFactHost.cs b/test/CodoMetis.TypeKit.Facts/PgFactHost.cs new file mode 100644 index 00000000..80591e67 --- /dev/null +++ b/test/CodoMetis.TypeKit.Facts/PgFactHost.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Npgsql; + +namespace CodoMetis.TypeKit.Facts; + +/// <summary> +/// A PostgreSQL database of its own for a fact, with plain EF Core and Npgsql and nothing of TypeKit's, on the +/// server the connection string in <c>TYPEKIT_PG</c> names (<c>Host=localhost;Port=55433;Username=postgres;Password=typekit</c>). +/// </summary> +/// <remarks> +/// A fact that needs one is skipped where the variable is unset. That is an interim: CI has no PostgreSQL service +/// for this project until WS6 adds one, and the entries' <c>test:</c> lines say so. +/// </remarks> +internal static class Pg +{ + public const string Variable = "TYPEKIT_PG"; + + /// <summary>A fresh database for <paramref name="model"/>, created unless <paramref name="create"/> is false, and dropped when disposed.</summary> + public static async Task<PgDb> ContextAsync(Action<ModelBuilder> model, bool create = true, [CallerFilePath] string fact = "") + { + if (Environment.GetEnvironmentVariable(Variable) is not { Length: > 0 } connection) + { + Assert.Skip($"{Variable} is unset: this fact needs a PostgreSQL server, until WS6's CI service."); + throw new UnreachableException(); + } + + var database = new NpgsqlConnectionStringBuilder(connection) { Database = $"kb_{Guid.NewGuid():N}" }; + var options = new DbContextOptionsBuilder<PgDb>().UseNpgsql(database.ConnectionString) + .ReplaceService<IModelCacheKeyFactory, PgDb.CacheKey>() + .Options; + var db = new PgDb(options, model, Path.GetFileNameWithoutExtension(fact)); + if (create) await db.Database.EnsureCreatedAsync(TestContext.Current.CancellationToken); + + return db; + } +} + +/// <summary>A context whose model is the fact's, cached per fact.</summary> +internal sealed class PgDb(DbContextOptions<PgDb> options, Action<ModelBuilder> model, string fact) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => model(modelBuilder); + + public override async ValueTask DisposeAsync() + { + await Database.EnsureDeletedAsync(); + await base.DisposeAsync(); + } + + public sealed class CacheKey : IModelCacheKeyFactory + { + public object Create(DbContext context, bool designTime) => context is PgDb db ? (typeof(PgDb), db.FactName, designTime) : (context.GetType(), designTime); + } + + private string FactName => fact; +} diff --git a/test/CodoMetis.TypeKit.Generators.Tests/BuildOutcomeTests.cs b/test/CodoMetis.TypeKit.Generators.Tests/BuildOutcomeTests.cs index a6b47576..858d93e9 100644 --- a/test/CodoMetis.TypeKit.Generators.Tests/BuildOutcomeTests.cs +++ b/test/CodoMetis.TypeKit.Generators.Tests/BuildOutcomeTests.cs @@ -81,6 +81,7 @@ public sealed partial class BuildOutcomeTests(BuildOutcomeTests.Consumer consume [InlineData("CMTK1011", "HandWrittenFrom")] [InlineData("CMTK1011", "HandWrittenValue")] [InlineData("CMTK1011", "HandWrittenParse")] + [InlineData("CMTK1011", "HandWrittenSpanParseOverString")] [InlineData("CMTK1011", "HandWrittenParseOverASet")] [InlineData("CMTK1011", "HandWrittenFormat")] [InlineData("CMTK1011", "HandWrittenMinValue")] @@ -870,6 +871,12 @@ public static Result<Sku, Fault> Create(string value) => public static HandWrittenParse Parse(string s, System.IFormatProvider? provider) => throw new System.NotSupportedException(); } + // string is ISpanParsable, so the span Parse is generated over it too, and a hand-written one is refused. + public readonly partial record struct HandWrittenSpanParseOverString : IValue<string> + { + public static HandWrittenSpanParseOverString Parse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider) => throw new System.NotSupportedException(); + } + public readonly partial record struct HandWrittenFormat : IValue<int> { public string ToString(string? format, System.IFormatProvider? provider) => ""; diff --git a/test/CodoMetis.TypeKit.Generators.Tests/EntryPointValidationTests.cs b/test/CodoMetis.TypeKit.Generators.Tests/EntryPointValidationTests.cs index ddbf6ea2..e346a907 100644 --- a/test/CodoMetis.TypeKit.Generators.Tests/EntryPointValidationTests.cs +++ b/test/CodoMetis.TypeKit.Generators.Tests/EntryPointValidationTests.cs @@ -50,6 +50,9 @@ private enum Outcome { Refused, Accepted } ["FromKnownGood(String)"] = input => Catching<InvalidOperationException>(() => ProbeCode.FromKnownGood(input)), ["Parse(String)"] = input => Catching<FormatException>(() => ProbeCode.Parse(input, null)), ["TryParse(String)"] = input => FromResult(ProbeCode.TryParse(input, null, out var v), v), + // Through the interface, so that this compiles where the span parsing is missing. + ["Parse(ReadOnlySpan<Char>)"] = input => Catching<FormatException>(() => SpanParsing.ParseSpan(typeof(ProbeCode), input)), + ["TryParse(ReadOnlySpan<Char>)"] = input => FromResult(SpanParsing.TryParseSpan(typeof(ProbeCode), input)), ["JSON value"] = input => Catching<JsonException>(() => JsonSerializer.Deserialize<ProbeCode>(JsonSerializer.Serialize(input))), ["JSON property name"] = input => Catching<JsonException>(() => JsonSerializer.Deserialize<Dictionary<ProbeCode, int>>($"{{{JsonSerializer.Serialize(input)}:1}}")!.Keys.Single()), ["TypeConverter"] = input => Catching<FormatException>(() => TypeDescriptor.GetConverter(typeof(ProbeCode)).ConvertFromInvariantString(input)), @@ -62,6 +65,8 @@ private enum Outcome { Refused, Accepted } ["FromKnownGood(String)"] = input => Catching<InvalidOperationException>(() => ProbeArticle.FromKnownGood(input)), ["Parse(String)"] = input => Catching<FormatException>(() => ProbeArticle.Parse(input, null)), ["TryParse(String)"] = input => FromResult(ProbeArticle.TryParse(input, null, out var v), v), + ["Parse(ReadOnlySpan<Char>)"] = input => Catching<FormatException>(() => SpanParsing.ParseSpan(typeof(ProbeArticle), input)), + ["TryParse(ReadOnlySpan<Char>)"] = input => FromResult(SpanParsing.TryParseSpan(typeof(ProbeArticle), input)), ["JSON value"] = input => Catching<JsonException>(() => JsonSerializer.Deserialize<ProbeArticle>(JsonSerializer.Serialize(input))), ["JSON property name"] = input => Catching<JsonException>(() => JsonSerializer.Deserialize<Dictionary<ProbeArticle, int>>($"{{{JsonSerializer.Serialize(input)}:1}}")!.Keys.Single()), ["TypeConverter"] = input => Catching<FormatException>(() => TypeDescriptor.GetConverter(typeof(ProbeArticle)).ConvertFromInvariantString(input)), @@ -183,6 +188,8 @@ public void A_validated_number_gets_no_MinValue_or_MaxValue() private static (Outcome, object?) FromResult(bool accepted, object? value) => accepted ? (Outcome.Accepted, value) : (Outcome.Refused, null); + private static (Outcome, object?) FromResult((bool Ok, object? Result) tried) => FromResult(tried.Ok, tried.Result); + private static (Outcome, object?) Catching<TException>(Func<object?> entryPoint) where TException : Exception { try diff --git a/test/CodoMetis.TypeKit.Generators.Tests/GeneratedSurface.verified.txt b/test/CodoMetis.TypeKit.Generators.Tests/GeneratedSurface.verified.txt index 30046dc7..c2e1484e 100644 --- a/test/CodoMetis.TypeKit.Generators.Tests/GeneratedSurface.verified.txt +++ b/test/CodoMetis.TypeKit.Generators.Tests/GeneratedSurface.verified.txt @@ -72,6 +72,7 @@ ProbeArticle implements IEquatable<ProbeArticle> implements IFormattable implements IParsable<ProbeArticle> + implements ISpanParsable<ProbeArticle> implements IValidatedValue<ProbeArticle, String, ProbeArticleFault> implements IValueObject<ProbeArticle, String> implements IValueObjectMaterializer<ProbeArticle, String> @@ -80,8 +81,10 @@ ProbeArticle new() static Create(String) : Result<ProbeArticle, ProbeArticleFault> static FromKnownGood(String, String) : ProbeArticle + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeArticle static Parse(String, IFormatProvider) : ProbeArticle static TryFrom(String) : Option<ProbeArticle> + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeArticle) : Boolean static TryParse(String, IFormatProvider, out ProbeArticle) : Boolean static op_Equality(ProbeArticle, ProbeArticle) : Boolean static op_GreaterThan(ProbeArticle, ProbeArticle) : Boolean @@ -135,6 +138,7 @@ ProbeCode implements IEquatable<ProbeCode> implements IFormattable implements IParsable<ProbeCode> + implements ISpanParsable<ProbeCode> implements IValidatedValue<ProbeCode, String, ProbeCodeFault> implements IValueObject<ProbeCode, String> implements IValueObjectMaterializer<ProbeCode, String> @@ -143,8 +147,10 @@ ProbeCode new() static Create(String) : Result<ProbeCode, ProbeCodeFault> static FromKnownGood(String, String) : ProbeCode + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeCode static Parse(String, IFormatProvider) : ProbeCode static TryFrom(String) : Option<ProbeCode> + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeCode) : Boolean static TryParse(String, IFormatProvider, out ProbeCode) : Boolean static op_Equality(ProbeCode, ProbeCode) : Boolean static op_GreaterThan(ProbeCode, ProbeCode) : Boolean @@ -752,13 +758,16 @@ ProbeLabel implements IFormattable implements IParsable<ProbeLabel> implements IPlainValueObject<ProbeLabel, String> + implements ISpanParsable<ProbeLabel> implements IValue<String> implements IValueObject<ProbeLabel, String> implements IValueObjectMaterializer<ProbeLabel, String> nested ProbeLabelJsonConverter : JsonConverter<ProbeLabel>, IStoredJsonConverterSource nested ProbeLabelTypeConverter : TypeConverter static From(String) : ProbeLabel + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeLabel static Parse(String, IFormatProvider) : ProbeLabel + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeLabel) : Boolean static TryParse(String, IFormatProvider, out ProbeLabel) : Boolean static op_Equality(ProbeLabel, ProbeLabel) : Boolean static op_GreaterThan(ProbeLabel, ProbeLabel) : Boolean @@ -913,6 +922,7 @@ ProbeName implements IFormattable implements IParsable<ProbeName> implements IPlainValueObject<ProbeName, String> + implements ISpanParsable<ProbeName> implements IValue<String> implements IValueObject<ProbeName, String> implements IValueObjectMaterializer<ProbeName, String> @@ -920,7 +930,9 @@ ProbeName nested ProbeNameTypeConverter : TypeConverter new() static From(String) : ProbeName + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeName static Parse(String, IFormatProvider) : ProbeName + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeName) : Boolean static TryParse(String, IFormatProvider, out ProbeName) : Boolean static op_Equality(ProbeName, ProbeName) : Boolean static op_GreaterThan(ProbeName, ProbeName) : Boolean @@ -992,6 +1004,7 @@ ProbeOverride implements IEquatable<ProbeOverride> implements IFormattable implements IParsable<ProbeOverride> + implements ISpanParsable<ProbeOverride> implements IValidatedValue<ProbeOverride, String, ProbeOverrideFault> implements IValueObject<ProbeOverride, String> implements IValueObjectMaterializer<ProbeOverride, String> @@ -1000,8 +1013,10 @@ ProbeOverride new() static Create(String) : Result<ProbeOverride, ProbeOverrideFault> static FromKnownGood(String, String) : ProbeOverride + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeOverride static Parse(String, IFormatProvider) : ProbeOverride static TryFrom(String) : Option<ProbeOverride> + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeOverride) : Boolean static TryParse(String, IFormatProvider, out ProbeOverride) : Boolean static op_Equality(ProbeOverride, ProbeOverride) : Boolean static op_GreaterThan(ProbeOverride, ProbeOverride) : Boolean @@ -1206,13 +1221,16 @@ ProbeReversedLabel implements IFormattable implements IParsable<ProbeReversedLabel> implements IPlainValueObject<ProbeReversedLabel, String> + implements ISpanParsable<ProbeReversedLabel> implements IValue<String> implements IValueObject<ProbeReversedLabel, String> implements IValueObjectMaterializer<ProbeReversedLabel, String> nested ProbeReversedLabelJsonConverter : JsonConverter<ProbeReversedLabel>, IStoredJsonConverterSource nested ProbeReversedLabelTypeConverter : TypeConverter static From(String) : ProbeReversedLabel + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeReversedLabel static Parse(String, IFormatProvider) : ProbeReversedLabel + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeReversedLabel) : Boolean static TryParse(String, IFormatProvider, out ProbeReversedLabel) : Boolean static op_Equality(ProbeReversedLabel, ProbeReversedLabel) : Boolean static op_GreaterThan(ProbeReversedLabel, ProbeReversedLabel) : Boolean @@ -1243,13 +1261,16 @@ ProbeSecret implements IEquatable<ProbeSecret> implements IParsable<ProbeSecret> implements IPlainValueObject<ProbeSecret, String> + implements ISpanParsable<ProbeSecret> implements IValue<String> implements IValueObject<ProbeSecret, String> implements IValueObjectMaterializer<ProbeSecret, String> nested ProbeSecretJsonConverter : JsonConverter<ProbeSecret>, IStoredJsonConverterSource nested ProbeSecretTypeConverter : TypeConverter static From(String) : ProbeSecret + static Parse(ReadOnlySpan<Char>, IFormatProvider) : ProbeSecret static Parse(String, IFormatProvider) : ProbeSecret + static TryParse(ReadOnlySpan<Char>, IFormatProvider, out ProbeSecret) : Boolean static TryParse(String, IFormatProvider, out ProbeSecret) : Boolean static op_Equality(ProbeSecret, ProbeSecret) : Boolean static op_GreaterThan(ProbeSecret, ProbeSecret) : Boolean diff --git a/test/CodoMetis.TypeKit.Generators.Tests/SpanParsingTests.cs b/test/CodoMetis.TypeKit.Generators.Tests/SpanParsingTests.cs new file mode 100644 index 00000000..f966ae2d --- /dev/null +++ b/test/CodoMetis.TypeKit.Generators.Tests/SpanParsingTests.cs @@ -0,0 +1,203 @@ +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Text; +using CodoMetis.TypeKit.Generators.Probes; +using CodoMetis.TypeKit.ValueObjects; + +namespace CodoMetis.TypeKit.Generators.Tests; + +public readonly partial record struct OverLong : IValue<long>; + +public readonly partial record struct OverByte : IValue<byte>; + +public readonly partial record struct OverUInt64 : IValue<ulong>; + +public readonly partial record struct OverChar : IValue<char>; + +/// <summary><see cref="Version"/> reads UTF-8 but neither a string nor a span through an interface: it parses through its string constructor.</summary> +public readonly partial record struct OverVersion : IValue<Version>; + +[Flags] +public enum ProbeAccess +{ + None = 0, + Read = 1, + Write = 2 +} + +public readonly partial record struct OverAccess : IValue<ProbeAccess>; + +/// <summary> +/// Span and UTF-8 parsing are generated exactly where the wrapped type has them, and read and refuse +/// what <c>Parse(string)</c> reads and refuses, one row per wrapped type of the shape catalogue +/// (<c>shape.wrapped</c> in <c>.claude/probe-axes.md</c>) and per parse strategy. +/// </summary> +/// <remarks> +/// A value object over <see cref="string"/> got no <c>ISpanParsable</c>, although <see cref="string"/> +/// implements it: its strategy was picked before the span check and implemented <c>IParsable</c> alone +/// (found by the prober, claim C11, 2026-10-17). +/// </remarks> +public sealed class SpanParsingTests +{ + private const string Secret = "SECRET-7f3a"; + + /// <param name="ValueObject">The value object.</param> + /// <param name="Wrapped">The type it wraps.</param> + /// <param name="Valid">Text the value object reads.</param> + /// <param name="Refused">Text the value object refuses, the wrapped type's refusal or <c>Create</c>'s, none for a plain string.</param> + public sealed record Wrapping(Type ValueObject, Type Wrapped, string Valid, params string[] Refused); + + private static readonly Dictionary<string, Wrapping> Cases = new() + { + ["string"] = new(typeof(ProbeName), typeof(string), "text"), + ["string, record class"] = new(typeof(ProbeLabel), typeof(string), "text"), + ["string, validated"] = new(typeof(ProbeCode), typeof(string), " ABC ", "s3cr3t"), + ["string, fault set"] = new(typeof(ProbeArticle), typeof(string), " ABC ", "s3cr3t"), + ["Guid"] = new(typeof(ProbeId), typeof(Guid), "0199f3a4-8c5e-7b2a-9d41-3c6e5f708192", Secret), + ["int"] = new(typeof(ProbeCount), typeof(int), "42", Secret), + ["int, validated"] = new(typeof(ProbePercentage), typeof(int), "50", Secret, "1010101"), + ["long"] = new(typeof(OverLong), typeof(long), "42", Secret), + ["byte"] = new(typeof(OverByte), typeof(byte), "42", Secret, "2560"), + ["ulong"] = new(typeof(OverUInt64), typeof(ulong), "42", Secret, "-4242"), + ["decimal"] = new(typeof(ProbeAmount), typeof(decimal), "1.5", Secret), + ["double"] = new(typeof(ProbeRatio), typeof(double), "0.25", Secret), + ["bool"] = new(typeof(ProbeFlag), typeof(bool), "true", Secret), + ["char"] = new(typeof(OverChar), typeof(char), "c", Secret), + ["DateTime"] = new(typeof(ProbeTimestamp), typeof(DateTime), "2026-10-17T12:00:00", Secret), + ["DateTimeOffset"] = new(typeof(ProbeMoment), typeof(DateTimeOffset), "2026-10-17T12:00:00+02:00", Secret), + ["DateOnly"] = new(typeof(ProbeDate), typeof(DateOnly), "2026-10-17", Secret), + ["TimeOnly"] = new(typeof(ProbeTime), typeof(TimeOnly), "12:30", Secret), + ["TimeSpan"] = new(typeof(ProbeDuration), typeof(TimeSpan), "01:02:03", Secret), + ["enum"] = new(typeof(ProbeWeekday), typeof(DayOfWeek), "Monday", Secret), + ["flags enum"] = new(typeof(OverAccess), typeof(ProbeAccess), "Read, Write", Secret), + ["Uri"] = new(typeof(ProbeUri), typeof(Uri), "/orders/7", "http://[SECRET-7f3a"), + ["NodaTime.Instant"] = new(typeof(ProbeInstant), typeof(NodaTime.Instant), "2026-10-17T12:00:00Z", Secret), + ["Version"] = new(typeof(OverVersion), typeof(Version), "1.2.3", Secret), + ["own IParsable class"] = new(typeof(ProbeSpan), typeof(ProbeSpanText), "1..2", Secret), + ["own static Parse"] = new(typeof(ProbeSku), typeof(ProbeSkuText), "ABC-123", Secret), + ["own string constructor"] = new(typeof(ProbeIsbn), typeof(ProbeIsbnText), "9783161484100", Secret), + ["closed set"] = new(typeof(OverProbeStatus), typeof(ProbeStatus), "Shipped", Secret), + }; + + public static TheoryData<string> CaseNames => [.. Cases.Keys]; + + [Theory] + [MemberData(nameof(CaseNames))] + public void Span_and_UTF_8_parsing_are_generated_exactly_where_the_wrapped_type_has_them(string name) + { + var (valueObject, wrapped, _, _) = Cases[name]; + + SpanParsing.Implements(valueObject, typeof(ISpanParsable<>)).ShouldBe( + SpanParsing.Implements(wrapped, typeof(ISpanParsable<>)), + $"{valueObject.Name} and {wrapped.Name} disagree on ISpanParsable"); + + SpanParsing.Implements(valueObject, typeof(IUtf8SpanParsable<>)).ShouldBe( + SpanParsing.Implements(wrapped, typeof(IUtf8SpanParsable<>)), + $"{valueObject.Name} and {wrapped.Name} disagree on IUtf8SpanParsable"); + } + + [Theory] + [MemberData(nameof(CaseNames))] + public void Span_and_UTF_8_parsing_read_what_Parse_reads(string name) + { + var (valueObject, _, valid, _) = Cases[name]; + var parsed = SpanParsing.Parse(valueObject, valid); + + foreach (var (entry, parse, tryParse) in SpanParsing.EntriesOf(valueObject)) + { + parse(valid).ShouldBe(parsed, $"{entry}.Parse"); + + var (ok, result) = tryParse(valid); + ok.ShouldBeTrue($"{entry}.TryParse"); + result.ShouldBe(parsed, $"{entry}.TryParse"); + } + } + + /// <summary> + /// The same refusal as <c>Parse(string)</c>'s, the wrapped type's or <c>Create</c>'s: a + /// <see cref="FormatException"/> naming the value object, with no inner exception, never quoting the + /// input; and a <see langword="false"/> from <c>TryParse</c>, which throws nothing. + /// </summary> + [Theory] + [MemberData(nameof(CaseNames))] + public void Span_and_UTF_8_parsing_refuse_what_Parse_refuses_without_quoting_it(string name) + { + var (valueObject, _, _, refused) = Cases[name]; + + foreach (var text in refused) + { + var expected = Should.Throw<FormatException>(() => SpanParsing.Parse(valueObject, text)); + + foreach (var (entry, parse, tryParse) in SpanParsing.EntriesOf(valueObject)) + { + var exception = Should.Throw<FormatException>(() => parse(text), $"{entry}.Parse('{text}')"); + + exception.Message.ShouldBe(expected.Message, $"{entry}.Parse('{text}')"); + exception.Message.ShouldContain(valueObject.Name); + exception.Message.ShouldNotContain(text, Case.Insensitive); + exception.InnerException.ShouldBeNull(); + + tryParse(text).Ok.ShouldBeFalse($"{entry}.TryParse('{text}')"); + } + } + } +} + +/// <summary> +/// A value object's <c>Parse</c>/<c>TryParse</c> through <c>IParsable</c>, <c>ISpanParsable</c> and +/// <c>IUtf8SpanParsable</c>, by reflection, so a test of a value object that lacks one compiles and fails +/// rather than not compiling. +/// </summary> +internal static class SpanParsing +{ + public static bool Implements(Type type, Type definition) => + type.GetInterfaces().Any(implemented => implemented.IsGenericType && implemented.GetGenericTypeDefinition() == definition && implemented.GenericTypeArguments[0] == type); + + /// <summary><c>IParsable<T>.Parse(text, null)</c>.</summary> + public static object Parse(Type type, string text) => Call(nameof(ParseText), typeof(IParsable<>), type, text); + + /// <summary><c>ISpanParsable<T>.Parse(text, null)</c> over the characters.</summary> + public static object ParseSpan(Type type, string text) => Call(nameof(ParseChars), typeof(ISpanParsable<>), type, text); + + public static (bool Ok, object? Result) TryParseSpan(Type type, string text) => ((bool, object?))Call(nameof(TryParseChars), typeof(ISpanParsable<>), type, text); + + /// <summary><c>IUtf8SpanParsable<T>.Parse(text, null)</c> over the text's UTF-8.</summary> + public static object ParseUtf8(Type type, string text) => Call(nameof(ParseBytes), typeof(IUtf8SpanParsable<>), type, text); + + public static (bool Ok, object? Result) TryParseUtf8(Type type, string text) => ((bool, object?))Call(nameof(TryParseBytes), typeof(IUtf8SpanParsable<>), type, text); + + /// <summary>The span and UTF-8 entry points <paramref name="type"/> has.</summary> + public static IEnumerable<(string Entry, Func<string, object> Parse, Func<string, (bool Ok, object? Result)> TryParse)> EntriesOf(Type type) + { + if (Implements(type, typeof(ISpanParsable<>))) + yield return ("ISpanParsable", text => ParseSpan(type, text), text => TryParseSpan(type, text)); + + if (Implements(type, typeof(IUtf8SpanParsable<>))) + yield return ("IUtf8SpanParsable", text => ParseUtf8(type, text), text => TryParseUtf8(type, text)); + } + + private static object Call(string helper, Type definition, Type type, string text) + { + Implements(type, definition).ShouldBeTrue($"{type.Name} does not implement {definition.Name}"); + + try + { + return typeof(SpanParsing).GetMethod(helper, BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(type).Invoke(null, [text])!; + } + catch (TargetInvocationException exception) when (exception.InnerException is not null) + { + ExceptionDispatchInfo.Throw(exception.InnerException); + throw; + } + } + + private static object ParseText<T>(string text) where T : IParsable<T> => T.Parse(text, null); + + private static object ParseChars<T>(string text) where T : ISpanParsable<T> => T.Parse(text.AsSpan(), null); + + private static (bool, object?) TryParseChars<T>(string text) where T : ISpanParsable<T> => (T.TryParse(text.AsSpan(), null, out var result), result); + + private static object ParseBytes<T>(string text) where T : IUtf8SpanParsable<T> => T.Parse(Encoding.UTF8.GetBytes(text), null); + + private static (bool, object?) TryParseBytes<T>(string text) where T : IUtf8SpanParsable<T> => (T.TryParse(Encoding.UTF8.GetBytes(text), null, out var result), result); +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/ClaimsFileTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/ClaimsFileTests.cs new file mode 100644 index 00000000..c7fa558b --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/ClaimsFileTests.cs @@ -0,0 +1,159 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// claims.json is read against its schema, and each exclusion carries the reason a reviewer reads (decision 102): +/// a field nobody reads is refused, not kept, and a publish rewrites the file only where it changed. +/// </summary> +public sealed class ClaimsFileTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private void Claims(JsonObject claim) + { + var c = new JsonObject { ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s") }; + foreach (var (key, value) in claim) c[key] = value?.DeepClone(); + fx.Write("tools/probe/corpus/claims.json", new JsonArray(c).ToJsonString()); + } + + private static JsonArray Pairs(params (string A, string B)[] pairs) => new([.. pairs.Select(p => (JsonNode)new JsonArray(p.A, p.B))]); + + [Theory] + [InlineData(null, "claim C1: exclude[0].reason is required")] + [InlineData("", "claim C1: exclude[0].reason does not match")] + [InlineData(" ", "claim C1: exclude[0].reason does not match")] + public void An_exclusion_without_a_reason_is_refused_naming_the_claim(string? reason, string expected) + { + var exclusion = new JsonObject { ["pairs"] = Pairs(("x=1", "y=2")) }; + if (reason is not null) exclusion["reason"] = reason; + Claims(new JsonObject { ["exclude"] = new JsonArray(exclusion) }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain(expected); + } + + [Fact] + public void A_bare_pair_is_refused_as_an_exclusion_without_its_reason() + { + Claims(new JsonObject { ["exclude"] = Pairs(("x=1", "y=2")) }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("claim C1: exclude[0] is not an object"); + } + + [Theory] + [InlineData("excludeReasons")] + [InlineData("excludeWhy")] + public void A_reason_field_of_its_own_is_refused(string field) + { + Claims(new JsonObject + { + ["exclude"] = new JsonArray(new JsonObject { ["reason"] = "No row holds both.", ["pairs"] = Pairs(("x=1", "y=2")) }), + [field] = new JsonArray(new JsonObject { ["match"] = "x=1 x y=2", ["reason"] = "No row holds both." }), + }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain($"claim C1: {field} is not a field the schema has"); + } + + [Fact] + public void A_field_of_its_own_in_an_exclusion_is_refused() + { + Claims(new JsonObject + { + ["exclude"] = new JsonArray(new JsonObject { ["reason"] = "No row holds both.", ["why"] = "twice", ["pairs"] = Pairs(("x=1", "y=2")) }), + }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("claim C1: exclude[0].why is not a field the schema has"); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void The_universe_shows_each_exclusion_with_its_reason(string newLine) + { + Claims(new JsonObject + { + ["exclude"] = new JsonArray(new JsonObject { ["reason"] = "No row holds x=1 with y=2.", ["pairs"] = Pairs(("y=2", "x=1")) }), + }); + var (exit, output, error) = fx.LedgerEnding([], newLine, "universe", "--base", "HEAD"); + exit.ShouldBe(0, error); + output.ReplaceLineEndings("\n").ShouldContain("C1: 2 axes, 3 pairs\n exclude 1 pair(s), first x=1 y=2: No row holds x=1 with y=2.\n"); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void The_universe_shows_each_narrowing_with_its_quote(string newLine) + { + Claims(new JsonObject { ["narrow"] = new JsonObject { ["y"] = "The claim." } }); + var (exit, output, error) = fx.LedgerEnding([], newLine, "universe", "--base", "HEAD"); + exit.ShouldBe(0, error); + output.ReplaceLineEndings("\n").ShouldContain("C1: 1 axes, 0 pairs\n narrow y: \"The claim.\"\n"); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void A_publish_that_changes_nothing_keeps_the_file_as_it_is(string staged) + { + // The form publish writes, by hand: a reason with a backtick, a plus and angle brackets, all literal, and \n line + // ends, whatever this source file was checked out with. The staged copy may come with either line end. + var tracked = """ + [ + { + "id": "C1", + "source": "README.md#a", + "surfaces": [ + "s" + ], + "exclude": [ + { + "reason": "A `+` row and an Option<T> row cannot meet.", + "pairs": [ + [ + "x=1", + "y=2" + ] + ] + } + ] + } + ] + + """.ReplaceLineEndings("\n"); + fx.Write("tools/probe/corpus/claims.json", tracked); + fx.Commit("claims"); + fx.Write(".claude/agent-memory-local/prober/branches/main/staging/claims.json", tracked.ReplaceLineEndings(staged)); + var (exit, _, error) = fx.Ledger("publish"); + exit.ShouldBe(0, error); + fx.Sh("git", "diff", "--exit-code", "--", "tools/probe/corpus/claims.json"); + File.ReadAllText(Path.Combine(fx.Root, "tools/probe/corpus/claims.json")).ShouldBe(tracked, "byte for byte: the escapes and the line ends are the ones publish writes"); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void The_corpus_claims_match_their_schema_in_the_form_publish_writes(string checkedOut) + { + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "CodoMetis.TypeKit.slnx"))) root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("no CodoMetis.TypeKit.slnx above the test output"); + var path = Path.Combine(root, "tools/probe/corpus/claims.json"); + var claims = Ledger.ReadClaims(path, "tools/probe/corpus/claims.json"); + claims.ShouldNotBeEmpty(); + // Git may check the file out with either line end (core.autocrlf on Windows); what it stores, and diffs, has \n. + var text = File.ReadAllText(path).ReplaceLineEndings(checkedOut); + Files.Format(JsonNode.Parse(text)!).ShouldBe(text.ReplaceLineEndings("\n"), "claims.json is in the form `prober publish` writes, so a publish diffs only what it changes"); + } + + [Fact] + public void A_schema_keyword_the_check_does_not_read_is_an_error_not_a_pass() + { + var schema = new JsonObject { ["type"] = "string", ["maxLength"] = 3 }; + Should.Throw<InvalidOperationException>(() => SchemaCheck.Problems("abcd", schema)).Message.ShouldContain("maxLength"); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/CodoMetis.TypeKit.Prober.Tests.csproj b/test/CodoMetis.TypeKit.Prober.Tests/CodoMetis.TypeKit.Prober.Tests.csproj index 57e6c47e..2126c5f2 100644 --- a/test/CodoMetis.TypeKit.Prober.Tests/CodoMetis.TypeKit.Prober.Tests.csproj +++ b/test/CodoMetis.TypeKit.Prober.Tests/CodoMetis.TypeKit.Prober.Tests.csproj @@ -9,6 +9,14 @@ <ProjectReference Include="..\..\tools\Prober\Prober.csproj" /> </ItemGroup> + <!-- + The probe corpus's row harness, compiled in as the replay compiles it into the Samples, so HarnessErrorTests + runs the real Rec.Run and feeds the lines it writes to `seeds` (decision 103). + --> + <ItemGroup> + <Compile Include="../../tools/probe/corpus/harness/Row.cs" Link="Harness/Row.cs" /> + </ItemGroup> + <!-- The stale-tool test runs the built prober.dll from a throwaway repository (ToolSandbox.cs); ToolOutput says where the output is. --> <ItemGroup> <Compile Include="../CodoMetis.TypeKit.BuildVerdict.Tests/ToolSandbox.cs" Link="ToolSandbox.cs" /> diff --git a/test/CodoMetis.TypeKit.Prober.Tests/CoveringTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/CoveringTests.cs new file mode 100644 index 00000000..0a824ae1 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/CoveringTests.cs @@ -0,0 +1,80 @@ +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// The covering array never puts an excluded pair or one no obligation wants in a row, not even to fill an axis whose every +/// value would bring one (#54 and #59 saw `rows` place them): such an axis is left out of that row, and an axis with no wanted +/// pair at all is left out of every row. +/// </summary> +public sealed class CoveringTests +{ + private static Inapplicability Declared(string cite, params (string Axis, string[] Values)[] axes) => + new(axes.ToDictionary(a => a.Axis, a => a.Values.ToList(), StringComparer.Ordinal), cite); + + private static Claim Over(string[] axes, IEnumerable<Pair>? excluded = null, params Inapplicability[] declared) => new() + { + Id = "C1", Source = "README.md#a", Surfaces = ["s"], Watched = [], Excluded = [.. excluded ?? []], + Obligations = declared.Length == 0 ? [] : [new("keys", "The claim.", new SortedDictionary<string, List<string>>(StringComparer.Ordinal)) { Inapplicable = declared }], + Axes = new(axes.ToDictionary(a => a, _ => new List<string> { "1", "2" }), StringComparer.Ordinal), + }; + + private static bool Holds(IReadOnlyDictionary<string, string> row, Pair pair) => + new[] { pair.A, pair.B }.All(item => row.TryGetValue(item[..item.IndexOf('=', StringComparison.Ordinal)], out var v) && v == item[(item.IndexOf('=', StringComparison.Ordinal) + 1)..]); + + private static IEnumerable<string> Shown(IEnumerable<Pair> pairs) => pairs.Select(p => p.ToString()).Order(StringComparer.Ordinal); + + [Fact] + public void An_axis_whose_every_value_brings_an_excluded_pair_is_left_out_of_the_row() + { + // x=1 goes with no y (as shape.containing goes with no shape.position=root): a row holding x=1 holds no y. + Pair[] excluded = [new("x=1", "y=1"), new("x=1", "y=2")]; + var claim = Over(["x", "y", "z"], excluded); + for (var seed = 1; seed <= 20; seed++) + { + var rows = Covering.Rows(claim, claim.Pairs(), 2000, seed); + rows.ShouldNotContain(r => excluded.Any(p => Holds(r.Row, p)), $"seed {seed}"); + rows.Where(r => r.Row["x"] == "1").ShouldAllBe(r => !r.Row.ContainsKey("y"), $"seed {seed}"); + Shown(rows.SelectMany(r => r.Pairs)).ShouldBe(Shown(claim.Pairs()), $"seed {seed}: every pair once, x=1 with z among them"); + } + } + + [Fact] + public void An_axis_whose_every_value_brings_a_pair_no_obligation_wants_is_left_out_of_the_row() + { + var claim = Over(["x", "y", "z"], null, Declared("record:0001", ("x", ["1"]), ("y", ["1", "2"]))); + var dead = claim.DeclaredInapplicable().Keys.ToList(); + dead.Count.ShouldBe(2); + for (var seed = 1; seed <= 20; seed++) + { + var rows = Covering.Rows(claim, claim.PairsWanted(), 2000, seed); + rows.ShouldNotContain(r => dead.Any(p => Holds(r.Row, p)), $"seed {seed}"); + Shown(rows.SelectMany(r => r.Pairs)).ShouldBe(Shown(claim.PairsWanted()), $"seed {seed}"); + } + } + + [Fact] + public void An_axis_with_no_wanted_pair_is_in_no_row() + { + // every pair of z is excluded (as shape.refused-kind is where the refusal is not the claim): rows over x and y alone. + var claim = Over(["x", "y", "z"], [.. from x in new[] { "x=1", "x=2", "y=1", "y=2" } from z in new[] { "z=1", "z=2" } select new Pair(x, z)]); + for (var seed = 1; seed <= 5; seed++) + { + var rows = Covering.Rows(claim, claim.Pairs(), 2000, seed); + rows.ShouldAllBe(r => !r.Row.ContainsKey("z"), $"seed {seed}"); + Shown(rows.SelectMany(r => r.Pairs)).ShouldBe(["x=1 y=1", "x=1 y=2", "x=2 y=1", "x=2 y=2"], $"seed {seed}"); + } + } + + [Fact] + public void A_seeded_row_leaves_out_an_axis_no_value_of_which_can_join_it() + { + // c=1 goes with no d: the row seeded from a=1 c=1 is a, b and c. + var claim = Over(["a", "b", "c", "d"], [new("c=1", "d=1"), new("c=1", "d=2")]); + Covering.Seeded(claim, new Pair("a=1", "c=1"), claim.DeclaredInapplicable()) + .ShouldBe(new SortedDictionary<string, string>(StringComparer.Ordinal) { ["a"] = "1", ["b"] = "1", ["c"] = "1" }); + Covering.Seeded(claim, new Pair("a=1", "b=2"), claim.DeclaredInapplicable()) + .ShouldBe(new SortedDictionary<string, string>(StringComparer.Ordinal) { ["a"] = "1", ["b"] = "2", ["c"] = "1" }, + "c=1 is c's first value, and d then has none"); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/DeclaredApplicabilityTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/DeclaredApplicabilityTests.cs new file mode 100644 index 00000000..aae5e585 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/DeclaredApplicabilityTests.cs @@ -0,0 +1,439 @@ +using System.Text.Json.Nodes; +using Prober; +using Probes.Shapes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// An obligation declares where it does not apply, as axis values with a citation: <c>Axes.Build</c> holds the +/// declaration to the claim's axes, values and not-promised list; a pair is wanted by an obligation unless one of +/// its declarations matches it, and a pair no obligation wants is declared inapplicable, which `universe`, `gaps` +/// and `status` show with its citations, the gate leaves out as it leaves out an exclusion, and the covering +/// generator never puts in a row. Fixture: C1 over x and y, each 1 and 2, so four pairs. +/// </summary> +public sealed class DeclaredApplicabilityTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private static JsonObject Declared(string cite, params (string Axis, string[] Values)[] axes) => new() + { + ["axes"] = new JsonObject(axes.Select(a => KeyValuePair.Create(a.Axis, (JsonNode?)new JsonArray([.. a.Values.Select(v => (JsonNode)v)])))), + ["cite"] = cite, + }; + + private static JsonObject Obligation(string id, params JsonObject[] inapplicable) + { + var o = new JsonObject { ["id"] = id, ["sentence"] = "The claim." }; + if (inapplicable.Length > 0) o["inapplicable"] = new JsonArray([.. inapplicable]); + return o; + } + + /// <summary>C1 with the obligations given and one not-promised entry, <c>no-one</c>.</summary> + private void Claim(params JsonObject[] obligations) => + fx.Write("tools/probe/corpus/claims.json", new JsonArray(new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray([.. obligations]), + ["not-promised"] = new JsonArray(new JsonObject { ["id"] = "no-one", ["source"] = "README.md#a", ["sentence"] = "The claim." }), + }).ToJsonString()); + + /// <summary> + /// keys does not apply where x=1 and y=1 (one declaration over both axes), nulls nowhere x=1: the pair x=1 y=1 is + /// wanted by neither, x=1 y=2 by keys alone. + /// </summary> + private void OnePairWantedByNone() => Claim( + Obligation("keys", Declared("docs:README.md#a", ("x", ["1"]), ("y", ["1"]))), + Obligation("nulls", Declared("not-promised:no-one", ("x", ["1"])))); + + private string Refused() + { + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + return error; + } + + [Fact] + public void A_declaration_naming_an_axis_the_claim_does_not_have_is_refused() + { + Claim(Obligation("keys", Declared("not-promised:no-one", ("z", ["1"])))); + Refused().ShouldContain("claim C1: obligation keys: inapplicable[0] names axis z, which the claim does not have"); + } + + [Fact] + public void A_declaration_naming_a_value_the_claim_does_not_list_is_refused() + { + Claim(Obligation("keys", Declared("not-promised:no-one", ("x", ["1"]))), Obligation("nulls", Declared("not-promised:no-one", ("x", ["3"])))); + Refused().ShouldContain("claim C1: obligation nulls: inapplicable[0] names x=3, a value the claim does not list for x"); + } + + [Theory] + [InlineData("fact:KB-31", "is not a knowledge entry's id (fact:KB-031)")] + [InlineData("record:94", "is not a decision record's number (record:0094)")] + [InlineData("docs:../README.md#a", "is not a docs source (docs:docs/json.md#decimals-in-one-spelling)")] + [InlineData("not-promised:No-one", "is not a not-promised id (not-promised:closed-set-has-no-text)")] + public void A_malformed_cite_is_refused(string cite, string why) + { + Claim(Obligation("keys", Declared(cite, ("x", ["1"])))); + Refused().ShouldContain($"claim C1: obligation keys: inapplicable[0]: cite {cite} {why}"); + } + + [Fact] + public void A_cite_of_a_not_promised_id_the_claim_lacks_is_refused() + { + Claim(Obligation("keys", Declared("not-promised:no-such-entry", ("x", ["1"])))); + Refused().ShouldContain("claim C1: obligation keys: inapplicable[0] cites not-promised:no-such-entry, which the claim's not-promised list does not hold"); + } + + [Fact] + public void A_field_a_declaration_does_not_have_is_refused_by_the_schema() + { + var declaration = Declared("not-promised:no-one", ("x", ["1"])); + declaration["reason"] = "x=1 has no key"; + Claim(Obligation("keys", declaration)); + Refused().ShouldContain("claim C1: obligations[0].inapplicable[0].reason is not a field the schema has"); + } + + [Fact] + public void A_pair_wanted_by_no_obligation_is_listed_by_universe_with_its_citations_and_the_claim_keeps_its_pairs() + { + OnePairWantedByNone(); + var (exit, output, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(0, error); + output.ReplaceLineEndings("\n").ShouldContain( + "C1: 2 axes, 4 pairs\n inapplicable 1 pair(s), wanted by no obligation, first x=1 y=1: docs:README.md#a, not-promised:no-one\n"); + } + + [Fact] + public void Gaps_lists_the_pairs_no_obligation_wants_under_their_own_heading() + { + OnePairWantedByNone(); + var (exit, output, error) = fx.Ledger("gaps", "--base", "HEAD"); + exit.ShouldBe(0, error); + output.ReplaceLineEndings("\n").ShouldContain( + "pairs no obligation wants (declared inapplicable): 1\n C1: inapplicable 1 pair(s), wanted by no obligation, first x=1 y=1: docs:README.md#a, not-promised:no-one\n"); + } + + private static Dictionary<string, string> R(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + + /// <summary>P passes rows over the three pairs some obligation wants, never x=1 y=1.</summary> + private void RecordTheWantedPairs() + { + fx.Rows([("1", "2"), ("2", "1"), ("2", "2")]); + fx.Commit("claims and rows"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + fx.Footprint(); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", fx.Evidence([R("1", "2"), R("2", "1"), R("2", "2")])).Exit.ShouldBe(0); + } + + [Fact] + public void Status_reports_the_inapplicable_pairs_and_is_done_once_every_wanted_pair_is_current() + { + OnePairWantedByNone(); + RecordTheWantedPairs(); + var (exit, output, error) = fx.Ledger("status", "--json"); + exit.ShouldBe(0, error); + var status = JsonNode.Parse(output)!; + var c1 = status["claims"]!["C1"]!; + c1["pairs"]!.GetValue<int>().ShouldBe(4, "the claim's pairs stay its pairs"); + c1["current"]!.GetValue<int>().ShouldBe(3); + c1["inapplicable_pairs"]!.GetValue<int>().ShouldBe(1); + status["inapplicable_pairs"]!.GetValue<int>().ShouldBe(1); + status["done"]!.GetValue<bool>().ShouldBeTrue("the one pair not current is wanted by no obligation"); + fx.Ledger("status").Out.ShouldContain(" C1: 3/4, 1 inapplicable"); + } + + [Fact] + public void Status_is_not_done_while_a_wanted_pair_is_not_current() + { + Claim(Obligation("keys"), Obligation("nulls")); + RecordTheWantedPairs(); + var status = JsonNode.Parse(fx.Ledger("status", "--json").Out)!; + status["claims"]!["C1"]!["inapplicable_pairs"]!.GetValue<int>().ShouldBe(0); + status["done"]!.GetValue<bool>().ShouldBeFalse("x=1 y=1 is wanted and not current"); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void The_gate_leaves_out_a_pair_wanted_by_no_obligation_as_it_leaves_out_an_exclusion(bool declared) + { + fx.Sh("git", "tag", "v1"); + if (declared) OnePairWantedByNone(); else Claim(Obligation("keys"), Obligation("nulls")); + RecordTheWantedPairs(); + fx.Write("README.md", "# a\n\nThe claim.\n\nMore words.\n"); + fx.Commit("touch the claim"); + + var (exit, output, _) = fx.Ledger("gate", "--since", "v1", "--json"); + var gate = JsonNode.Parse(output)!; + gate["touched"]!.AsArray().Select(c => c!.GetValue<string>()).ShouldBe(["C1"]); + if (declared) + { + exit.ShouldBe(0, output); + gate["uncovered_pairs"]!.GetValue<int>().ShouldBe(0); + gate["inapplicable_pairs"]!.GetValue<int>().ShouldBe(1); + } + else + { + exit.ShouldBe(1); + gate["reasons"]!.AsArray().Select(r => r!.GetValue<string>()).ShouldContain("C1: 1 of 4 pairs uncovered (touched since v1)"); + } + } + + [Fact] + public void Rows_for_a_claim_ask_for_no_pair_no_obligation_wants() + { + OnePairWantedByNone(); + fx.Commit("claims"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + var rows = JsonNode.Parse(fx.Ledger("rows", "--claim", "C1", "--full").Out)!; + rows["rows"]!.AsArray().Select(r => $"{r!["row"]!["x"]} {r["row"]!["y"]}").ShouldNotContain("1 1"); + rows["uncovered_after"]!.GetValue<int>().ShouldBe(0, "a pair wanted by no obligation is not left uncovered"); + } + + [Fact] + public void A_declaration_changes_the_identity_of_its_claim_s_probes() + { + Claim(Obligation("keys"), Obligation("nulls")); + fx.Commit("claims"); + var before = fx.Ledger("identity", "--probe", "P").Out; + OnePairWantedByNone(); + fx.Commit("declared"); + fx.Ledger("identity", "--probe", "P").Out.ShouldNotBe(before, "Row.Check applies the declarations, so a result under others is not this one"); + } + + [Fact] + public void The_overlay_stages_the_claims_beside_the_rows_with_the_staging_s_over_the_corpus_s() + { + Claim(Obligation("keys"), Obligation("nulls")); + fx.Commit("claims"); + var ledger = new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), "main"); + var staged = new JsonArray(new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray(Obligation("keys", Declared("docs:README.md#a", ("x", ["1"])))), + }); + fx.Write(Path.GetRelativePath(fx.Root, Path.Combine(ledger.StagingDir, "claims.json")), staged.ToJsonString()); + var worktree = fx.Root + "-overlay"; + try + { + var probeDir = global::Prober.Replay.Overlay(ledger, new JsonObject { ["probeDir"] = "probes" }, worktree); + var claims = JsonNode.Parse(File.ReadAllText(Path.Combine(probeDir, "claims.json")))!.AsArray(); + JsonNode.DeepEquals(claims, staged).ShouldBeTrue(claims.ToJsonString()); + global::Prober.Replay.Overlay(ledger, new JsonObject { ["probeDir"] = "probes" }, worktree); + File.Exists(Path.Combine(probeDir, "claims.json")).ShouldBeTrue("a second overlay keeps it"); + } + finally + { + try { Directory.Delete(worktree, recursive: true); } catch (IOException) { } + } + } +} + +/// <summary>The wanted pairs of a claim, computed on the claim itself, and the covering generator over them.</summary> +public sealed class WantedPairsTests +{ + private static Inapplicability Declared(string cite, params (string Axis, string[] Values)[] axes) => + new(axes.ToDictionary(a => a.Axis, a => a.Values.ToList(), StringComparer.Ordinal), cite); + + private static global::Prober.Obligation Obligation(string id, params Inapplicability[] inapplicable) => + new(id, "The claim.", new SortedDictionary<string, List<string>>(StringComparer.Ordinal)) { Inapplicable = inapplicable }; + + private static Claim Over(int axes, params global::Prober.Obligation[] obligations) => new() + { + Id = "C1", Source = "README.md#a", Surfaces = ["s"], Watched = [], Excluded = [], Obligations = obligations, + Axes = new(new[] { "x", "y", "z" }.Take(axes).ToDictionary(a => a, _ => new List<string> { "1", "2" }), StringComparer.Ordinal), + }; + + private static IEnumerable<string> Shown(IEnumerable<Pair> pairs) => pairs.Select(p => p.ToString()).Order(StringComparer.Ordinal); + + [Fact] + public void An_obligation_wants_the_pairs_none_of_its_declarations_matches_on_either_side() + { + // keys: not where x=1 (the first of a pair, ordinally); nulls: not where y=1 (the second); counts: nowhere declared. + var claim = Over(2, Obligation("keys", Declared("record:0001", ("x", ["1"]))), Obligation("nulls", Declared("record:0002", ("y", ["1"]))), Obligation("counts")); + Shown(claim.PairsWantedBy("keys")).ShouldBe(["x=2 y=1", "x=2 y=2"]); + Shown(claim.PairsWantedBy("nulls")).ShouldBe(["x=1 y=2", "x=2 y=2"]); + Shown(claim.PairsWantedBy("counts")).ShouldBe(["x=1 y=1", "x=1 y=2", "x=2 y=1", "x=2 y=2"]); + claim.Pairs().Count.ShouldBe(4, "the claim's pairs stay its pairs"); + claim.DeclaredInapplicable().ShouldBeEmpty("counts wants every pair"); + } + + [Fact] + public void A_pair_no_obligation_wants_is_declared_inapplicable_with_the_citations_that_match_it() + { + // keys wants x=2 y=1 alone, nulls x=1 y=2 alone. + var claim = Over(2, + Obligation("keys", Declared("record:0001", ("x", ["1"])), Declared("record:0003", ("y", ["2"]))), + Obligation("nulls", Declared("record:0002", ("y", ["1"])), Declared("record:0004", ("x", ["2"])))); + Shown(claim.PairsWanted()).ShouldBe(["x=1 y=2", "x=2 y=1"]); + var dead = claim.DeclaredInapplicable(); + Shown(dead.Keys).ShouldBe(["x=1 y=1", "x=2 y=2"]); + dead[new Pair("x=1", "y=1")].ShouldBe(["record:0001", "record:0002"]); + dead[new Pair("x=2", "y=2")].ShouldBe(["record:0003", "record:0004"]); + } + + [Fact] + public void A_declaration_over_two_axes_matches_only_the_pair_holding_both() + { + var claim = Over(3, Obligation("keys", Declared("record:0001", ("x", ["1"]), ("y", ["1"])))); + Shown(claim.DeclaredInapplicable().Keys).ShouldBe(["x=1 y=1"], "x=1 with z, or y=1 with z, is in rows where the other axis is 2"); + } + + [Fact] + public void The_covering_rows_never_hold_a_pair_no_obligation_wants_and_count_each_wanted_pair_once() + { + // Both obligations want the same eleven pairs: none of x=1 with y=1. + var claim = Over(3, Obligation("keys", Declared("record:0001", ("x", ["1"]), ("y", ["1"]))), Obligation("nulls", Declared("record:0002", ("x", ["1"]), ("y", ["1"])))); + for (var seed = 1; seed <= 20; seed++) + { + var rows = Covering.Rows(claim, claim.Pairs(), 2000, seed); + rows.ShouldNotContain(r => r.Row["x"] == "1" && r.Row["y"] == "1", $"seed {seed}"); + var gained = rows.SelectMany(r => r.Pairs).ToList(); + gained.Count.ShouldBe(11, $"seed {seed}: each wanted pair once"); + Shown(gained).ShouldBe(Shown(claim.PairsWanted()), $"seed {seed}"); + } + } + + [Fact] + public void A_seeded_row_fills_each_other_axis_with_a_value_that_puts_no_unwanted_pair_in_it() + { + var claim = Over(3, Obligation("keys", Declared("record:0001", ("x", ["1"]), ("z", ["1"])))); + var row = Covering.Seeded(claim, new Pair("y=1", "z=1"), claim.DeclaredInapplicable()); + row.ShouldBe(new SortedDictionary<string, string>(StringComparer.Ordinal) { ["x"] = "2", ["y"] = "1", ["z"] = "1" }); + } + + [Theory] + [InlineData("not-promised:closed-set-has-no-text")] + [InlineData("not-promised:Closed")] + [InlineData("docs:docs/json.md#decimals-in-one-spelling")] + [InlineData("docs:README.md")] + [InlineData("docs:README.md#What TypeKit guarantees")] + [InlineData("docs:/etc/passwd.md")] + [InlineData("docs:../README.md#a")] + [InlineData("fact:KB-031")] + [InlineData("fact:KB-31")] + [InlineData("record:0094")] + [InlineData("record:94")] + [InlineData("README.md#a")] + [InlineData("note:x")] + public void A_cite_the_universe_accepts_is_one_the_harness_can_cite(string cite) + { + var harness = Record.Exception(() => Cite.Parse(cite)) is null; + (Axes.CiteProblem(cite) is null).ShouldBe(harness, harness ? "the harness cites it" : "the harness refuses it"); + } +} + +/// <summary> +/// The harness applies the claim's declarations before a check's body runs: where one matches the row's values the +/// check is inapplicable with the declaration's citation, for that check only, and the body does not run. Without a +/// staged claims file, as for a probe these tests run in-process, no claim declares anything. +/// </summary> +[Collection(nameof(ProbeOut))] +public sealed class DeclaredCheckTests : IDisposable +{ + private readonly string dir = Path.Combine(Path.GetTempPath(), "prober-tests", Guid.NewGuid().ToString("N")); + private readonly string? before = Rows.ClaimsFile; + + public DeclaredCheckTests() + { + Directory.CreateDirectory(dir); + File.WriteAllText(Path.Combine(dir, "claims.json"), new JsonArray(new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray( + new JsonObject + { + ["id"] = "keys", ["sentence"] = "The claim.", + ["inapplicable"] = new JsonArray( + new JsonObject { ["axes"] = new JsonObject { ["x"] = new JsonArray("1"), ["y"] = new JsonArray("2") }, ["cite"] = "not-promised:no-key-here" }, + new JsonObject { ["axes"] = new JsonObject { ["y"] = new JsonArray("3") }, ["cite"] = "fact:KB-031" }), + }, + new JsonObject { ["id"] = "nulls", ["sentence"] = "The claim." }), + }).ToJsonString()); + Rows.ClaimsFile = Path.Combine(dir, "claims.json"); + } + + public void Dispose() + { + Rows.ClaimsFile = before; + try { Directory.Delete(dir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + /// <summary>Runs one row of C1 through <c>Rec.Run</c> with a check of keys and one of nulls, synchronous or not; which bodies ran, and the line written.</summary> + private (List<string> Ran, Exception? Ended, JsonObject Line) Run(string x, string y, bool async = false, string? claim = "C1") + { + var ran = new List<string>(); + var outDir = Path.Combine(dir, "out"); + var previous = Environment.GetEnvironmentVariable("PROBE_OUT"); + Environment.SetEnvironmentVariable("PROBE_OUT", outDir); + Exception? ended = null; + try + { + Rec.Run("P", new Row(0, new Dictionary<string, string> { ["x"] = x, ["y"] = y }, claim), async r => + { + foreach (var obligation in new[] { "keys", "nulls" }) + _ = async + ? await r.Check(obligation, Baseline.Twin, c => { ran.Add(obligation); return Task.FromResult<string?>(c["x"] == "0" ? "differs" : null); }) + : r.Check(obligation, Baseline.Twin, c => { ran.Add(obligation); return c["x"] == "0" ? "differs" : null; }); + return null; + }).GetAwaiter().GetResult(); + } + catch (Exception e) { ended = e; } + finally { Environment.SetEnvironmentVariable("PROBE_OUT", previous); } + var line = File.ReadAllLines(Path.Combine(outDir, "P.jsonl")).Select(l => JsonNode.Parse(l)!.AsObject()).Last(); + File.Delete(Path.Combine(outDir, "P.jsonl")); + return (ran, ended, line); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void A_check_its_claim_declares_inapplicable_for_the_row_s_values_does_not_run_and_records_the_cite(bool async) + { + var (ran, ended, line) = Run("1", "2", async); + ran.ShouldBe(["nulls"], "keys does not apply where x=1 and y=2"); + ended.ShouldBeNull("nulls compared and passed; keys did not apply, which voids nothing"); + var keys = HarnessRun.Check(line, "keys"); + Files.Str(keys["category"]).ShouldBe("inapplicable"); + Files.Strings(keys["reasons"]).ShouldBe(["inapplicable:not-promised:no-key-here(declared x=1 y=2)"]); + Files.StringMap(keys["applied"]).ShouldBe(new Dictionary<string, string> { ["x"] = "1", ["y"] = "2" }, "the axes the declaration matched"); + Files.Str(HarnessRun.Check(line, "nulls")["category"]).ShouldBe("pass", "the declaration is keys', for that check only"); + } + + [Fact] + public void Each_declaration_of_the_obligation_is_applied_and_one_over_two_axes_needs_both() + { + Run("1", "1").Ran.ShouldBe(["keys", "nulls"], "x=1 with y=1 is not x=1 with y=2"); + Run("2", "2").Ran.ShouldBe(["keys", "nulls"], "x=2 with y=2 is not x=1 with y=2"); + var (ran, _, line) = Run("2", "3"); + ran.ShouldBe(["nulls"]); + Files.Strings(HarnessRun.Check(line, "keys")["reasons"]).ShouldBe(["inapplicable:fact:KB-031(declared y=3)"]); + } + + [Fact] + public void A_row_in_hand_without_a_claim_or_without_a_staged_claims_file_sees_no_declarations() + { + Run("1", "2", claim: null).Ran.ShouldBe(["keys", "nulls"]); + Rows.ClaimsFile = null; + Run("1", "2").Ran.ShouldBe(["keys", "nulls"]); + Rows.ClaimsFile = Path.Combine(dir, "none.json"); + Run("1", "2").Ran.ShouldBe(["keys", "nulls"]); + } + + [Fact] + public void A_copy_of_a_row_keeps_its_claim() + { + var row = new Row(0, new Dictionary<string, string> { ["x"] = "1", ["y"] = "2" }, "C1"); + row.Fresh().Claim.ShouldBe("C1"); + row.With("y", "1").Claim.ShouldBe("C1"); + } + + [Fact] + public void A_row_of_a_rows_file_holds_the_file_s_claim() + { + var path = Path.Combine(dir, "P.json"); + File.WriteAllText(path, """{"claim": "C1", "rows": [{"row": {"x": "1", "y": "2"}}]}"""); + Rows.Read(path).ShouldHaveSingleItem().Claim.ShouldBe("C1"); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/DotnetTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/DotnetTests.cs new file mode 100644 index 00000000..40c07acf --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/DotnetTests.cs @@ -0,0 +1,73 @@ +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// Which dotnet a tool starts (tools/Shared/Dotnet.cs, design/adr/0105): the one on PATH when it satisfies +/// global.json, the SDK installer's when only that one does, and otherwise the one on PATH, whose own error then +/// reaches whoever reads the output. +/// </summary> +public sealed class DotnetTests : IDisposable +{ + private readonly string installer = Directory.CreateTempSubdirectory("dotnet-installer-").FullName; + public void Dispose() => Directory.Delete(installer, recursive: true); + + private string Executable => Path.Combine(installer, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + + private void Install() => File.WriteAllText(Executable, ""); + + [Fact] + public void The_one_on_path_when_it_satisfies_the_pin() + { + Install(); + var asked = new List<string>(); + Dotnet.Choose(installer, d => { asked.Add(d); return true; }).ShouldBe(("dotnet", null)); + asked.ShouldBe(["dotnet"], "the installer's is not even asked"); + } + + [Fact] + public void The_installers_when_only_it_satisfies_the_pin() + { + Install(); + Dotnet.Choose(installer, d => d != "dotnet").ShouldBe((Executable, installer)); + } + + [Fact] + public void The_one_on_path_when_neither_does() + { + Install(); + Dotnet.Choose(installer, _ => false).ShouldBe(("dotnet", null)); + } + + [Fact] + public void The_one_on_path_when_the_installer_put_none() + { + var asked = new List<string>(); + Dotnet.Choose(installer, d => { asked.Add(d); return d != "dotnet"; }).ShouldBe(("dotnet", null)); + asked.ShouldBe(["dotnet"]); + } + + [Fact] + public void A_dotnet_satisfies_a_directory_whose_pin_it_has_and_not_one_whose_pin_it_lacks() + { + Dotnet.Satisfies("dotnet", installer).ShouldBeTrue("no global.json: any SDK"); + File.WriteAllText(Path.Combine(installer, "global.json"), """{"sdk": {"version": "0.0.1", "rollForward": "disable"}}"""); + Dotnet.Satisfies("dotnet", installer).ShouldBeFalse("an SDK no dotnet has"); + } + + [Fact] + public void A_program_that_does_not_start_satisfies_nothing() => + Dotnet.Satisfies(Path.Combine(installer, "no-such-dotnet"), installer).ShouldBeFalse(); + + [Fact] + public void A_child_is_pointed_at_the_chosen_directory() + { + var environment = new Dictionary<string, string?> { ["PATH"] = "/usr/bin" }; + Dotnet.Apply(installer, environment); + environment["PATH"].ShouldBe(installer + Path.PathSeparator + "/usr/bin"); + environment["DOTNET_ROOT"].ShouldBe(installer); + + var untouched = new Dictionary<string, string?> { ["PATH"] = "/usr/bin" }; + Dotnet.Apply(null, untouched); + untouched.Keys.ShouldBe(["PATH"]); + untouched["PATH"].ShouldBe("/usr/bin"); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/FixTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/FixTests.cs new file mode 100644 index 00000000..38cf460c --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/FixTests.cs @@ -0,0 +1,90 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// <c>prober fix F --commit SHA</c> writes the fixed line of findings.jsonl as finding-line.schema.json wants it, where a +/// fixed finding was a line written by hand (F3, #45): the open line's claim and probe, the commit's full id. +/// </summary> +public sealed class FixTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private string Findings => Path.Combine(fx.BranchDir, "findings.jsonl"); + + private void Open(string id) => + File.AppendAllText(Findings, new JsonObject + { + ["schemaVersion"] = 1, ["id"] = id, ["claim"] = "C1", ["status"] = "open", ["probe"] = "P", ["reason"] = "row 3 differs", + }.ToJsonString() + "\n"); + + private JsonObject Last() => JsonNode.Parse(File.ReadAllLines(Findings)[^1])!.AsObject(); + + private Ledger Ledger() => new(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null); + + [Fact] + public void It_writes_the_fixed_line_with_the_open_lines_claim_and_probe_and_the_full_commit() + { + Open("F9"); + var head = fx.Sh("git", "rev-parse", "HEAD").Trim(); + var (exit, output, error) = fx.Ledger("fix", "F9", "--commit", head[..7], "--reason", "fixed by the converter's null"); + exit.ShouldBe(0, error); + output.ShouldBe($"F9 fixed at {head[..7]}{Environment.NewLine}"); + var line = Last(); + line.Select(kv => kv.Key).ShouldBe(["schemaVersion", "id", "claim", "status", "commit", "probe", "reason"]); + line["schemaVersion"]!.GetValue<int>().ShouldBe(1); + line["id"]!.GetValue<string>().ShouldBe("F9"); + line["claim"]!.GetValue<string>().ShouldBe("C1"); + line["status"]!.GetValue<string>().ShouldBe("fixed"); + line["commit"]!.GetValue<string>().ShouldBe(head); + line["probe"]!.GetValue<string>().ShouldBe("P"); + line["reason"]!.GetValue<string>().ShouldBe("fixed by the converter's null"); + Ledger().OpenFindings().ShouldBeEmpty(); + Ledger().Findings()["F9"]["status"]!.GetValue<string>().ShouldBe("fixed"); + } + + [Fact] + public void A_commit_outside_heads_history_is_recorded_and_counts_once_it_is_in_it() + { + Open("F9"); + fx.Sh("git", "checkout", "-q", "-b", "fix"); + fx.Write("src/a.cs", "class A { }\n"); + fx.Sh("git", "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qam", "the fix"); // the state stays untracked + var fix = fx.Sh("git", "rev-parse", "HEAD").Trim(); + fx.Sh("git", "checkout", "-q", "main"); + var (exit, output, error) = fx.Ledger("fix", "F9", "--commit", fix); + exit.ShouldBe(0, error); + output.ShouldContain($"F9 fixed at {fix[..7]}; it counts once that commit is in HEAD's history"); + Ledger().OpenFindings().ShouldBe(["F9"]); + fx.Sh("git", "merge", "-q", "--ff-only", "fix"); + Ledger().OpenFindings().ShouldBeEmpty(); + } + + [Theory] + [InlineData("F8", "HEAD", 1, "F8 is no finding of this ledger")] + [InlineData("F9", "0123456789abcdef0123456789abcdef01234567", 1, "--commit 0123456789abcdef0123456789abcdef01234567 is no commit of this repository")] + [InlineData("F9", "", 2, "--commit is required")] + public void It_refuses_a_finding_the_ledger_does_not_hold_and_a_commit_the_repository_lacks(string id, string commit, int code, string message) + { + Open("F9"); + var before = File.ReadAllText(Findings); + var (exit, _, error) = commit.Length > 0 ? fx.Ledger("fix", id, "--commit", commit) : fx.Ledger("fix", id); + exit.ShouldBe(code); + error.ShouldContain(message); + File.ReadAllText(Findings).ShouldBe(before); + } + + [Fact] + public void It_refuses_a_finding_already_fixed_and_a_call_without_one_id() + { + Open("F9"); + fx.Ledger("fix", "F9", "--commit", "HEAD").Exit.ShouldBe(0); + var (exit, _, error) = fx.Ledger("fix", "F9", "--commit", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("F9 is fixed, not open"); + fx.Ledger("fix", "--commit", "HEAD").Exit.ShouldBe(2); + fx.Ledger("fix", "F9", "F10", "--commit", "HEAD").Exit.ShouldBe(2); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/Fixture.cs b/test/CodoMetis.TypeKit.Prober.Tests/Fixture.cs index a31c20ec..b388e557 100644 --- a/test/CodoMetis.TypeKit.Prober.Tests/Fixture.cs +++ b/test/CodoMetis.TypeKit.Prober.Tests/Fixture.cs @@ -42,8 +42,9 @@ public void Write(string relative, string text) File.WriteAllText(path, text); } + /// <summary>P's rows: each probe has a rows file of its own, its claim inside.</summary> public void Rows(IEnumerable<(string X, string Y)> rows) => - Write("tools/probe/corpus/rows/C1.json", new JsonObject + Write("tools/probe/corpus/rows/P.json", new JsonObject { ["claim"] = "C1", ["rows"] = new JsonArray([.. rows.Select(r => (JsonNode)new JsonObject { ["row"] = new JsonObject { ["x"] = r.X, ["y"] = r.Y } })]), @@ -69,13 +70,39 @@ public string Sh(string file, params string[] args) /// <summary>Runs a prober command in-process, in this repository, with the environment given.</summary> public (int Exit, string Out, string Err) Ledger(params string[] args) => LedgerWith(new Dictionary<string, string?>(), args); - public (int Exit, string Out, string Err) LedgerWith(Dictionary<string, string?> env, params string[] args) + public (int Exit, string Out, string Err) LedgerWith(Dictionary<string, string?> env, params string[] args) => LedgerEnding(env, Environment.NewLine, args); + + /// <summary>As <see cref="LedgerWith"/>, with the line end the output writers use, so a test sees Windows' output on any OS.</summary> + public (int Exit, string Out, string Err) LedgerEnding(Dictionary<string, string?> env, string newLine, params string[] args) { - var output = new StringWriter(); var error = new StringWriter(); + var output = new StringWriter { NewLine = newLine }; var error = new StringWriter { NewLine = newLine }; var exit = Program.Run(new Context(Root, env, output, error), args); return (exit, output.ToString(), error.ToString()); } + /// <summary>Runs a prober command in-process in another checkout of this repository (a linked worktree).</summary> + public (int Exit, string Out, string Err) LedgerIn(string checkout, params string[] args) + { + var output = new StringWriter(); var error = new StringWriter(); + var exit = Program.Run(new Context(checkout, new Dictionary<string, string?>(), output, error), args); + return (exit, output.ToString(), error.ToString()); + } + + /// <summary>A linked worktree of this repository (<c>git worktree add</c>) on a new branch, beside it; removed with the fixture.</summary> + public string Linked(string branch) + { + var path = Root + "-" + branch; + Sh("git", "worktree", "add", "-q", "-b", branch, path); + linked.Add(path); + return path; + } + + private readonly List<string> linked = []; + + /// <summary>The lock file of a branch's state, wherever the tool puts it.</summary> + public string LockFile(string branch = "main") => + RoundLock.LockPath(new Ledger(new Context(Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), branch)); + /// <summary>Runs the built dll as its own process: for the tests that race several takers.</summary> public Process Spawn(params string[] args) { @@ -92,8 +119,12 @@ public void Footprint() new JsonObject { ["probe"] = "P", ["footprint"] = new JsonObject { ["src/a.cs"] = blob }, ["compileTime"] = new JsonObject(), ["toolchain"] = new JsonObject() }.ToJsonString() + "\n"); } - /// <summary>What a replay of P records: the rows it ran, how each ended and the settings that applied.</summary> - public string Evidence(IReadOnlyList<Dictionary<string, string>> applied, string category = "pass", int[]? executed = null) + /// <summary> + /// What a replay of P records: the rows it ran, how each ended and the settings that applied; outcomes, when + /// given, sets each row's category and the finding it skips for. + /// </summary> + public string Evidence(IReadOnlyList<Dictionary<string, string>> applied, string category = "pass", int[]? executed = null, + IReadOnlyList<(string Category, string? Finding)>? outcomes = null) { var idx = Enumerable.Range(0, applied.Count).ToArray(); var path = Path.Combine(Root, "evidence.json"); @@ -106,7 +137,8 @@ public string Evidence(IReadOnlyList<Dictionary<string, string>> applied, string }), ["rows"] = new JsonObject(idx.Select(i => KeyValuePair.Create(i.ToString(System.Globalization.CultureInfo.InvariantCulture), (JsonNode?)new JsonObject { - ["category"] = category, ["applied"] = new JsonObject(applied[i].Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), + ["category"] = outcomes?[i].Category ?? category, ["applied"] = new JsonObject(applied[i].Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), + ["findings"] = outcomes?[i].Finding is { } finding ? new JsonArray(finding) : new JsonArray(), }))), }.ToJsonString()); return path; @@ -121,7 +153,7 @@ public int Covered() public void AgeTheLock() { - var path = Path.Combine(Memory, "round.lock"); + var path = LockFile(); var held = JsonNode.Parse(File.ReadAllText(path))!.AsObject(); held["heartbeat"] = held["heartbeat"]!.GetValue<double>() - 21 * 60; File.WriteAllText(path, held.ToJsonString()); @@ -129,6 +161,7 @@ public void AgeTheLock() public void Dispose() { - try { Directory.Delete(Root, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + foreach (var dir in linked.Append(Root)) + try { Directory.Delete(dir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } } } diff --git a/test/CodoMetis.TypeKit.Prober.Tests/GapsTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/GapsTests.cs new file mode 100644 index 00000000..d9db71e3 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/GapsTests.cs @@ -0,0 +1,160 @@ +using System.Text.Json.Nodes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// `prober gaps` prints what the corpus leaves out, each under its heading with its recorded reason, and says +/// none under a heading with nothing: a report from the tracked corpus and the generated axes that decides +/// nothing (decision 104). +/// </summary> +public sealed class GapsTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + /// <summary>The output after its first line, which names the branch and the commit.</summary> + private string Gaps(string newLine) + { + var (exit, output, error) = fx.LedgerEnding([], newLine, "gaps", "--base", "HEAD"); + exit.ShouldBe(0, "a report decides nothing: " + error); + var text = output.ReplaceLineEndings("\n"); + text.ShouldStartWith("prober gaps: main at "); + return text[(text.IndexOf('\n', StringComparison.Ordinal) + 1)..]; + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void Each_gap_is_listed_under_its_heading_with_its_reason(string newLine) + { + fx.Write("design/adr/0001-limits.md", "# 1. Limits\n\nAn ordering of keys is not promised.\n\nNo culture but the invariant one is promised.\n"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject + { + ["x"] = new JsonArray("1", "2", "3"), ["y"] = new JsonArray("1", "2"), + ["shape.refused-kind"] = new JsonArray("in-generic-type"), + }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["refused"] = new JsonObject { ["x"] = new JsonObject { ["3"] = "the framework refuses 3 on its own (KB-001)" } }, + ["out-of-reach"] = new JsonObject { ["postgresql"] = "needs a server" }, + }.ToJsonString() + "\n```\n"); + File.WriteAllText(Path.Combine(fx.Memory, "axes-generated.json"), new JsonObject + { + ["axes"] = new JsonObject { ["json.z"] = new JsonArray("true", "false") }, + ["needsValues"] = new JsonArray("json.w"), + ["versions"] = new JsonObject { ["System.Text.Json"] = "10.0.0" }, + }.ToJsonString()); + fx.Write("tools/probe/corpus/claims.json", new JsonArray(new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["narrow"] = new JsonObject { ["y"] = "The claim." }, + ["not-promised"] = new JsonArray( + new JsonObject + { + ["id"] = "order", ["source"] = "design/adr/0001-limits.md#1-limits", ["sentence"] = "An ordering of keys is not promised.", + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2") }, + }, + new JsonObject { ["id"] = "culture", ["source"] = "design/adr/0001-limits.md#1-limits", ["sentence"] = "No culture but the invariant one is promised." }), + }).ToJsonString()); + + Gaps(newLine).ShouldBe(""" + settings no surface reaches and nothing ignores (enumerated at System.Text.Json 10.0.0): 1 + json.z (no surface reaches it and it is not ignored) + settings without values: 1 + json.w (no hand values and not ignored) + declarations the generators refuse (shape.refused-kind): 1 + in-generic-type + values the frameworks refuse on their own: 1 + x=3: the framework refuses 3 on its own (KB-001) + not promised, by what it concerns: 3 + the whole claim: C1/culture "No culture but the invariant one is promised." (design/adr/0001-limits.md#1-limits) + x=1|2: C1/order "An ordering of keys is not promised." (design/adr/0001-limits.md#1-limits) + y: C1 narrows it, "The claim." (README.md#a) + pairs no obligation wants (declared inapplicable): none + pairs no passing check read both axes of (the last replay's evidence): none + out of reach: 1 + postgresql: needs a server + + """.ReplaceLineEndings("\n")); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void A_corpus_without_gaps_says_none_under_each_heading(string newLine) => + Gaps(newLine).ShouldBe(""" + settings no surface reaches and nothing ignores: none + settings without values: none + declarations the generators refuse (shape.refused-kind): none + values the frameworks refuse on their own: none + not promised, by what it concerns: none + pairs no obligation wants (declared inapplicable): none + pairs no passing check read both axes of (the last replay's evidence): none + out of reach: none + + """.ReplaceLineEndings("\n")); + + /// <summary> + /// C1 over x, y and z: shape wants every pair and its passing checks read x and y; parse does not apply where x=1 and + /// z=1 and its passing checks read x and z. x=1 z=1 is wanted by shape alone, which never read z, and no passing check + /// read y with z: those five pairs are listed. A check that did not pass reads nothing for the listing. + /// </summary> + [Fact] + public void The_pairs_no_obligation_wanting_them_read_both_axes_of_are_listed() + { + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2"), ["z"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y", "z"), ["watched"] = new JsonArray("src") } }, + }.ToJsonString() + "\n```\n"); + fx.Write("tools/probe/corpus/claims.json", new JsonArray(new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray( + new JsonObject { ["id"] = "shape", ["sentence"] = "The claim." }, + new JsonObject + { + ["id"] = "parse", ["sentence"] = "The claim.", + ["inapplicable"] = new JsonArray(new JsonObject { ["axes"] = new JsonObject { ["x"] = new JsonArray("1"), ["z"] = new JsonArray("1") }, ["cite"] = "docs:README.md#a" }), + }), + }).ToJsonString()); + fx.Commit("three axes"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + Gaps("\n").ShouldContain("pairs no passing check read both axes of (the last replay's evidence): none\n", Case.Sensitive, "no evidence yet: nothing to say"); + + static JsonObject Check(string obligation, string category, params (string Axis, string Value)[] read) => new() + { + ["obligation"] = obligation, ["category"] = category, ["baseline"] = "twin", + ["applied"] = new JsonObject(read.Select(r => KeyValuePair.Create(r.Axis, (JsonNode?)r.Value))), + }; + var evidence = Path.Combine(fx.Root, "evidence.json"); + File.WriteAllText(evidence, new JsonObject + { + ["variants"] = new JsonArray(new JsonObject { ["env"] = new JsonObject(), ["expected"] = new JsonArray(0), ["executed"] = new JsonArray(0) }), + ["rows"] = new JsonObject + { + ["0"] = new JsonObject + { + ["category"] = "pass", ["applied"] = new JsonObject(), ["findings"] = new JsonArray(), + ["checks"] = new JsonArray( + Check("shape", "pass", ("x", "1"), ("y", "1")), + Check("parse", "pass", ("x", "2"), ("z", "1")), + Check("parse", "inapplicable", ("y", "2"), ("z", "2"))), + }, + }, + }.ToJsonString()); + fx.Footprint(); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", evidence).Exit.ShouldBe(0); + + Gaps("\n").ShouldContain(""" + pairs no passing check read both axes of (the last replay's evidence): 5 + C1: x=1 z=1 + C1: y=1 z=1 + C1: y=1 z=2 + C1: y=2 z=1 + C1: y=2 z=2 + + """.ReplaceLineEndings("\n")); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/HarnessErrorTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/HarnessErrorTests.cs new file mode 100644 index 00000000..be0ec024 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/HarnessErrorTests.cs @@ -0,0 +1,141 @@ +using System.Text.Json.Nodes; +using Prober; +using Probes.Shapes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary>The harness writes rows to the process's PROBE_OUT, so its tests run one at a time.</summary> +[CollectionDefinition(nameof(ProbeOut), DisableParallelization = true)] +public sealed class ProbeOut; + +/// <summary> +/// A harness error is never a catch (decision 103): the probe corpus's own <c>Rec.Run</c> (harness/Row.cs, +/// compiled in) ends a row whose check throws as a harness error, not as a violation, and `seeds`, reading +/// the lines it writes, counts no such row caught, nor a violation whose reasons carry a harness error. +/// </summary> +[Collection(nameof(ProbeOut))] +public sealed class HarnessErrorTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + + public HarnessErrorTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }, + }.ToJsonString() + "\n```\n"); + fx.Write("src/a.cs", "class A { /* DEFECT */ }\n"); + fx.Write("tools/probe/corpus/seeds/C1.patch", fx.Sh("git", "diff", "--", "src/a.cs")); + fx.Write("src/a.cs", "class A {}\n"); + fx.Write("tools/probe/corpus/registry.json", new JsonObject + { + ["probes"] = new JsonObject + { + ["P"] = new JsonObject { ["claim"] = "C1", ["filter"] = "*P", ["seed"] = new JsonObject { ["defect"] = "A holds the defect.", ["patch"] = "seeds/C1.patch" } }, + }, + }.ToJsonString()); + fx.Commit("seed"); + } + + public void Dispose() + { + fx.Dispose(); + try { Directory.Delete(cache, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + /// <summary>Runs one row through the harness's <c>Rec.Run</c>, writing to <paramref name="outDir"/>; the exception it ends the test with, if any.</summary> + private static Exception? Harness(string outDir, int idx, Func<Row, Task<string?>> check) + { + var previous = Environment.GetEnvironmentVariable("PROBE_OUT"); + Environment.SetEnvironmentVariable("PROBE_OUT", outDir); + try + { + Rec.Run("C1", new Row(idx, new Dictionary<string, string> { ["x"] = "1", ["y"] = "1" }), check).GetAwaiter().GetResult(); + return null; + } + catch (Exception ended) { return ended; } + finally { Environment.SetEnvironmentVariable("PROBE_OUT", previous); } + } + + private static Task<string?> Throws(Row _) => throw new InvalidOperationException("the check broke"); + + [Fact] + public void A_check_that_throws_ends_as_a_harness_error_and_fails_the_test_naming_the_exception() + { + var outDir = Path.Combine(fx.Root, "rows-out"); + var ended = Harness(outDir, 1, Throws); + ended.ShouldNotBeNull().Message.ShouldContain("UNEXPECTED InvalidOperationException: the check broke"); + + var line = JsonNode.Parse(File.ReadAllLines(Path.Combine(outDir, "C1.jsonl")).ShouldHaveSingleItem())!; + Files.Str(line["category"]).ShouldBe("harness-error", "an exception is the harness breaking, never the claim's check firing"); + Files.Strings(line["reasons"]).ShouldBe(["harness-error:UNEXPECTED InvalidOperationException: the check broke"]); + Files.Str(line["status"]).ShouldBe("fail"); + } + + [Fact] + public void A_seed_that_only_makes_a_check_throw_is_missed_not_caught() + { + var (exit, output) = Seeds(new HarnessAdapter(Throws)); + exit.ShouldBe(1, output); + output.ShouldContain("P (C1): missed, no row violates that did not already (2 rows: harness-error 1, pass 1; 1 ended in a harness error, which is no catch;"); + output.ShouldNotContain("caught,"); + } + + [Fact] + public void A_violation_whose_reasons_carry_a_harness_error_is_no_catch() + { + var (exit, output) = Seeds(new HarnessAdapter(r => + { + r.Outcome("harness-error", "the check could not tell"); + return Task.FromResult<string?>("and failed anyway"); + })); + exit.ShouldBe(1, output); + output.ShouldContain("P (C1): missed, no row violates that did not already (2 rows: pass 1, violation 1; 1 ended in a harness error, which is no catch;"); + } + + [Fact] + public void A_violation_through_the_harness_is_a_catch() + { + var (exit, output) = Seeds(new HarnessAdapter(_ => Task.FromResult<string?>("the claim does not hold"))); + exit.ShouldBe(0, output); + output.ShouldContain("P (C1): caught, 1 of 2 rows violate; e.g. #1: violation:the claim does not hold"); + } + + private (int Exit, string Out) Seeds(IProbeAdapter adapter) + { + var output = new StringWriter(); + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, output); + int exit; + try { exit = global::Prober.Seeds.Run(context, new Args([], new HashSet<string>(StringComparer.Ordinal)), adapter); } + catch (Refusal refusal) { output.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString()); + } + + /// <summary> + /// The probe P over C1's two rows, each run through the real harness: row 0 passes, and row 1 runs + /// <paramref name="seeded"/> while src/a.cs holds DEFECT. + /// </summary> + private sealed class HarnessAdapter(Func<Row, Task<string?>> seeded) : IProbeAdapter + { + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) => new(0, "Build: OK", ""); + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + var defect = File.ReadAllText(Path.Combine(worktree, "src", "a.cs")).Contains("DEFECT", StringComparison.Ordinal); + var outDir = environment["PROBE_OUT"]!; + var failed = false; + for (var idx = 0; idx < 2; idx++) + failed |= Harness(outDir, idx, idx == 1 && defect ? seeded : _ => Task.FromResult<string?>(null)) is not null; + return new Ran(failed ? 1 : 0, "", ""); + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/LedgerTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/LedgerTests.cs index 406720f8..62ec4ad4 100644 --- a/test/CodoMetis.TypeKit.Prober.Tests/LedgerTests.cs +++ b/test/CodoMetis.TypeKit.Prober.Tests/LedgerTests.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using Prober; namespace CodoMetis.TypeKit.Prober.Tests; @@ -91,6 +92,82 @@ public void A_row_that_did_not_run_is_never_credited() } } +/// <summary> +/// What earned nothing is counted where the coverage is read, and a row that steps around a finding binds: the +/// gate refuses while one does, and the ledger names a skip whose finding is fixed or dismissed. +/// </summary> +public sealed class OutcomeTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private static Dictionary<string, string> R(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + + private string Record(params (string Category, string? Finding)[] outcomes) + { + fx.Footprint(); + var evidence = fx.Evidence([R("1", "1"), R("2", "2")], outcomes: outcomes); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", evidence).Exit.ShouldBe(0); + return evidence; + } + + private List<string> StaleSkips(string evidence) => + new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null) + .StaleSkips(JsonNode.Parse(File.ReadAllText(evidence)) as JsonObject); + + private void Finding(string status, string commit) => + File.AppendAllText(Path.Combine(fx.BranchDir, "findings.jsonl"), + new JsonObject { ["id"] = "F9", ["claim"] = "C1", ["status"] = status, ["commit"] = commit }.ToJsonString() + "\n"); + + [Fact] + public void Status_counts_how_the_rows_of_a_claim_ended() + { + Record(("pass", null), ("expected-refusal", null)); + var outcomes = JsonNode.Parse(fx.Ledger("status", "--json").Out)!["claims"]!["C1"]!["outcomes"]!; + outcomes["pass"]!.GetValue<double>().ShouldBe(1); + outcomes["expected-refusal"]!.GetValue<double>().ShouldBe(1); + fx.Ledger("status").Out.ShouldContain("C1: 1/4 (rows: expected-refusal 1, pass 1; checks: expected-refusal 1, pass 1)"); + } + + [Fact] + public void A_skip_for_a_finding_is_shown_and_refused_by_the_gate_without_any_ledger_of_findings() + { + fx.Sh("git", "tag", "v1"); + Record(("pass", null), ("known-finding", "F9")); + fx.Ledger("status").Out.ShouldContain("1 row(s) skip for finding F9"); + var (exit, output, _) = fx.Ledger("gate", "--since", "v1", "--json"); + exit.ShouldNotBe(0); + JsonNode.Parse(output)!["reasons"]!.AsArray().Select(r => r!.GetValue<string>()).ShouldContain("P: 1 row(s) skip for finding F9; fix it and remove the skip, or dismiss it"); + } + + [Fact] + public void A_skip_for_an_open_finding_stands() + { + var evidence = Record(("known-finding", "F9"), ("pass", null)); + Finding("open", ""); + StaleSkips(evidence).ShouldBeEmpty(); + } + + [Fact] + public void A_skip_for_a_fixed_finding_is_stale() + { + var evidence = Record(("known-finding", "F9"), ("pass", null)); + var head = fx.Sh("git", "rev-parse", "HEAD").Trim(); + Finding("open", ""); + Finding("fixed", head); + StaleSkips(evidence).ShouldBe([$"1 row(s) skip for F9, which is fixed at {head[..7]}: remove the skip"]); + } + + [Fact] + public void A_skip_for_a_dismissed_finding_is_stale() + { + var evidence = Record(("known-finding", "F9"), ("pass", null)); + Finding("open", ""); + fx.Write("tools/probe/corpus/dismissed.jsonl", new JsonObject { ["id"] = "F9", ["reason"] = "intended", ["date"] = "2026-10-15" }.ToJsonString() + "\n"); + StaleSkips(evidence).ShouldBe(["1 row(s) skip for F9, which is dismissed: remove the skip"]); + } +} + /// <summary>One round at a time, on every OS: an exclusive create, liveness by heartbeat, a mutex for takeovers.</summary> public sealed class LockTests : IDisposable { @@ -152,7 +229,7 @@ public void Racing_threads_over_a_dead_holder_get_one_lock() threads.ForEach(t => t.Start()); threads.ForEach(t => t.Join()); exits.Count(e => e == 0).ShouldBe(1, $"round {round}"); - File.Delete(Path.Combine(fx.Memory, "round.lock")); + File.Delete(fx.LockFile()); } } @@ -166,10 +243,11 @@ public void Racing_threads_over_a_dead_holder_get_one_lock() [Fact] public void A_mutex_the_OS_refuses_to_create_is_waited_for_and_refused_not_thrown() { - Directory.CreateDirectory(fx.Memory); - var mutex = Path.Combine(fx.Memory, "round.lock.mutex"); + var directory = Path.GetDirectoryName(fx.LockFile())!; + Directory.CreateDirectory(directory); + var mutex = fx.LockFile() + ".mutex"; if (OperatingSystem.IsWindows()) Directory.CreateDirectory(mutex); - else File.SetUnixFileMode(fx.Memory, UnixFileMode.UserRead | UnixFileMode.UserExecute); + else File.SetUnixFileMode(directory, UnixFileMode.UserRead | UnixFileMode.UserExecute); try { var (exit, _, error) = fx.Ledger("lock", "--holder", "a", "--mode", "explore"); @@ -180,10 +258,32 @@ public void A_mutex_the_OS_refuses_to_create_is_waited_for_and_refused_not_throw finally { if (OperatingSystem.IsWindows()) Directory.Delete(mutex); - else File.SetUnixFileMode(fx.Memory, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + else File.SetUnixFileMode(directory, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); } } + [Fact] + public void A_lock_of_one_branch_leaves_another_branchs_lock_free() + { + var other = fx.Linked("other"); + fx.Ledger("lock", "--holder", "a", "--mode", "explore").Exit.ShouldBe(0); + + var (exit, output, error) = fx.LedgerIn(other, "lock", "--holder", "b", "--mode", "explore"); + + exit.ShouldBe(0, error + output); + File.Exists(fx.LockFile("other")).ShouldBeTrue(); + File.Exists(fx.LockFile("main")).ShouldBeTrue("a's lock is still held"); + } + + [Fact] + public void A_second_lock_of_one_branch_is_refused_from_another_checkout() + { + var other = fx.Linked("other"); + fx.Ledger("lock", "--holder", "a", "--mode", "explore").Exit.ShouldBe(0); + + fx.LedgerIn(other, "lock", "--branch", "main", "--holder", "b", "--mode", "replay").Exit.ShouldNotBe(0); + } + private int RaceOf(int takers) { var processes = Enumerable.Range(0, takers).Select(i => fx.Spawn("lock", "--holder", $"h{i}", "--mode", "replay")).ToList(); @@ -242,8 +342,8 @@ public void State_goes_where_PROBE_STATE_DIR_says() try { fx.LedgerWith(new() { ["PROBE_STATE_DIR"] = state }, "lock", "--holder", "ci", "--mode", "replay").Exit.ShouldBe(0); - File.Exists(Path.Combine(state, "round.lock")).ShouldBeTrue(); - File.Exists(Path.Combine(fx.Memory, "round.lock")).ShouldBeFalse(); + Directory.EnumerateFiles(state, "round.lock", SearchOption.AllDirectories).ShouldNotBeEmpty(); + File.Exists(fx.LockFile()).ShouldBeFalse(); } finally { if (Directory.Exists(state)) Directory.Delete(state, recursive: true); } } diff --git a/test/CodoMetis.TypeKit.Prober.Tests/NewProbeTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/NewProbeTests.cs new file mode 100644 index 00000000..2d9071e6 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/NewProbeTests.cs @@ -0,0 +1,193 @@ +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// `new` stages what a probe writer used to do by hand: the skeleton in the harness's form with the claim's +/// obligations quoted and one throwing check each, the claim's rows, and the registry entry with no seed yet, +/// so the seeds every obligation owes are named. C1 lists two obligations; C7-two-words lists none. +/// </summary> +public sealed class NewProbeTests : IDisposable +{ + private readonly Fixture fx = new(); + + public NewProbeTests() + { + fx.Write("README.md", "# a\n\nThe claim.\n\nA key is written as the wrapped type writes it.\n\nA null is refused.\n"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src"), ["families"] = new JsonObject { ["f"] = new JsonArray("s") } }, + }.ToJsonString() + "\n```\n"); + fx.Write("tools/probe/corpus/claims.json", new JsonArray( + new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray( + new JsonObject { ["id"] = "keys", ["sentence"] = "A key is written as the wrapped type writes it." }, + new JsonObject { ["id"] = "nulls", ["sentence"] = "A null is refused." }), + }, + new JsonObject { ["id"] = "C7-two-words", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s") }).ToJsonString()); + fx.Commit("claims with obligations"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + } + + public void Dispose() => fx.Dispose(); + + private string Staged(string relative) => Path.Combine(fx.BranchDir, "staging", relative); + + [Fact] + public void It_stages_the_skeleton_the_rows_and_the_registry_entry() + { + var (exit, output, error) = fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe"); + exit.ShouldBe(0, output + error); + + var source = File.ReadAllText(Staged("probes/C1KeysProbe.cs")); + source.ShouldContain("// Claim C1 (README.md#a), surfaces s, family f."); + source.ShouldContain("// keys: \"A key is written as the wrapped type writes it.\""); + source.ShouldContain("// nulls: \"A null is refused.\""); + source.ShouldContain("// TODO keys: \"A key is written as the wrapped type writes it.\""); + source.ShouldContain("// TODO nulls: \"A null is refused.\""); + + // The probe's own rows, C1's other probe P's untouched: P has covered nothing, so a covering array over all four pairs. + var staged = JsonNode.Parse(File.ReadAllText(Staged("rows/C1KeysProbe.json")))!; + staged["claim"]!.GetValue<string>().ShouldBe("C1"); + staged["rows"]!.AsArray().Select(r => $"{r!["row"]!["x"]}{r["row"]!["y"]}").Order().ShouldBe(["11", "12", "21", "22"]); + File.Exists(Staged("rows/P.json")).ShouldBeFalse("a new probe leaves its sibling's rows as they are"); + + var entry = JsonNode.Parse(File.ReadAllText(Staged("registry.json")))!["probes"]!["C1KeysProbe"]!.AsObject(); + entry["claim"]!.GetValue<string>().ShouldBe("C1"); + entry["filter"]!.GetValue<string>().ShouldBe("*C1KeysProbe"); + entry["seeds"]!.AsArray().ShouldBeEmpty(); + entry.ContainsKey("seed").ShouldBeFalse(); + + output.ShouldContain("still owed, per obligation:"); + output.ShouldContain(" keys: the check CheckKeys"); + output.ShouldContain(" nulls: the check CheckNulls"); + } + + [Fact] + public void The_seeds_each_obligation_owes_are_named() + { + fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe").Exit.ShouldBe(0); + var problems = global::Prober.Seeds.Problems(new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)); + problems.ShouldContain("C1KeysProbe: no seed, its registry entry names none"); + problems.ShouldContain("C1: obligation keys has no seed in any of its probes"); + problems.ShouldContain("C1: obligation nulls has no seed in any of its probes"); + + var (exit, output, _) = fx.Ledger("seeds", "--dry-run"); + exit.ShouldBe(0); + output.ShouldContain(" C1KeysProbe/keys: (none)"); + output.ShouldContain(" C1KeysProbe/nulls: (none)"); + } + + [Fact] + public void A_claim_not_in_the_claims_is_refused_and_nothing_staged() + { + var (exit, _, error) = fx.Ledger("new", "--claim", "C9"); + exit.ShouldBe(1); + error.ShouldContain("no claim C9 in claims.json"); + Directory.Exists(Path.Combine(fx.BranchDir, "staging")).ShouldBeFalse(); + } + + [Fact] + public void A_name_a_probe_has_is_refused_and_nothing_changes() + { + fx.Write("tools/probe/corpus/registry.json", """{"probes": {"P": {"claim": "C1", "filter": "*P"}, "C1OldProbe": {"claim": "C1", "filter": "*C1OldProbe"}}}"""); + fx.Commit("a second probe of C1"); + var (exit, _, error) = fx.Ledger("new", "--claim", "C1", "--probe", "C1OldProbe"); + exit.ShouldBe(1); + error.ShouldContain("a probe C1OldProbe exists already (in the registry)"); + fx.Ledger("new", "--claim", "C1", "--probe", "P").Exit.ShouldBe(2, "a name that does not end in Probe"); + Directory.Exists(Path.Combine(fx.BranchDir, "staging")).ShouldBeFalse(); + + fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe").Exit.ShouldBe(0); + var registry = File.ReadAllText(Staged("registry.json")); + var source = File.ReadAllText(Staged("probes/C1KeysProbe.cs")); + fx.Ledger("new", "--claim", "C7-two-words", "--probe", "C1KeysProbe").Exit.ShouldBe(1, "a second `new` of one name"); + File.ReadAllText(Staged("registry.json")).ShouldBe(registry); + File.ReadAllText(Staged("probes/C1KeysProbe.cs")).ShouldBe(source); + } + + [Theory] + [InlineData("C20-testing-shouldhold", "C20TestingShouldholdProbe")] + [InlineData("E02-value-translates-to-column", "E02ValueTranslatesToColumnProbe")] + [InlineData("L01-json-bytes", "L01JsonBytesProbe")] + public void The_name_defaults_to_the_claim_s_prefix_and_words(string claim, string probe) => NewProbe.DefaultName(claim).ShouldBe(probe); + + [Fact] + public void Without_a_name_the_default_is_staged() + { + fx.Ledger("new", "--claim", "C7-two-words").Exit.ShouldBe(0); + // The registry's key, not the file: a file system that ignores case finds the file under another spelling. + JsonNode.Parse(File.ReadAllText(Staged("registry.json")))!["probes"]!.AsObject().Select(kv => kv.Key).ShouldBe(["C7TwoWordsProbe"]); + File.ReadAllText(Staged("probes/C7TwoWordsProbe.cs")).ShouldContain("// claim: the claim as a whole"); + } + + [Fact] + public void A_family_that_is_not_the_claim_s_is_refused() + { + var (exit, _, error) = fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe", "--family", "g"); + exit.ShouldBe(2); + error.ShouldContain("claim C1 is of family f (its first surface, s), not g"); + fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe", "--family", "f").Exit.ShouldBe(0); + } + + /// <summary> + /// The lines that make a probe a probe of this harness, with its own names taken out: the namespace, the + /// class, the rows as member data, the theory and the row's run through Rec.Run with a check of the row. + /// </summary> + private static List<string> Form(string source) => + [.. source.Split('\n').Select(line => line.Trim()).Select(line => line switch + { + _ when line.StartsWith("namespace ", StringComparison.Ordinal) => line, + _ when Regex.IsMatch(line, @"^public sealed class \w+Probe$") => "public sealed class {probe}", + _ when line.StartsWith("public static TheoryData<string, int> Data()", StringComparison.Ordinal) => + Regex.Replace(line, @"Rows\.Data\(""[^""]+""\)", "Rows.Data({probe})"), + _ when line.StartsWith("[Theory", StringComparison.Ordinal) => line, + _ when line.StartsWith("public Task ", StringComparison.Ordinal) => Regex.Replace(line, @"Rec\.Run\(probe, idx, [^;]+\);$", "Rec.Run(probe, idx, {check});"), + _ => null, + }).OfType<string>()]; + + private static readonly string Corpus = Path.Combine(RepositoryRoot(), "tools", "probe", "corpus", "probes"); + + [Theory] + [InlineData("C08SameNameWarningProbe")] + [InlineData("C12DescribedConverterProbe")] + [InlineData("C20ShouldHoldProbe")] + public void The_skeleton_has_the_form_of_the_corpus_s_probes(string probe) + { + fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe").Exit.ShouldBe(0); + var skeleton = File.ReadAllText(Staged("probes/C1KeysProbe.cs")); + var corpus = File.ReadAllText(Path.Combine(Corpus, probe + ".cs")); + Form(skeleton).ShouldBe(Form(corpus)); + Form(skeleton).Count.ShouldBe(5, "namespace, class, rows, theory and run"); + var usings = corpus.Split('\n').Where(l => l.StartsWith("using ", StringComparison.Ordinal)).Select(l => l.Trim()).ToList(); + skeleton.Split('\n').Where(l => l.StartsWith("using ", StringComparison.Ordinal)).ShouldAllBe(l => usings.Contains(l.Trim())); + } + + [Fact] + public void Every_obligation_s_check_throws_until_it_is_written() + { + fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe").Exit.ShouldBe(0); + var skeleton = File.ReadAllText(Staged("probes/C1KeysProbe.cs")); + foreach (var method in new[] { "CheckKeys", "CheckNulls" }) + { + skeleton.ShouldContain($"static Task<string?> {method}(Row r) => throw new NotImplementedException("); + Regex.IsMatch(skeleton, $@"static async Task<string\?> Check\(Row r\) =>[^;]*await {method}\(r\)").ShouldBeTrue($"Check runs {method}"); + } + Regex.Matches(skeleton, @"=> throw new NotImplementedException\(").Count.ShouldBe(2); + skeleton.ShouldNotContain("return null"); + skeleton.ShouldNotContain("FromResult"); + } + + private static string RepositoryRoot() + { + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "CodoMetis.TypeKit.slnx"))) root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("no CodoMetis.TypeKit.slnx above the test output"); + return root; + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/ObligationsTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/ObligationsTests.cs new file mode 100644 index 00000000..94369bb4 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/ObligationsTests.cs @@ -0,0 +1,228 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// A claim lists what it promises, each obligation a sentence quoted from its source, and what it does not +/// promise, each with the sentence that makes it intended; every obligation of a claim with probes has a seed +/// that names it (decision 104). The probe here is a fake adapter: row N of C1 violates while SEED_ROW is N. +/// </summary> +public sealed class ObligationsTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + private readonly FakeAdapter adapter = new(); + + public ObligationsTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + fx.Write("README.md", "# a\n\nThe claim.\n\nA key is written as the wrapped type writes it.\n\nA null is refused.\n"); + fx.Write("design/adr/0001-limits.md", "# 1. Limits\n\nAn ordering of keys is not promised.\n"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }, + }.ToJsonString() + "\n```\n"); + fx.Commit("sources and replay configuration"); + } + + public void Dispose() + { + fx.Dispose(); + try { Directory.Delete(cache, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + private static JsonObject Obligation(string id, string sentence) => new() { ["id"] = id, ["sentence"] = sentence }; + + private static JsonArray TwoObligations() => new( + Obligation("keys", "A key is written as the wrapped type writes it."), + Obligation("nulls", "A null is refused.")); + + private void Claims(JsonObject claim) + { + var c = new JsonObject { ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s") }; + foreach (var (key, value) in claim) c[key] = value?.DeepClone(); + fx.Write("tools/probe/corpus/claims.json", new JsonArray(c).ToJsonString()); + } + + /// <summary>Registers P for C1 with the seeds given, each an environment toggle that makes the check of its obligation violate in row <c>row</c>.</summary> + private void Register(JsonObject? seed = null, params (string Obligation, string Row)[] seeds) + { + var entry = new JsonObject { ["claim"] = "C1", ["filter"] = "*P" }; + if (seed is not null) entry["seed"] = seed; + if (seeds.Length > 0) + entry["seeds"] = new JsonArray([.. seeds.Select(s => (JsonNode)new JsonObject + { + ["obligation"] = s.Obligation, ["env"] = new JsonObject { ["SEED_ROW"] = s.Row, ["SEED_CHECK"] = s.Obligation }, ["defect"] = $"{s.Obligation} is broken.", + })]); + fx.Write("tools/probe/corpus/registry.json", new JsonObject { ["probes"] = new JsonObject { ["P"] = entry } }.ToJsonString()); + fx.Commit("register"); + } + + private static JsonObject EnvSeed() => new() { ["env"] = new JsonObject { ["SEED_ROW"] = "1" }, ["defect"] = "Row 1 is broken." }; + + private List<string> Problems() => Seeds.Problems(new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)); + + private JsonObject Status() + { + var (exit, output, error) = fx.Ledger("status", "--json"); + exit.ShouldBe(0, error); + return JsonNode.Parse(output)!["claims"]!["C1"]!.AsObject(); + } + + /// <summary>Each obligation of the claim in `status --json`, as "id: the probes whose seeds break it".</summary> + private static List<string> ObligationsOf(JsonObject claim) => + [.. claim["obligations"]!.AsArray().Select(o => $"{o!["id"]!.GetValue<string>()}: {string.Join(", ", o["seeded_by"]!.AsArray().Select(p => p!.GetValue<string>()))}")]; + + [Fact] + public void An_obligation_quoting_a_sentence_its_source_does_not_hold_is_refused() + { + Claims(new JsonObject { ["obligations"] = new JsonArray(Obligation("keys", "A key is written by ToString().")) }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("claim C1: obligation keys quotes a sentence not found in README.md"); + } + + [Fact] + public void A_not_promised_entry_is_checked_against_its_own_source() + { + // The sentence is in the claim's README, not in the record the entry names, so it is refused. + Claims(new JsonObject + { + ["not-promised"] = new JsonArray(new JsonObject { ["id"] = "order", ["source"] = "design/adr/0001-limits.md#1-limits", ["sentence"] = "A null is refused." }), + }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("claim C1: not-promised order quotes a sentence not found in design/adr/0001-limits.md"); + } + + [Fact] + public void A_not_promised_entry_its_source_holds_is_kept() + { + Claims(new JsonObject + { + ["not-promised"] = new JsonArray(new JsonObject { ["id"] = "order", ["source"] = "design/adr/0001-limits.md#1-limits", ["sentence"] = "An ordering of keys is not promised." }), + }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(0, error); + } + + [Fact] + public void A_duplicate_obligation_id_is_refused() + { + Claims(new JsonObject + { + ["obligations"] = new JsonArray(Obligation("keys", "A key is written as the wrapped type writes it."), Obligation("keys", "A null is refused.")), + }); + var (exit, _, error) = fx.Ledger("universe", "--base", "HEAD"); + exit.ShouldBe(1); + error.ShouldContain("claim C1: obligation keys is listed twice"); + } + + [Fact] + public void A_claim_without_obligations_has_one_the_claim_seeded_by_a_seed_naming_none() + { + Register(EnvSeed()); + ObligationsOf(Status()).ShouldBe(["claim: P"]); + Problems().ShouldBeEmpty(); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void Status_shows_the_obligations_no_seed_breaks(string newLine) + { + Claims(new JsonObject { ["obligations"] = TwoObligations() }); + Register(null, ("keys", "1")); + ObligationsOf(Status()).ShouldBe(["keys: P", "nulls: "]); + var (exit, output, error) = fx.LedgerEnding([], newLine, "status"); + exit.ShouldBe(0, error); + output.ReplaceLineEndings("\n").ShouldContain("\n C1: 0/4 obligations 2 (1 without a seed: nulls), 2 uncredited\n"); + + Register(null, ("keys", "1"), ("nulls", "0")); + fx.LedgerEnding([], newLine, "status").Out.ReplaceLineEndings("\n").ShouldContain("\n C1: 0/4 obligations 2, each seeded, 2 uncredited\n"); + } + + [Fact] + public void The_check_without_a_build_names_an_obligation_no_seed_breaks() + { + Claims(new JsonObject { ["obligations"] = TwoObligations() }); + Register(EnvSeed(), ("keys", "1")); + Problems().ShouldBe(["C1: obligation nulls has no seed in any of its probes"], "a seed naming no obligation proves the probe, not an obligation the claim lists"); + Register(null, ("keys", "1"), ("nulls", "0")); + Problems().ShouldBeEmpty(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void The_check_without_a_build_names_a_seed_naming_an_obligation_the_claim_does_not_list(bool listed) + { + if (listed) Claims(new JsonObject { ["obligations"] = new JsonArray(Obligation("keys", "A key is written as the wrapped type writes it.")) }); + Register(null, ("keys", "1"), ("kyes", "0")); + Problems().ShouldBe(listed + ? ["P/kyes: names obligation kyes, which claim C1 does not list"] + : ["P/keys: names obligation keys, which claim C1 does not list", "P/kyes: names obligation kyes, which claim C1 does not list"]); + } + + [Fact] + public void Seeds_runs_each_seed_under_its_obligation() + { + Claims(new JsonObject { ["obligations"] = TwoObligations() }); + Register(null, ("keys", "1"), ("nulls", "7")); + var output = new StringWriter(); var error = new StringWriter(); + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, error); + var exit = Seeds.Run(context, new Args([], new HashSet<string>(StringComparer.Ordinal) { "dry-run" }), adapter); + + exit.ShouldBe(1, "the nulls seed makes no row violate, so it is missed and fails the command"); + var text = output.ToString().ReplaceLineEndings("\n"); + text.ShouldContain(": 2 seeds of 1 probes, in "); + text.ShouldContain("\n P/keys (C1): caught, 1 of 2 rows violate; e.g. #1 keys: violation:row 1 is broken\n"); + text.ShouldContain("\n P/nulls (C1): missed, no row violates that did not already (2 rows: pass 2;"); + var last = JsonNode.Parse(File.ReadAllText(Path.Combine(fx.BranchDir, "seeds-last.json")))!["seeds"]!.AsArray() + .Select(s => (s!["probe"]!.GetValue<string>(), s["obligation"]!.GetValue<string>(), s["seed"]!.GetValue<string>(), s["result"]!.GetValue<string>())); + last.ShouldBe([("P", "keys", "SEED_ROW=1,SEED_CHECK=keys", "caught"), ("P", "nulls", "SEED_ROW=7,SEED_CHECK=nulls", "missed")]); + } + + /// <summary> + /// The probe P over C1's two rows, without a build: row N violates where SEED_ROW is N, by its check of the + /// obligation SEED_CHECK names where it names one (each row then runs that check), else as a row without checks. + /// </summary> + private sealed class FakeAdapter : IProbeAdapter + { + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) => new(0, "Build: OK", ""); + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + var broken = environment.GetValueOrDefault("SEED_ROW"); + var check = environment.GetValueOrDefault("SEED_CHECK"); + var outDir = environment["PROBE_OUT"]!; + Directory.CreateDirectory(outDir); + var lines = Enumerable.Range(0, 2).Select(idx => + { + var violates = broken == idx.ToString(System.Globalization.CultureInfo.InvariantCulture); + var line = new JsonObject + { + ["idx"] = idx, ["category"] = violates ? "violation" : "pass", + ["reasons"] = violates && check is null ? new JsonArray($"violation:row {idx} is broken") : new JsonArray(), + }; + if (check is not null) + line["checks"] = new JsonArray(new JsonObject + { + ["obligation"] = check, ["baseline"] = "twin", ["category"] = violates ? "violation" : "pass", + ["reasons"] = violates ? new JsonArray($"violation:row {idx} is broken") : new JsonArray(), + }); + return line.ToJsonString(); + }).ToList(); + File.WriteAllText(Path.Combine(outDir, "C1.jsonl"), string.Join('\n', lines) + "\n"); + return new Ran(lines.Any(l => l.Contains("\"violation\"", StringComparison.Ordinal)) ? 1 : 0, "", ""); + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/PerCheckTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/PerCheckTests.cs new file mode 100644 index 00000000..6602c34b --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/PerCheckTests.cs @@ -0,0 +1,492 @@ +using System.Text.Json.Nodes; +using Prober; +using Probes.Shapes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary>Runs rows through the probe corpus's own harness (harness/Row.cs, compiled in) and reads the lines it writes.</summary> +internal static class HarnessRun +{ + /// <summary> + /// Runs one row of axes x and y through <c>Rec.Run</c>, writing to <paramref name="outDir"/>: the exception the + /// test ends with (a failure or a skip), if any, and the line the harness wrote for the row. + /// </summary> + public static (Exception? Ended, JsonObject Line) Row(string outDir, int idx, Func<Row, Task<string?>> check, string x = "1", string y = "1") + { + var previous = Environment.GetEnvironmentVariable("PROBE_OUT"); + Environment.SetEnvironmentVariable("PROBE_OUT", outDir); + Exception? ended = null; + try { Rec.Run("C1", new Row(idx, new Dictionary<string, string> { ["x"] = x, ["y"] = y }), check).GetAwaiter().GetResult(); } + catch (Exception e) { ended = e; } + finally { Environment.SetEnvironmentVariable("PROBE_OUT", previous); } + var line = File.ReadAllLines(Path.Combine(outDir, "C1.jsonl")).Select(l => JsonNode.Parse(l)!.AsObject()).Last(l => (int)Files.Num(l["idx"]) == idx); + return (ended, line); + } + + public static JsonObject Check(JsonObject line, string obligation) => + line["checks"]!.AsArray().OfType<JsonObject>().Single(c => Files.Str(c["obligation"]) == obligation); +} + +/// <summary> +/// A check of a row (<c>Row.Check</c>) has one obligation, one baseline, and reads, notes and an outcome of its own: a +/// failure it returns is a violation, an exception a harness error that the other checks of the row survive, and a +/// pass with no baseline a harness error. The row passes when no check failed and one passed, and is skipped, each +/// check's outcome listed, when none passed. +/// </summary> +[Collection(nameof(ProbeOut))] +public sealed class CheckHarnessTests : IDisposable +{ + private readonly string outDir = Path.Combine(Path.GetTempPath(), "prober-tests", Guid.NewGuid().ToString("N")); + + public void Dispose() + { + try { Directory.Delete(outDir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + [Fact] + public void A_failure_a_check_returns_is_its_violation_with_its_own_reads() + { + string? returned = "not run"; + var (ended, line) = HarnessRun.Row(outDir, 0, async r => + { + _ = r["x"]; + returned = await r.Check("keys", Baseline.Twin, c => Task.FromResult<string?>(c["y"] == "1" ? "the key differs" : null)); + return returned; + }); + returned.ShouldBe("the key differs", "Check returns the failure, so a probe can stop the row at it"); + ended.ShouldNotBeNull().Message.ShouldContain("keys violation: the key differs"); + Files.Str(line["category"]).ShouldBe("violation"); + Files.StringMap(line["applied"]).ShouldBe(new Dictionary<string, string> { ["x"] = "1" }, "the row's own reads are those outside any check"); + Files.Strings(line["reasons"]).ShouldBeEmpty("the failure is the check's, not the row's own"); + var check = HarnessRun.Check(line, "keys"); + Files.Str(check["baseline"]).ShouldBe("twin"); + Files.Str(check["category"]).ShouldBe("violation"); + Files.StringMap(check["applied"]).ShouldBe(new Dictionary<string, string> { ["y"] = "1" }); + Files.Strings(check["reasons"]).ShouldBe(["violation:the key differs"]); + } + + [Fact] + public void An_exception_in_a_check_is_a_harness_error_the_other_checks_survive() + { + string? returned = "not run"; + var (ended, line) = HarnessRun.Row(outDir, 0, async r => + { + returned = await r.Check("keys", Baseline.Twin, (Func<Check, Task<string?>>)(_ => throw new InvalidOperationException("the check broke"))); + return r.Check("nulls", Baseline.RoundTrip, c => c.Is("x", "1") ? null : "never"); + }); + returned.ShouldBeNull("an exception never reads as a failure, so a probe returning it makes no violation"); + ended.ShouldNotBeNull().Message.ShouldContain("keys harness-error: UNEXPECTED InvalidOperationException: the check broke"); + Files.Str(line["category"]).ShouldBe("harness-error"); + Files.Str(HarnessRun.Check(line, "keys")["category"]).ShouldBe("harness-error"); + Files.Str(HarnessRun.Check(line, "nulls")["category"]).ShouldBe("pass", "the next check of the row still ran"); + } + + [Fact] + public void The_synchronous_form_keeps_an_exception_from_the_row_too() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => + { + var returned = r.Check("keys", Baseline.Twin, (Func<Check, string?>)(_ => throw new FormatException("bad"))); + return Task.FromResult(returned); + }); + ended.ShouldNotBeNull().Message.ShouldContain("keys harness-error: UNEXPECTED FormatException: bad"); + Files.Strings(line["reasons"]).ShouldBeEmpty("the exception stayed in the check"); + } + + [Fact] + public void A_pass_without_a_baseline_is_a_harness_error() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => Task.FromResult(r.Check("keys", null, c => c.Is("x", "1") ? null : "differs"))); + ended.ShouldNotBeNull().Message.ShouldContain("keys harness-error: pass-without-baseline"); + Files.Str(HarnessRun.Check(line, "keys")["category"]).ShouldBe("harness-error"); + HarnessRun.Check(line, "keys")["baseline"].ShouldBeNull(); + } + + [Fact] + public void A_row_with_a_passing_check_and_one_that_did_not_apply_passes() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => + { + r.Check("keys", Baseline.Twin, c => c.Is("x", "1") ? null : "differs"); + r.Check("nulls", Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("closed-set-has-no-null"), "x=1")); + return Task.FromResult<string?>(null); + }); + ended.ShouldBeNull("one check compared and passed; the other did not apply, which voids nothing"); + Files.Str(line["status"]).ShouldBe("pass"); + Files.Str(line["category"]).ShouldBe("inapplicable", "the row's category is the highest of its checks'"); + Files.Strings(HarnessRun.Check(line, "nulls")["reasons"]).ShouldBe(["inapplicable:not-promised:closed-set-has-no-null(x=1)"]); + Files.Str(HarnessRun.Check(line, "nulls")["notes"]).ShouldBe("not-promised:closed-set-has-no-null(x=1)"); + } + + [Fact] + public void A_row_where_no_check_passed_is_skipped_listing_each_check() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => + { + r.Check("smallest-scale", Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("closed-set-has-no-scale"))); + r.Check("number-is-a-number", Baseline.Fact("KB-031"), c => c.Refused(Cite.Fact("KB-031"))); + r.Check("reads-a-number", Baseline.Model, c => c.Inconclusive("both threw, not alike")); + r.Check("covers", Baseline.Docs("docs/json.md#decimals-in-one-spelling"), c => c.Refused(Cite.Docs("docs/json.md#decimals-in-one-spelling"))); + r.Check("keeps", Baseline.Cost, c => c.Inapplicable(Cite.Record("0094"))); + return Task.FromResult<string?>(null); + }); + ended.ShouldNotBeNull().GetType().Name.ShouldBe("SkipException"); + ended.Message.ShouldContain("smallest-scale inapplicable: not-promised:closed-set-has-no-scale; number-is-a-number expected-refusal: fact:KB-031; " + + "reads-a-number inconclusive: both threw, not alike; covers expected-refusal: docs:docs/json.md#decimals-in-one-spelling; " + + "keeps inapplicable: record:0094"); + Files.Str(line["status"]).ShouldBe("fail"); + Files.Str(line["category"]).ShouldBe("inconclusive"); + line["checks"]!.AsArray().Select(c => Files.Str(c!["baseline"])).ShouldBe(["twin", "fact:KB-031", "model", "docs:docs/json.md#decimals-in-one-spelling", "cost"]); + } + + [Fact] + public void A_violation_fails_the_row_even_beside_a_passing_check() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => + { + r.Check("keys", Baseline.Twin, _ => (string?)null); + r.Check("nulls", Baseline.Twin, _ => "a null was written"); + return Task.FromResult<string?>(null); + }); + ended.ShouldNotBeNull().GetType().Name.ShouldNotBe("SkipException"); + ended.Message.ShouldContain("nulls violation: a null was written"); + ended.Message.ShouldNotContain("keys pass"); + Files.Str(line["status"]).ShouldBe("fail"); + } + + [Fact] + public void A_row_that_runs_no_check_is_written_as_before() + { + var (ended, line) = HarnessRun.Row(outDir, 0, r => Task.FromResult<string?>(r["x"] == "1" ? null : "differs")); + ended.ShouldBeNull(); + line.ContainsKey("checks").ShouldBeFalse(); + line.Select(kv => kv.Key).ShouldBe(["idx", "status", "category", "completed", "applied", "reasons", "findings", "note"]); + } + + [Fact] + public void Reads_notes_and_substitutions_through_the_row_inside_a_check_are_the_checks() + { + var (ended, line) = HarnessRun.Row(outDir, 0, async r => + { + await r.Check("keys", Baseline.Twin, async c => + { + await Task.Yield(); + _ = r["y"]; // a helper handed the row, inside the check + _ = r.Get("x"); + r.Substituted("x", "2", "the options ran as x=2"); + r.Note(NoteKeys.Compared, "write"); + return null; + }); + return null; + }); + ended.ShouldBeNull(); + Files.StringMap(line["applied"]).ShouldBeEmpty("the row read nothing outside the check"); + Files.Str(line["note"]).ShouldBeEmpty(); + var check = HarnessRun.Check(line, "keys"); + Files.StringMap(check["applied"]).OrderBy(kv => kv.Key, StringComparer.Ordinal).Select(kv => $"{kv.Key}={kv.Value}").ShouldBe(["x=2", "y=1"]); + Files.Str(check["notes"]).ShouldBe("substituted(x=2, the options ran as x=2);compared(write)"); + } + + [Fact] + public void A_malformed_citation_or_baseline_is_refused_at_construction() + { + Should.Throw<ArgumentException>(() => Cite.Fact("KB-31")); + Should.Throw<ArgumentException>(() => Cite.Record("94")); + Should.Throw<ArgumentException>(() => Cite.NotPromised("Closed set")); + Should.Throw<ArgumentException>(() => Cite.Docs("README.md#What TypeKit guarantees")); + Should.Throw<ArgumentException>(() => Cite.Docs("../outside.md")); + Should.Throw<ArgumentException>(() => Baseline.Fact("fact:KB-031")); + Should.Throw<ArgumentException>(() => Baseline.Docs("#anchor-alone")); + new[] { Baseline.Twin, Baseline.Framework, Baseline.RoundTrip, Baseline.Model, Baseline.Refusal, Baseline.Cost, Baseline.Fact("KB-031"), Baseline.Docs("README.md") } + .Select(b => b.Spelling).ShouldBe(["twin", "framework", "round-trip", "model", "refusal", "cost", "fact:KB-031", "docs:README.md"]); + new[] { Cite.NotPromised("a-b"), Cite.Docs("CHANGELOG.md#2.1.0-fixed"), Cite.Fact("KB-001"), Cite.Record("0094") } + .Select(c => c.Spelling).ShouldBe(["not-promised:a-b", "docs:CHANGELOG.md#2.1.0-fixed", "fact:KB-001", "record:0094"]); + + var (ended, line) = HarnessRun.Row(outDir, 0, r => Task.FromResult(r.Check("keys", Baseline.Twin, c => c.Refused(Cite.Fact("KB-31"))))); + ended.ShouldNotBeNull().Message.ShouldContain("keys harness-error: UNEXPECTED ArgumentException:"); + Files.Str(HarnessRun.Check(line, "keys")["category"]).ShouldBe("harness-error"); + } +} + +/// <summary> +/// The ledger credits checks, not rows: a passing check earns the pairs of the settings it ran under (the row's, its +/// own laid over them) whatever another check of the row ended as, never a pair put together from two checks' reads, +/// and `status` says per obligation what current checks earned and which no passing check named. The evidence is the +/// harness's own lines, copied as the replay copies them. +/// </summary> +[Collection(nameof(ProbeOut))] +public sealed class CheckLedgerTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + /// <summary>Runs P's two rows (x=1 y=1, x=2 y=2) through the harness and records the run as the replay would.</summary> + private JsonObject Record(Func<Row, Task<string?>> check) + { + var outDir = Path.Combine(fx.Root, "rows-out"); + var rows = new JsonObject(); + foreach (var (idx, v) in new[] { (0, "1"), (1, "2") }) + rows[idx.ToString(System.Globalization.CultureInfo.InvariantCulture)] = Replay.EvidenceRow(HarnessRun.Row(outDir, idx, check, v, v).Line); + var path = Path.Combine(fx.Root, "evidence.json"); + File.WriteAllText(path, new JsonObject + { + ["variants"] = new JsonArray(new JsonObject { ["env"] = new JsonObject(), ["expected"] = new JsonArray(0, 1), ["executed"] = new JsonArray(0, 1) }), + ["rows"] = rows, + }.ToJsonString()); + fx.Footprint(); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", path).Exit.ShouldBe(0); + return JsonNode.Parse(File.ReadAllText(Path.Combine(fx.BranchDir, "evidence", "P.json")))!.AsObject(); + } + + private void Obligations() + { + fx.Write("README.md", "# a\n\nThe claim.\n\nA key is written as the wrapped type writes it.\n\nA null is refused.\n"); + fx.Write("tools/probe/corpus/claims.json", """ + [{"id": "C1", "source": "README.md#a", "surfaces": ["s"], "obligations": [ + {"id": "keys", "sentence": "A key is written as the wrapped type writes it."}, + {"id": "nulls", "sentence": "A null is refused."}]}] + """); + fx.Commit("obligations"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + } + + private JsonObject Status() + { + var (exit, output, error) = fx.Ledger("status", "--json"); + exit.ShouldBe(0, error); + return JsonNode.Parse(output)!["claims"]!["C1"]!.AsObject(); + } + + [Fact] + public void A_passing_check_earns_its_pairs_beside_a_check_that_did_not_apply() + { + var evidence = Record(r => + { + r.Check("keys", Baseline.Twin, c => c["x"] == c["y"] ? null : "differs"); + r.Check("nulls", Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("no-null-here"))); + return Task.FromResult<string?>(null); + }); + Files.Num(evidence["schemaVersion"]).ShouldBe(2); + fx.Covered().ShouldBe(2, "x1y1 and x2y2, each read by the passing check, though every row ended inapplicable"); + } + + [Fact] + public void A_pair_is_never_put_together_from_two_checks_reads() + { + Record(r => + { + r.Check("keys", Baseline.Twin, c => c["x"] is not null ? null : "never"); + if (r.Index == 0) r.Check("nulls", Baseline.Twin, c => c["y"] is not null ? null : "never"); + else r.Check("nulls", Baseline.Twin, c => c["x"] is not null && c["y"] is not null ? null : "never"); + return Task.FromResult<string?>(null); + }); + fx.Covered().ShouldBe(1, "row 0's checks read x and y apart, which credits no pair; row 1's second check read both"); + } + + [Fact] + public void The_rows_own_reads_are_what_every_check_ran_under() + { + Record(r => + { + _ = r["x"]; + r.Check("keys", Baseline.Twin, c => c["y"] is not null ? null : "never"); + return Task.FromResult<string?>(null); + }); + fx.Covered().ShouldBe(2); + } + + [Fact] + public void A_row_without_checks_is_one_check_of_the_claim() + { + Record(r => Task.FromResult<string?>(r["x"] == r["y"] ? null : "differs")); + fx.Covered().ShouldBe(2); + var obligation = Status()["obligations"]!.AsArray().ShouldHaveSingleItem()!; + Files.Str(obligation["id"]).ShouldBe("claim"); + Files.Num(obligation["current"]).ShouldBe(2); + } + + [Fact] + public void Status_gives_each_obligation_its_current_pairs_and_counts_those_no_passing_check_named() + { + Obligations(); + Record(r => + { + r.Check("keys", Baseline.Twin, c => c["x"] == c["y"] ? null : "differs"); + r.Check("nulls", Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("no-null-here"))); + return Task.FromResult<string?>(null); + }); + var claim = Status(); + claim["obligations"]!.AsArray().Select(o => $"{Files.Str(o!["id"])} {Files.Num(o["current"])} {o["passed"]!.GetValue<bool>()}").ShouldBe(["keys 2 True", "nulls 0 False"]); + claim["check_outcomes"]!.AsObject().Select(kv => $"{kv.Key} {Files.Num(kv.Value)}").ShouldBe(["inapplicable 2", "pass 2"]); + claim["outcomes"]!.AsObject().Select(kv => $"{kv.Key} {Files.Num(kv.Value)}").ShouldBe(["inapplicable 2"]); + fx.Ledger("status").Out.ReplaceLineEndings("\n") + .ShouldContain("\n C1: 2/4 (rows: inapplicable 2; checks: inapplicable 2, pass 2) obligations 2 (2 without a seed: keys, nulls), 1 uncredited\n"); + } + + [Fact] + public void The_replay_keeps_each_checks_fields_and_six_reasons() + { + var line = JsonNode.Parse(""" + {"idx": 0, "category": "inapplicable", "applied": {"x": "1"}, "reasons": [], "findings": [], "note": "", + "checks": [{"obligation": "keys", "baseline": "twin", "category": "inapplicable", "applied": {"y": "1"}, + "reasons": ["inapplicable:1", "inapplicable:2", "inapplicable:3", "inapplicable:4", "inapplicable:5", "inapplicable:6", "inapplicable:7"], "notes": "n"}, + {"obligation": "nulls", "baseline": null, "category": "harness-error", "applied": {}, "reasons": [], "notes": ""}]} + """)!.AsObject(); + var row = Replay.EvidenceRow(line); + var checks = row["checks"]!.AsArray(); + checks.Count.ShouldBe(2); + Files.Str(checks[0]!["obligation"]).ShouldBe("keys"); + Files.Str(checks[0]!["baseline"]).ShouldBe("twin"); + Files.StringMap(checks[0]!["applied"]).ShouldBe(new Dictionary<string, string> { ["y"] = "1" }); + Files.Strings(checks[0]!["reasons"]).Count().ShouldBe(6); + Files.Str(checks[0]!["notes"]).ShouldBe("n"); + checks[1]!["baseline"].ShouldBeNull(); + Replay.EvidenceRow(JsonNode.Parse("""{"idx": 0, "category": "pass", "applied": {}}""")!.AsObject()).ContainsKey("checks").ShouldBeFalse(); + } +} + +/// <summary> +/// `seeds` judges a seed by the checks it fires: a seed naming obligation O is caught only by a clean violation of a +/// check naming O, in a row whose unseeded run had no violation of O; a seed naming none by any check. P's rows run a +/// check of each obligation through the real harness; SEED names the check the seeded build breaks. +/// </summary> +[Collection(nameof(ProbeOut))] +public sealed class CheckSeedsTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + + public CheckSeedsTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + fx.Write("README.md", "# a\n\nThe claim.\n\nA key is written as the wrapped type writes it.\n\nA null is refused.\n"); + fx.Write("tools/probe/corpus/claims.json", """ + [{"id": "C1", "source": "README.md#a", "surfaces": ["s"], "obligations": [ + {"id": "keys", "sentence": "A key is written as the wrapped type writes it."}, + {"id": "nulls", "sentence": "A null is refused."}]}] + """); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }, + }.ToJsonString() + "\n```\n"); + fx.Commit("obligations and the replay configuration"); + } + + public void Dispose() + { + fx.Dispose(); + try { Directory.Delete(cache, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + /// <summary>Registers P with one seed per (obligation, SEED value): the seed names the obligation, and its build breaks the check SEED names.</summary> + private void Register(params (string? Obligation, string Breaks)[] seeds) + { + fx.Write("tools/probe/corpus/registry.json", new JsonObject + { + ["probes"] = new JsonObject + { + ["P"] = new JsonObject + { + ["claim"] = "C1", ["filter"] = "*P", + ["seeds"] = new JsonArray([.. seeds.Select(s => (JsonNode)new JsonObject + { + ["obligation"] = s.Obligation, ["env"] = new JsonObject { ["SEED"] = s.Breaks }, ["defect"] = $"{s.Breaks} breaks.", + })]), + }, + }, + }.ToJsonString()); + fx.Commit("register"); + } + + private (int Exit, string Out) Seeds(HarnessAdapter adapter) + { + var output = new StringWriter(); + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, output); + int exit; + try { exit = global::Prober.Seeds.Run(context, new Args([], new HashSet<string>(StringComparer.Ordinal)), adapter); } + catch (Refusal refusal) { output.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString().ReplaceLineEndings("\n")); + } + + /// <summary>Row 1 runs a check of keys and one of nulls; the one SEED names fails. <paramref name="unseeded"/> fails a check of row 1 without a seed.</summary> + private static Func<Row, string?, Task<string?>> TwoChecks(string? unseeded = null) => (r, seed) => + { + foreach (var obligation in new[] { "keys", "nulls" }) + r.Check(obligation, Baseline.Twin, _ => r.Index == 1 && (seed == obligation || unseeded == obligation) ? $"{obligation} broke" : null); + return Task.FromResult<string?>(null); + }; + + [Fact] + public void A_seed_naming_an_obligation_is_caught_only_by_a_check_naming_it() + { + Register(("keys", "nulls"), ("nulls", "nulls")); + var (exit, output) = Seeds(new HarnessAdapter(TwoChecks())); + exit.ShouldBe(1, output); + output.ShouldContain("\n P/keys (C1): missed, no row violates that did not already (2 rows: pass 1, violation 1; 1 violate by a check naming nulls, not keys;"); + output.ShouldContain("\n P/nulls (C1): caught, 1 of 2 rows violate; e.g. #1 nulls: violation:nulls broke\n"); + } + + [Fact] + public void The_baseline_is_per_row_and_obligation() + { + Register(("keys", "keys")); + var (exit, output) = Seeds(new HarnessAdapter(TwoChecks(unseeded: "nulls"))); + exit.ShouldBe(0, output); + output.ShouldContain("\n P/keys (C1): caught, 1 of 2 rows violate; e.g. #1 keys: violation:keys broke\n", + Case.Sensitive, "row 1 violated nulls unseeded, which takes nothing from a violation of keys there"); + } + + [Fact] + public void A_seed_naming_none_is_caught_by_any_check() + { + Register((null, "nulls")); + var (exit, output) = Seeds(new HarnessAdapter(TwoChecks())); + exit.ShouldBe(0, output); + output.ShouldContain("\n P (C1): caught, 1 of 2 rows violate; e.g. #1 nulls: violation:nulls broke\n"); + } + + [Fact] + public void A_harness_error_of_the_row_outside_its_checks_makes_no_check_a_catch() + { + Register(("keys", "keys")); + var (exit, output) = Seeds(new HarnessAdapter((r, seed) => + { + if (seed is not null && r.Index == 1) r.Outcome("harness-error", "the host did not start"); + return TwoChecks()(r, seed); + })); + exit.ShouldBe(1, output); + output.ShouldContain("\n P/keys (C1): missed, no row violates that did not already (2 rows: pass 1, violation 1; 1 ended in a harness error, which is no catch;"); + } + + [Fact] + public void A_row_without_checks_catches_no_seed_naming_an_obligation() + { + Register(("keys", "keys")); + var (exit, output) = Seeds(new HarnessAdapter((r, seed) => Task.FromResult<string?>(seed is not null && r.Index == 1 ? "row 1 broke" : null))); + exit.ShouldBe(1, output); + output.ShouldContain("1 violate by a check naming claim, not keys;"); + } + + /// <summary>The probe P over two rows, each run through the real harness with the value of SEED (null unseeded).</summary> + private sealed class HarnessAdapter(Func<Row, string?, Task<string?>> check) : IProbeAdapter + { + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) => new(0, "Build: OK", ""); + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + var seed = environment.GetValueOrDefault("SEED"); + var outDir = environment["PROBE_OUT"]!; + var failed = false; + for (var idx = 0; idx < 2; idx++) + failed |= HarnessRun.Row(outDir, idx, r => check(r, seed)).Ended is not null; + return new Ran(failed ? 1 : 0, "", ""); + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/RegisterTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/RegisterTests.cs new file mode 100644 index 00000000..a8e95229 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/RegisterTests.cs @@ -0,0 +1,171 @@ +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// `register --obligation O` writes a seed per obligation (decision 104) into the entry's `seeds`, in place of +/// any seed that names O already, and refuses an O the claim does not list; `new` prints that command for each +/// listed obligation, so no seed is written into the registry by hand. C1 lists keys and nulls; C7 lists none. +/// </summary> +public sealed class RegisterTests : IDisposable +{ + private readonly Fixture fx = new(); + + public RegisterTests() + { + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src"), ["families"] = new JsonObject { ["f"] = new JsonArray("s") } }, + }.ToJsonString() + "\n```\n"); + fx.Write("tools/probe/corpus/claims.json", new JsonArray( + new JsonObject + { + ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["obligations"] = new JsonArray( + new JsonObject { ["id"] = "keys", ["sentence"] = "The claim." }, + new JsonObject { ["id"] = "nulls", ["sentence"] = "The claim." }), + }, + new JsonObject { ["id"] = "C7", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s") }).ToJsonString()); + fx.Commit("claims with obligations"); + fx.Ledger("universe", "--base", "HEAD").Exit.ShouldBe(0); + } + + public void Dispose() => fx.Dispose(); + + private string StagedRegistry => Path.Combine(fx.BranchDir, "staging", "registry.json"); + + private JsonObject Entry(string probe = "P") => JsonNode.Parse(File.ReadAllText(StagedRegistry))!["probes"]![probe]!.AsObject(); + + private (int Exit, string Out, string Err) Register(params string[] more) => + fx.Ledger(["register", "--probe", "P", "--claim", "C1", "--filter", "*P", .. more]); + + private static string Seeds(JsonObject entry) => + string.Join("; ", entry["seeds"]!.AsArray().Select(s => $"{s!["obligation"]}={s["patch"]}:{s["defect"]}")); + + [Fact] + public void An_obligation_s_seed_goes_into_seeds() + { + var (exit, output, error) = Register("--obligation", "keys", "--seed", "seeds/C1-keys.patch", "--defect", "A key is written with ToString()."); + exit.ShouldBe(0, output + error); + var entry = Entry(); + entry["seeds"]!.ToJsonString().ShouldBe("""[{"obligation":"keys","patch":"seeds/C1-keys.patch","defect":"A key is written with ToString()."}]"""); + entry.ContainsKey("seed").ShouldBeFalse("a seed naming an obligation is not the probe's one `seed`"); + output.ShouldContain("seed for obligation keys of P"); + } + + [Fact] + public void A_seed_for_an_obligation_that_has_one_replaces_it() + { + Register("--obligation", "keys", "--seed", "seeds/C1-keys.patch", "--defect", "first").Exit.ShouldBe(0); + Register("--obligation", "nulls", "--seed", "seeds/C1-nulls.patch", "--defect", "nulls").Exit.ShouldBe(0); + var (exit, output, _) = Register("--obligation", "keys", "--seed", "seeds/C1-keys-2.patch", "--defect", "second"); + exit.ShouldBe(0); + output.ShouldContain("seed for obligation keys of P, in place of the 1 it had"); + Seeds(Entry()).ShouldBe("nulls=seeds/C1-nulls.patch:nulls; keys=seeds/C1-keys-2.patch:second"); + } + + [Fact] + public void The_one_seed_naming_the_obligation_is_replaced_and_one_naming_another_kept() + { + fx.Write("tools/probe/corpus/registry.json", """ + {"probes": { + "P": {"claim": "C1", "filter": "*P", "seed": {"patch": "seeds/C1.patch", "defect": "old", "obligation": "keys"}}, + "Q": {"claim": "C1", "filter": "*Q", "seed": {"patch": "seeds/C1-q.patch", "defect": "kept", "obligation": "nulls"}}}} + """); + fx.Commit("probes whose one seed names an obligation"); + + Register("--obligation", "keys", "--seed", "seeds/C1-keys.patch", "--defect", "new").Exit.ShouldBe(0); + Entry().ContainsKey("seed").ShouldBeFalse("the seed that named keys is replaced, not kept beside the new one"); + Seeds(Entry()).ShouldBe("keys=seeds/C1-keys.patch:new"); + + fx.Ledger("register", "--probe", "Q", "--claim", "C1", "--filter", "*Q", "--obligation", "keys", "--seed", "seeds/C1-qk.patch", "--defect", "q").Exit.ShouldBe(0); + Entry("Q")["seed"]!["obligation"]!.GetValue<string>().ShouldBe("nulls"); + Seeds(Entry("Q")).ShouldBe("keys=seeds/C1-qk.patch:q"); + } + + [Theory] + [InlineData("C1", "kyes", "claim C1 lists no obligation kyes (it lists keys, nulls)")] + [InlineData("C1", "claim", "claim C1 lists no obligation claim (it lists keys, nulls)")] + [InlineData("C7", "keys", "claim C7 lists no obligation keys (it lists claim)")] + public void An_obligation_the_claim_does_not_list_is_refused_and_nothing_staged(string claim, string obligation, string message) + { + var (exit, _, error) = fx.Ledger("register", "--probe", "P", "--claim", claim, "--filter", "*P", "--obligation", obligation, "--seed", "seeds/x.patch", "--defect", "d"); + exit.ShouldBe(2); + error.ShouldContain(message); + File.Exists(StagedRegistry).ShouldBeFalse(); + } + + [Fact] + public void A_claim_without_obligations_takes_a_seed_for_its_one() + { + fx.Ledger("register", "--probe", "P", "--claim", "C7", "--filter", "*P", "--obligation", "claim", "--seed", "seeds/C7.patch", "--defect", "d").Exit.ShouldBe(0); + Seeds(Entry()).ShouldBe("claim=seeds/C7.patch:d"); + } + + [Fact] + public void A_claim_that_is_not_in_the_claims_is_refused() + { + var (exit, _, error) = fx.Ledger("register", "--probe", "P", "--claim", "C9", "--filter", "*P", "--obligation", "keys", "--seed", "seeds/x.patch", "--defect", "d"); + exit.ShouldBe(1); + error.ShouldContain("no claim C9 in claims.json"); + File.Exists(StagedRegistry).ShouldBeFalse(); + } + + [Fact] + public void An_obligation_without_a_seed_or_a_defect_is_refused() + { + var (exit, _, error) = Register("--obligation", "keys", "--defect", "d"); + exit.ShouldBe(2); + error.ShouldContain("give the seed with --seed PATCH or --seed-env NAME=VALUE"); + Register("--obligation", "keys", "--seed", "seeds/C1-keys.patch").Exit.ShouldBe(2, "a seed without its defect"); + File.Exists(StagedRegistry).ShouldBeFalse(); + } + + [Fact] + public void An_environment_toggle_is_a_seed_too() + { + Register("--obligation", "nulls", "--seed-env", "A=1", "B=x=y", "--defect", "d").Exit.ShouldBe(0); + Entry()["seeds"]!.ToJsonString().ShouldBe("""[{"obligation":"nulls","env":{"A":"1","B":"x=y"},"defect":"d"}]"""); + Register("--seed-env", "C=2", "--defect", "one").Exit.ShouldBe(0); + Entry()["seed"]!.ToJsonString().ShouldBe("""{"env":{"C":"2"},"defect":"one"}"""); + Register("--obligation", "nulls", "--seed-env", "=1", "--defect", "d").Exit.ShouldBe(2, "a toggle without a name"); + } + + [Fact] + public void A_seed_stales_no_result() + { + var before = new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null).Identity("P"); + Register("--obligation", "keys", "--seed", "seeds/C1-keys.patch", "--defect", "d").Exit.ShouldBe(0); + Register("--seed", "seeds/C1.patch", "--defect", "d").Exit.ShouldBe(0); + Entry().ContainsKey("seeds").ShouldBeTrue(); + new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null).Identity("P").ShouldBe(before); + } + + [Fact] + public void The_commands_new_prints_register_each_obligation_s_seed() + { + var (exit, output, _) = fx.Ledger("new", "--claim", "C1", "--probe", "C1KeysProbe"); + exit.ShouldBe(0); + var commands = Regex.Matches(output, @"`prober (register [^`]*)`").Select(m => m.Groups[1].Value).ToList(); + commands.Count.ShouldBe(2, output); + output.ShouldNotContain("in the entry's `seeds`"); + foreach (var command in commands) + { + var args = Regex.Matches(command, @"""([^""]*)""|(\S+)").Select(m => m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value) + .Select(arg => arg == "..." ? "What the seed breaks." : arg).ToArray(); + var (registered, said, error) = fx.Ledger(args); + registered.ShouldBe(0, command + ": " + said + error); + } + + var entry = Entry("C1KeysProbe"); + entry["filter"]!.GetValue<string>().ShouldBe("*C1KeysProbe"); + Seeds(entry).ShouldBe("keys=seeds/C1-keys.patch:What the seed breaks.; nulls=seeds/C1-nulls.patch:What the seed breaks."); + var problems = global::Prober.Seeds.Problems(new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)); + problems.ShouldNotContain(p => p.StartsWith("C1KeysProbe: no seed", StringComparison.Ordinal)); + problems.ShouldNotContain(p => p.StartsWith("C1: obligation", StringComparison.Ordinal)); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/ReplayTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/ReplayTests.cs new file mode 100644 index 00000000..b4502eec --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/ReplayTests.cs @@ -0,0 +1,368 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// The replay runs probes in parallel, by family, with or without a footprint, and `merge` joins the states +/// of a replay split by family (decision 101). The probes here are a fake adapter: P covers C1 (surface s) +/// and Q covers C2 (surface t); a run writes its rows and, when asked for one, a coverage report that names +/// src/a.cs. +/// </summary> +public sealed class ReplayTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + private readonly FakeAdapter adapter = new(); + private readonly List<string> states = []; + + public ReplayTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + Axes(new JsonObject { ["a"] = new JsonArray("s"), ["b"] = new JsonArray("t") }); + fx.Write("tools/probe/corpus/claims.json", """[{"id": "C1", "source": "README.md#a", "surfaces": ["s"]}, {"id": "C2", "source": "README.md#a", "surfaces": ["t"]}]"""); + fx.Write("tools/probe/corpus/registry.json", """{"probes": {"P": {"claim": "C1", "filter": "*P"}, "Q": {"claim": "C2", "filter": "*Q"}}}"""); + fx.Write("tools/probe/corpus/harness/Q.cs", "// probe Q\n"); + fx.Write("tools/probe/corpus/rows/Q.json", new JsonObject + { + ["claim"] = "C2", ["rows"] = new JsonArray(new JsonObject { ["row"] = new JsonObject { ["x"] = "1", ["y"] = "1" } }), + }.ToJsonString()); + fx.Commit("two probes of two families"); + } + + public void Dispose() + { + fx.Dispose(); + foreach (var dir in states.Append(cache)) + try { Directory.Delete(dir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + /// <summary>The axes file with a replay configuration; its families are the given ones, or none.</summary> + private void Axes(JsonObject? families) + { + var replay = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }; + if (families is not null) replay["families"] = families; + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject + { + ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") }, + ["t"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") }, + }, + ["replay"] = replay, + }.ToJsonString() + "\n```\n"); + } + + /// <summary>A state directory of its own, as a leg of a matrix or the job that merges them has.</summary> + private string State(string name) + { + var dir = fx.Root + "-" + name; + states.Add(dir); + return dir; + } + + private static readonly HashSet<string> Flags = new(StringComparer.Ordinal) { "all", "dry-run", "where", "no-footprint" }; + + private (int Exit, string Out, string Err) Replay(string? state, params string[] args) + { + var output = new StringWriter(); var error = new StringWriter(); + var env = new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }; + if (state is not null) env["PROBE_STATE_DIR"] = state; + int exit; + try { exit = global::Prober.Replay.Run(new Context(fx.Root, env, output, error), new Args(args, Flags), adapter); } + catch (Refusal refusal) { error.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString(), error.ToString()); + } + + /// <summary>A replay in another checkout of the repository, a linked worktree, with the fake adapter.</summary> + private (int Exit, string Out, string Err) ReplayIn(string checkout, params string[] args) + { + var output = new StringWriter(); var error = new StringWriter(); + int exit; + try { exit = global::Prober.Replay.Run(new Context(checkout, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, error), new Args(args, Flags), adapter); } + catch (Refusal refusal) { error.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString(), error.ToString()); + } + + private string Home(string checkout) => + global::Prober.Replay.Home(new Context(checkout, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, TextWriter.Null, TextWriter.Null), + new global::Prober.Ledger(new Context(checkout, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)); + + private (int Exit, string Out, string Err) Ledger(string? state, params string[] args) => + state is null ? fx.Ledger(args) : fx.LedgerWith(new Dictionary<string, string?> { ["PROBE_STATE_DIR"] = state }, args); + + private string BranchDir(string? state) => Path.Combine(state ?? fx.Memory, "branches", "main"); + private bool HasEvidence(string? state, string probe) => File.Exists(Path.Combine(BranchDir(state), "evidence", probe + ".json")); + private JsonObject Last(string? state) => (JsonObject)JsonNode.Parse(File.ReadAllText(Path.Combine(BranchDir(state), "replay-last.json")))!; + private int Lines(string? state, string file) => + File.Exists(Path.Combine(BranchDir(state), file)) ? File.ReadAllLines(Path.Combine(BranchDir(state), file)).Count(l => l.Trim().Length > 0) : 0; + + [Fact] + public void A_family_replays_the_probes_whose_claim_surface_it_holds() + { + var (exit, output, error) = Replay(null, "--all", "--family", "a"); + exit.ShouldBe(0, error + output); + output.ShouldContain("1 of 1 probes to replay (family a)"); + HasEvidence(null, "P").ShouldBeTrue(); + HasEvidence(null, "Q").ShouldBeFalse("Q's claim is of surface t, family b"); + Files.Strings(Last(null)["selected"]).ShouldBe(["P"]); + Files.Str(Last(null)["family"]).ShouldBe("a"); + + Replay(null, "--all", "--family", "b").Exit.ShouldBe(0); + HasEvidence(null, "Q").ShouldBeTrue(); + } + + [Fact] + public void A_probe_whose_surface_is_in_no_family_refuses_the_replay() + { + Axes(new JsonObject { ["a"] = new JsonArray("s") }); + fx.Commit("family b dropped"); + var (exit, _, error) = Replay(null, "--all", "--family", "a"); + exit.ShouldBe(1, "a matrix by family would never run Q"); + error.ShouldContain("Q"); + error.ShouldContain("in no family"); + HasEvidence(null, "P").ShouldBeFalse("nothing ran"); + } + + [Fact] + public void An_unknown_family_and_a_bad_parallel_are_usage_errors() + { + Replay(null, "--all", "--family", "c").Exit.ShouldBe(2); + Replay(null, "--all", "--parallel", "0").Exit.ShouldBe(2); + Replay(null, "--all", "--parallel", "two").Exit.ShouldBe(2); + Axes(null); + fx.Commit("no families"); + var (exit, _, error) = Replay(null, "--all", "--family", "a"); + exit.ShouldBe(2); + error.ShouldContain("names no families"); + } + + [Fact] + public void A_replay_without_a_footprint_records_evidence_and_leaves_the_probes_stale() + { + var (exit, output, error) = Replay(null, "--all", "--no-footprint"); + exit.ShouldBe(0, error + output); + adapter.CoverageAsked.ShouldBeEmpty("no coverage report is asked for without a footprint"); + output.ShouldContain("footprint: not recorded (--no-footprint)"); + HasEvidence(null, "P").ShouldBeTrue(); + HasEvidence(null, "Q").ShouldBeTrue(); + Lines(null, "footprints.jsonl").ShouldBe(0); + Last(null)["footprint"]!.GetValue<bool>().ShouldBeFalse(); + Ledger(null, "stale").Out.ShouldStartWith("2 of 2 probes to replay"); + + Replay(null, "--all").Exit.ShouldBe(0); + adapter.CoverageAsked.Count.ShouldBe(2); + Lines(null, "footprints.jsonl").ShouldBe(2); + Ledger(null, "stale").Out.ShouldStartWith("0 of 2 probes to replay"); + } + + [Fact] + public void The_rows_a_probe_is_expected_to_run_are_those_of_its_own_rows_file() + { + Replay(null, "--all", "--no-footprint").Exit.ShouldBe(0); + List<int> Expected(string probe) => + [.. JsonNode.Parse(File.ReadAllText(Path.Combine(BranchDir(null), "evidence", probe + ".json")))!["variants"]![0]!["expected"]!.AsArray().Select(i => i!.GetValue<int>())]; + Expected("P").ShouldBe([0, 1], "rows/P.json holds two rows"); + Expected("Q").ShouldBe([0], "rows/Q.json holds one"); + } + + [Fact] + public void Probes_run_as_many_at_a_time_as_asked_without_a_footprint_and_are_recorded_in_registry_order() + { + var (exit, output, error) = Replay(null, "--all", "--no-footprint", "--parallel", "2"); + exit.ShouldBe(0, error + output); + adapter.MostAtOnce.ShouldBe(2); + output.IndexOf(" P:", StringComparison.Ordinal).ShouldBeLessThan(output.IndexOf(" Q:", StringComparison.Ordinal)); + Last(null)["parallel"]!.GetValue<int>().ShouldBe(2); + + adapter.MostAtOnce = 0; + Replay(null, "--all", "--no-footprint", "--parallel", "1").Exit.ShouldBe(0); + adapter.MostAtOnce.ShouldBe(1); + } + + /// <summary> + /// The coverage collector rewrites the assemblies on disk, so two hosts from one output directory corrupt + /// each other (KB-025): a replay that records a footprint runs one host at a time, whatever --parallel says. + /// </summary> + [Fact] + public void A_replay_with_a_footprint_runs_one_host_at_a_time() + { + var (exit, output, error) = Replay(null, "--all", "--parallel", "2"); + exit.ShouldBe(0, error + output); + adapter.MostAtOnce.ShouldBe(1); + output.ShouldContain("one at a time (the coverage collector rewrites the assemblies on disk)"); + Last(null)["parallel"]!.GetValue<int>().ShouldBe(1); + } + + [Fact] + public void Merge_joins_the_legs_and_refuses_a_probe_no_leg_saw() + { + var legA = State("leg-a"); var legB = State("leg-b"); var merged = State("merged"); + Replay(legA, "--all", "--family", "a").Exit.ShouldBe(0); + Replay(legB, "--all", "--family", "b").Exit.ShouldBe(0); + + var (exit, output, error) = Ledger(merged, "merge", "--from", legA, legB); + exit.ShouldBe(0, error + output); + output.ShouldContain("2 leg(s)"); + HasEvidence(merged, "P").ShouldBeTrue(); + HasEvidence(merged, "Q").ShouldBeTrue(); + Lines(merged, "runs.jsonl").ShouldBe(2); + Lines(merged, "footprints.jsonl").ShouldBe(2); + Files.Strings(Last(merged)["selected"]).ShouldBe(["P", "Q"]); + Files.Strings(Last(merged)["legs"]).ShouldBe(["a", "b"]); + Ledger(merged, "stale").Out.ShouldStartWith("0 of 2 probes to replay"); + + Ledger(merged, "merge", "--from", legA, legB).Exit.ShouldBe(0); + Lines(merged, "runs.jsonl").ShouldBe(2, "a line a leg carried is added once"); + + var (shortExit, _, shortError) = Ledger(State("short"), "merge", "--from", legA); + shortExit.ShouldBe(1, "Q was in no leg"); + shortError.ShouldContain("Q"); + + var (noneExit, _, noneError) = Ledger(State("none"), "merge", "--from", State("empty")); + noneExit.ShouldBe(1); + noneError.ShouldContain("no replay of main"); + } + + [Fact] + public void Merge_fails_when_a_leg_failed() + { + adapter.Violating.Add("C1"); + var legA = State("leg-a"); var legB = State("leg-b"); var merged = State("merged"); + var (legExit, legOutput, _) = Replay(legA, "--all", "--family", "a"); + legExit.ShouldBe(1, "P violates"); + File.Exists(Path.Combine(BranchDir(legA), "logs", "P-0-host.log")).ShouldBeTrue("a failed run's full log is kept in the state"); + legOutput.ShouldContain(" log: "); + Replay(legB, "--all", "--family", "b").Exit.ShouldBe(0); + Directory.Exists(Path.Combine(BranchDir(legB), "logs")).ShouldBeFalse("a passing run keeps no log"); + + var (exit, output, _) = Ledger(merged, "merge", "--from", legA, legB); + exit.ShouldBe(1); + output.ShouldContain("failed: P"); + Files.Strings(Last(merged)["failed"]).ShouldBe(["P"]); + HasEvidence(merged, "Q").ShouldBeTrue("the merge still joined every leg"); + File.Exists(Path.Combine(BranchDir(merged), "logs", "P-0-host.log")).ShouldBeTrue("the merge carries the legs' logs"); + } + + [Fact] + public void Two_checkouts_of_one_repository_get_scratch_worktrees_of_their_own() + { + var other = fx.Linked("other"); + + var (main, linked) = (Home(fx.Root), Home(other)); + + linked.ShouldNotBe(main); + Path.GetFileName(linked).ShouldStartWith(Path.GetFileName(other) + "-"); + ReplayIn(other, "--where").Out.ShouldContain(Path.Combine(linked, "seeds")); + } + + /// <summary>Removing a linked worktree leaves its cache behind; the line `--where` prints removes it, worktrees first.</summary> + [Fact] + public void The_remove_line_of_where_removes_the_cache_and_its_worktrees() + { + var other = fx.Linked("other"); + var home = Home(other); + fx.Sh("git", "worktree", "add", "-q", "--detach", Path.Combine(home, "seeds"), "HEAD"); + var line = ReplayIn(other, "--where").Out.Split('\n').Single(l => l.StartsWith("remove:", StringComparison.Ordinal))["remove:".Length..].Trim(); + + static string Slashed(string path) => path.Replace('\\', '/'); + Slashed(line).ShouldContain($"'{Slashed(home)}'"); + fx.Sh("sh", "-c", line); + + Directory.Exists(home).ShouldBeFalse(); + Slashed(fx.Sh("git", "worktree", "list", "--porcelain")).ShouldNotContain(Slashed(home)); + } + + /// <summary>The main checkout's cache is keyed as it was before linked worktrees had their own, so its warm copy is kept.</summary> + [Fact] + public void The_main_checkout_keeps_the_cache_it_had() + { + var root = Path.GetDirectoryName(fx.Sh("git", "rev-parse", "--path-format=absolute", "--git-common-dir").Trim().TrimEnd('/', '\\'))!; + var key = Convert.ToHexStringLower(System.Security.Cryptography.SHA1.HashData(System.Text.Encoding.UTF8.GetBytes(root)))[..8]; + + Home(fx.Root).ShouldBe(Path.Combine(cache, $"{Path.GetFileName(root)}-{key}")); + } + + [Fact] + public void A_round_on_one_branch_does_not_skip_a_replay_of_another() + { + var other = fx.Linked("other"); + fx.Ledger("lock", "--holder", "round-explore-1", "--mode", "explore").Exit.ShouldBe(0); + + var (exit, output, error) = ReplayIn(other, "--all"); + + exit.ShouldBe(0, error + output); + output.ShouldNotContain("skipped"); + File.Exists(Path.Combine(fx.Memory, "branches", "other", "evidence", "P.json")).ShouldBeTrue(output); + } + + /// <summary> + /// The lock is per branch and the replay worktree per checkout: a replay of another branch in this checkout (the + /// branch switched while the post-commit replay runs) skips while one holds the worktree, and never shares it. + /// </summary> + [Fact] + public void A_replay_skips_while_another_branchs_replay_in_its_checkout_holds_the_worktree() + { + using (global::Prober.Replay.HoldWorktree(Home(fx.Root))) + { + var (exit, output, error) = Replay(null, "--all", "--branch", "other"); + + exit.ShouldBe(0, error); + output.ShouldContain("skipped, another branch's replay in this checkout"); + File.Exists(Path.Combine(fx.Memory, "branches", "other", "evidence", "P.json")).ShouldBeFalse(); + } + Replay(null, "--all", "--branch", "other").Out.ShouldNotContain("skipped"); + } + + /// <summary>P over C1's two rows and Q over C2's one; a run takes a moment, so runs at once can be counted.</summary> + private sealed class FakeAdapter : IProbeAdapter + { + private int running; + public int MostAtOnce; + public List<string> CoverageAsked { get; } = []; + public HashSet<string> Violating { get; } = new(StringComparer.Ordinal); + + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) => new(0, "Build: OK", ""); + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + var now = Interlocked.Increment(ref running); + lock (this) MostAtOnce = Math.Max(MostAtOnce, now); + try + { + Thread.Sleep(300); + var claim = filter.EndsWith('P') ? "C1" : "C2"; + var violates = Violating.Contains(claim); + var outDir = environment["PROBE_OUT"]!; + Directory.CreateDirectory(outDir); + var lines = Enumerable.Range(0, claim == "C1" ? 2 : 1).Select(idx => new JsonObject + { + ["idx"] = idx, ["category"] = violates ? "violation" : "pass", ["applied"] = new JsonObject { ["x"] = "1", ["y"] = "1" }, + ["reasons"] = violates ? new JsonArray("violation:seeded") : new JsonArray(), + }.ToJsonString()); + File.WriteAllText(Path.Combine(outDir, claim + ".jsonl"), string.Join('\n', lines) + "\n"); + if (violates) + { + Directory.CreateDirectory(environment["PROBE_LOG_DIR"]!); + File.WriteAllText(Path.Combine(environment["PROBE_LOG_DIR"]!, "host.log"), "the host's full output"); + } + if (coverageFile.Length > 0) + { + lock (this) CoverageAsked.Add(coverageFile); + Directory.CreateDirectory(results); + File.WriteAllText(Path.Combine(results, coverageFile), + $"<coverage><sources><source>{worktree}</source></sources><packages><package name=\"p\"><classes>" + + "<class name=\"A\" filename=\"src/a.cs\"><lines><line number=\"1\" hits=\"1\"/></lines></class></classes></package></packages></coverage>"); + } + return new Ran(violates ? 1 : 0, violates ? "TESTS FAILED" : "TESTS OK", ""); + } + finally { Interlocked.Decrement(ref running); } + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/RowsPerProbeTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/RowsPerProbeTests.cs new file mode 100644 index 00000000..c52e5b5d --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/RowsPerProbeTests.cs @@ -0,0 +1,299 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// Rows are per probe: each probe of a claim runs a rows file of its own, its identity reads that file only, and +/// <c>rows --probe P</c> writes it whole, over the claim's pairs the other probes of the claim have not credited. +/// </summary> +public sealed class RowsPerProbeTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private static Dictionary<string, string> R(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + + /// <summary>C1 gets a second probe, Q, with a rows file of its own; P credits the pairs of rows 11 and 22.</summary> + public RowsPerProbeTests() + { + fx.Write("tools/probe/corpus/registry.json", """{"probes": {"P": {"claim": "C1", "filter": "*P"}, "Q": {"claim": "C1", "filter": "*Q"}}}"""); + QRows("1", "2"); + fx.Commit("a second probe of C1"); + fx.Footprint(); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", fx.Evidence([R("1", "1"), R("2", "2")])).Exit.ShouldBe(0); + } + + private void QRows(string x, string y) => + fx.Write("tools/probe/corpus/rows/Q.json", $$$"""{"claim": "C1", "rows": [{"row": {"x": "{{{x}}}", "y": "{{{y}}}"}}]}"""); + + private string Staged(string relative) => Path.Combine(fx.BranchDir, "staging", relative); + + private List<string> StagedRows(string probe) + { + var staged = JsonNode.Parse(File.ReadAllText(Staged($"rows/{probe}.json")))!; + staged["claim"]!.GetValue<string>().ShouldBe("C1", "a probe's rows file keeps its claim inside"); + return [.. staged["rows"]!.AsArray().Select(r => $"{r!["row"]!["x"]}{r["row"]!["y"]}").Order(StringComparer.Ordinal)]; + } + + private string Identity(string probe) + { + var (exit, output, error) = fx.Ledger("identity", "--probe", probe); + exit.ShouldBe(0, error); + return output.Trim(); + } + + [Fact] + public void Rows_for_a_probe_leave_out_the_pairs_its_siblings_credited() + { + var (exit, output, error) = fx.Ledger("rows", "--probe", "Q"); + exit.ShouldBe(0, output + error); + StagedRows("Q").ShouldBe(["12", "21"], "P credited the pairs of 11 and 22, so Q's rows cover the other two"); + File.Exists(Staged("rows/P.json")).ShouldBeFalse("writing one probe's rows leaves its sibling's as they are"); + } + + [Fact] + public void Rows_for_a_probe_do_not_subtract_its_own_credit() + { + fx.Ledger("rows", "--probe", "P").Exit.ShouldBe(0); + StagedRows("P").ShouldBe(["11", "12", "21", "22"], "P's credit came from the rows the file replaces, so it would end with them"); + } + + [Fact] + public void The_file_is_written_whole_never_appended() + { + fx.Write(Path.GetRelativePath(fx.Root, Staged("rows/Q.json")), """{"claim": "C1", "rows": [{"row": {"x": "2", "y": "1"}}, {"row": {"x": "1", "y": "2"}}, {"row": {"x": "1", "y": "2"}}]}"""); + fx.Ledger("rows", "--probe", "Q").Exit.ShouldBe(0); + StagedRows("Q").ShouldBe(["12", "21"], "the staged rows are regenerated, not kept and added to"); + } + + [Fact] + public void A_probe_that_is_not_registered_is_refused() + { + var (exit, _, error) = fx.Ledger("rows", "--probe", "Nope"); + exit.ShouldBe(2); + error.ShouldContain("no probe Nope in the registry"); + } + + [Fact] + public void Publish_moves_the_staged_rows_by_probe_with_their_claim() + { + fx.Ledger("rows", "--probe", "Q").Exit.ShouldBe(0); + fx.Ledger("publish").Exit.ShouldBe(0); + JsonNode published; + using (var gz = new System.IO.Compression.GZipStream(File.OpenRead(Path.Combine(fx.Root, "tools/probe/corpus/rows/Q.json.gz")), System.IO.Compression.CompressionMode.Decompress)) + published = JsonNode.Parse(gz)!; + published["claim"]!.GetValue<string>().ShouldBe("C1"); + published["rows"]!.AsArray().Select(r => $"{r!["row"]!["x"]}{r["row"]!["y"]}").Order(StringComparer.Ordinal).ShouldBe(["12", "21"]); + File.Exists(Path.Combine(fx.Root, "tools/probe/corpus/rows/C1.json.gz")).ShouldBeFalse("rows are published under the probe's name, not the claim's"); + } + + [Fact] + public void A_sibling_s_rows_changing_leaves_the_probe_s_identity_and_credit() + { + var before = Identity("P"); + QRows("2", "1"); + fx.Commit("Q's rows changed"); + Identity("P").ShouldBe(before, "P's identity reads P's rows, not Q's"); + fx.Covered().ShouldBe(2, "P's pass still counts"); + } + + [Fact] + public void The_overlay_unpacks_each_probe_s_rows_and_drops_a_file_the_corpus_no_longer_has() + { + var worktree = fx.Root + "-overlay"; + try + { + var ledger = new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), "main"); + var config = new JsonObject { ["probeDir"] = "probes" }; + var rows = Path.Combine(global::Prober.Replay.Overlay(ledger, config, worktree), "rows"); + Directory.EnumerateFiles(rows).Select(Path.GetFileName).Order(StringComparer.Ordinal).ShouldBe(["P.json", "Q.json"]); + File.Delete(Path.Combine(fx.Root, "tools/probe/corpus/rows/Q.json")); + global::Prober.Replay.Overlay(ledger, config, worktree); + Directory.EnumerateFiles(rows).Select(Path.GetFileName).ShouldBe(["P.json"], "a rows file left from an earlier overlay would be read by a probe of that name"); + } + finally + { + try { Directory.Delete(worktree, recursive: true); } catch (IOException) { } + } + } + + [Fact] + public void The_probe_s_own_rows_changing_moves_its_identity() + { + var before = Identity("P"); + fx.Rows([("1", "1"), ("2", "2"), ("1", "2")]); + fx.Commit("P's rows changed"); + Identity("P").ShouldNotBe(before); + fx.Covered().ShouldBe(0); + } +} + +/// <summary> +/// A registry entry's <c>watched</c> paths stale its footprint: what the probe's result depends on that the footprint, +/// the paths its own process ran under <c>src/</c>, cannot see. +/// </summary> +public sealed class WatchedTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private const string Catalogue = "test/Variants/Catalogue.cs"; + + private static Dictionary<string, string> R(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + + private void Registry(bool watched) => + fx.Write("tools/probe/corpus/registry.json", watched + ? $$$$"""{"probes": {"P": {"claim": "C1", "filter": "*P", "watched": ["{{{{Catalogue}}}}"]}}}""" + : """{"probes": {"P": {"claim": "C1", "filter": "*P"}}}"""); + + private void ChangeTheCatalogue() + { + fx.Write(Catalogue, "class Catalogue { int Site; }\n"); + fx.Commit("the catalogue gains a site"); + } + + /// <summary>A replay's record of P: the footprint from a coverage report of src/a.cs, through the command the replay runs, and a pass.</summary> + private void Replayed() + { + var report = Path.Combine(fx.Root, "coverage.cobertura.xml"); + File.WriteAllText(report, $"""<coverage><sources><source>{fx.Root}</source></sources><packages><package><classes><class filename="src/a.cs"><lines><line number="1" hits="1" /></lines></class></classes></package></packages></coverage>"""); + var (exit, output, error) = fx.Ledger("footprint", "--probe", "P", "--root", fx.Root, "--cobertura", report); + exit.ShouldBe(0, output + error); + File.Delete(report); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", fx.Evidence([R("1", "1"), R("2", "2")])).Exit.ShouldBe(0); + } + + private bool Stale() + { + var (exit, output, error) = fx.Ledger("stale", "--json"); + exit.ShouldBe(0, error); + return JsonNode.Parse(output)!.AsArray().Single(s => s!["probe"]!.GetValue<string>() == "P")!["stale"]!.GetValue<bool>(); + } + + private Ledger Ledger() => new(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), "main"); + + [Fact] + public void The_footprint_records_each_watched_path_with_its_id() + { + Registry(watched: true); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching a catalogue"); + Replayed(); + var line = JsonNode.Parse(File.ReadLines(Path.Combine(fx.BranchDir, "footprints.jsonl")).Last())!; + line["watched"]![Catalogue]!.GetValue<string>().ShouldBe(fx.Sh("git", "rev-parse", $"HEAD:{Catalogue}").Trim()); + Stale().ShouldBeFalse(); + fx.Covered().ShouldBe(2); + } + + [Fact] + public void A_change_to_a_watched_path_stales_the_footprint() + { + Registry(watched: true); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching a catalogue"); + Replayed(); + ChangeTheCatalogue(); + Stale().ShouldBeTrue("the catalogue P watches changed"); + fx.Covered().ShouldBe(0); + } + + [Fact] + public void Without_watched_the_footprint_does_not_see_the_path() + { + Registry(watched: false); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching nothing"); + Replayed(); + ChangeTheCatalogue(); + Stale().ShouldBeFalse("the control: nothing P ran or watches changed, which is the gap watched closes"); + fx.Covered().ShouldBe(2); + } + + [Fact] + public void A_watched_path_the_footprint_did_not_record_is_not_current() + { + Registry(watched: true); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching a catalogue"); + fx.Footprint(); + var ledger = Ledger(); + ledger.FootprintCurrent("P", ledger.Footprints()).ShouldBeFalse("the footprint line records nothing of the catalogue P watches"); + } + + [Fact] + public void A_change_to_a_watched_path_changes_the_footprint_since_a_release() + { + Registry(watched: true); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching a catalogue"); + Replayed(); + fx.Sh("git", "tag", "v1"); + var ledger = Ledger(); + ledger.FootprintChangedSince("P", "v1", ledger.Footprints()).ShouldBeFalse(); + ChangeTheCatalogue(); + ledger = Ledger(); + ledger.FootprintChangedSince("P", "v1", ledger.Footprints()).ShouldBeTrue(); + } + + /// <summary>A replay's record of a probe that runs nothing under src/ in its own process, as C22, which builds its consumer in a child process.</summary> + private (int Exit, string Output, string Error) RanNothingUnderSrc() + { + var report = Path.Combine(fx.Root, "coverage.cobertura.xml"); + File.WriteAllText(report, $"""<coverage><sources><source>{fx.Root}</source></sources><packages><package><classes><class filename="test/Harness.cs"><lines><line number="1" hits="1" /></lines></class></classes></package></packages></coverage>"""); + var result = fx.Ledger("footprint", "--probe", "P", "--root", fx.Root, "--cobertura", report); + File.Delete(report); + return result; + } + + [Fact] + public void A_probe_that_ran_nothing_under_src_is_recorded_by_what_it_watches() + { + Registry(watched: true); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching a catalogue, building in a child process"); + var (exit, output, error) = RanNothingUnderSrc(); + exit.ShouldBe(0, output + error); + var line = JsonNode.Parse(File.ReadLines(Path.Combine(fx.BranchDir, "footprints.jsonl")).Last())!; + line["footprint"]!.AsObject().Count.ShouldBe(0); + line["watched"]![Catalogue]!.GetValue<string>().ShouldBe(fx.Sh("git", "rev-parse", $"HEAD:{Catalogue}").Trim()); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", fx.Evidence([R("1", "1"), R("2", "2")])).Exit.ShouldBe(0); + Stale().ShouldBeFalse("what P watches is unchanged"); + fx.Covered().ShouldBe(2); + ChangeTheCatalogue(); + Stale().ShouldBeTrue("the catalogue P watches changed"); + fx.Covered().ShouldBe(0); + } + + [Fact] + public void A_probe_that_ran_nothing_under_src_and_watches_nothing_is_refused() + { + Registry(watched: false); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe watching nothing, building in a child process"); + var (exit, output, error) = RanNothingUnderSrc(); + exit.ShouldBe(1, output + error); + (output + error).ShouldContain("refusing to record an empty footprint"); + File.Exists(Path.Combine(fx.BranchDir, "footprints.jsonl")).ShouldBeFalse(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void The_gate_counts_a_change_to_a_watched_path_as_touching_the_claim(bool watched) + { + Registry(watched); + fx.Write(Catalogue, "class Catalogue {}\n"); + fx.Commit("a probe and a catalogue"); + fx.Footprint(); + fx.Sh("git", "tag", "v1"); + ChangeTheCatalogue(); + var (_, output, _) = fx.Ledger("gate", "--since", "v1", "--json"); + var gate = JsonNode.Parse(output)!; + static List<string> Names(JsonNode? list) => [.. list!.AsArray().Select(x => x!.GetValue<string>())]; + Names(gate["touched"]).ShouldBe(watched ? ["C1"] : [], "a change to what a probe of the claim watches touches the claim, and nothing else does here"); + // The conservative rule and the footprint agree: neither selects the claim without the other. + Names(gate["selected_by_footprint_only"]).ShouldBeEmpty(); + Names(gate["missed_by_footprint_alone"]).ShouldBeEmpty(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/SeedsTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/SeedsTests.cs new file mode 100644 index 00000000..2fce1dab --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/SeedsTests.cs @@ -0,0 +1,408 @@ +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// A probe proves it can fail (decision 100): `seeds` applies the defect its registry entry names in a scratch +/// worktree, runs the probe there, and counts the seed caught only for a violation the unseeded probe does +/// not have. The probe here is a fake adapter: row 1 of C1 violates while the product holds DEFECT. +/// </summary> +public sealed class SeedsTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + private readonly FakeAdapter adapter = new(); + + public SeedsTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }, + }.ToJsonString() + "\n```\n"); + fx.Commit("replay configuration"); + } + + public void Dispose() + { + fx.Dispose(); + try { Directory.Delete(cache, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + /// <summary>Writes the seed for P: a patch made by editing the file and taking the diff, and the registry entry naming it.</summary> + private void Seed(string? file = "src/a.cs", string? edit = "class A { /* DEFECT */ }\n", JsonObject? env = null) + { + var seed = new JsonObject { ["defect"] = "A holds the defect." }; + if (file is not null) + { + var original = File.ReadAllText(Path.Combine(fx.Root, file)); + fx.Write(file, edit!); + fx.Write("tools/probe/corpus/seeds/C1.patch", fx.Sh("git", "diff", "--", file)); + fx.Write(file, original); + seed["patch"] = "seeds/C1.patch"; + } + if (env is not null) seed["env"] = env; + fx.Write("tools/probe/corpus/registry.json", new JsonObject + { + ["probes"] = new JsonObject { ["P"] = new JsonObject { ["claim"] = "C1", ["filter"] = "*P", ["seed"] = seed } }, + }.ToJsonString()); + fx.Commit("seed"); + } + + private (int Exit, string Out, string Err) Seeds(params string[] args) + { + var output = new StringWriter(); var error = new StringWriter(); + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, error); + int exit; + try { exit = global::Prober.Seeds.Run(context, new Args(args, new HashSet<string>(StringComparer.Ordinal) { "dry-run", "all" }), adapter); } + catch (Refusal refusal) { error.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString(), error.ToString()); + } + + private string Worktree => Directory.GetDirectories(cache).Single() is var home ? Path.Combine(home, "seeds") : ""; + + [Fact] + public void A_seed_the_probe_catches_passes() + { + Seed(); + var (exit, output, error) = Seeds(); + exit.ShouldBe(0, output + error); + output.ShouldContain("P (C1): caught, 1 of 2 rows violate; e.g. #1: violation:"); + output.ShouldContain("1 caught"); + } + + [Fact] + public void A_seed_the_probe_misses_fails_the_command() + { + Seed(edit: "class A { /* harmless */ }\n"); + var (exit, output, _) = Seeds(); + exit.ShouldBe(1); + output.ShouldContain("P (C1): missed, no row violates that did not already (2 rows: pass 2;"); + } + + [Fact] + public void A_violation_the_probe_has_without_the_seed_is_not_the_seeds() + { + adapter.AlwaysViolates = 1; + Seed(); + var (exit, output, _) = Seeds(); + exit.ShouldBe(1, "row 1 fails unseeded too, so the seed proved nothing"); + output.ShouldContain("1 violations unseeded"); + output.ShouldContain("P (C1): missed"); + } + + [Fact] + public void A_probe_without_a_seed_fails_the_command() + { + var (exit, output, _) = Seeds(); + exit.ShouldBe(1); + output.ShouldContain("P (C1): no seed, its registry entry names none"); + } + + [Fact] + public void An_environment_toggle_is_a_seed() + { + Seed(file: null, env: new JsonObject { ["SEED_DEFECT"] = "1" }); + var (exit, output, error) = Seeds(); + exit.ShouldBe(0, output + error); + output.ShouldContain("P (C1): caught"); + } + + [Fact] + public void A_seed_outside_the_product_files_is_refused() + { + Seed(file: "README.md", edit: "# a\n\nThe claim, DEFECT.\n"); + var (exit, output, _) = Seeds(); + exit.ShouldBe(1); + output.ShouldContain("P (C1): does not apply, it patches README.md, outside the product files (src)"); + } + + [Fact] + public void A_seed_that_no_longer_applies_fails_the_command() + { + Seed(); + fx.Write("src/a.cs", "class B {}\n"); + fx.Commit("the product moved on"); + var (exit, output, _) = Seeds(); + exit.ShouldBe(1); + output.ShouldContain("P (C1): does not apply"); + } + + [Fact] + public void A_seed_that_breaks_the_build_proves_nothing() + { + adapter.BreaksOn = "DEFECT"; + Seed(); + var (exit, output, _) = Seeds(); + exit.ShouldBe(1); + output.ShouldContain("P (C1): build failed"); + } + + [Fact] + public void The_seed_is_applied_only_in_the_scratch_worktree_and_undone() + { + Seed(); + Seeds().Exit.ShouldBe(0); + File.ReadAllText(Path.Combine(fx.Root, "src/a.cs")).ShouldBe("class A {}\n"); + File.ReadAllText(Path.Combine(Worktree, "src/a.cs")).ShouldBe("class A {}\n"); + adapter.Worktrees.ShouldAllBe(w => w == Worktree); + } + + [Fact] + public void A_seeded_run_is_never_evidence() + { + Seed(); + Seeds().Exit.ShouldBe(0); + File.Exists(Path.Combine(fx.BranchDir, "runs.jsonl")).ShouldBeFalse(); + Directory.Exists(Path.Combine(fx.BranchDir, "evidence")).ShouldBeFalse(); + File.Exists(Path.Combine(fx.BranchDir, "seeds-last.json")).ShouldBeTrue(); + } + + [Fact] + public void The_seed_is_not_part_of_the_probe_s_identity() + { + var before = fx.Ledger("identity", "--probe", "P").Out; + Seed(); + fx.Ledger("identity", "--probe", "P").Out.ShouldBe(before, "a seed proves the probe; adding or changing one must not stale its results"); + } + + /// <summary>What the replay records for P: the product files it ran, at HEAD.</summary> + private void Footprint(params string[] files) => + File.AppendAllText(Path.Combine(fx.BranchDir, "footprints.jsonl"), new JsonObject + { + ["probe"] = "P", + ["footprint"] = new JsonObject(files.Select(f => KeyValuePair.Create(f, (JsonNode?)fx.Sh("git", "rev-parse", "HEAD:" + f).Trim()))), + ["compileTime"] = new JsonObject(), ["toolchain"] = new JsonObject(), + }.ToJsonString() + "\n"); + + /// <summary>P seeded and caught once, with a footprint over src/a.cs and src/b.cs or the files given, so the next run has a record to skip by.</summary> + private void CaughtOnce(params string[] footprint) + { + fx.Write("src/b.cs", "class B {}\n"); + fx.Commit("a second product file"); + Seed(); + Footprint(footprint.Length > 0 ? footprint : ["src/a.cs", "src/b.cs"]); + var (exit, output, error) = Seeds(); + exit.ShouldBe(0, output + error); + output.ShouldContain(" runs P: no run caught every seed of it yet"); + output.ShouldContain("P (C1): caught"); + } + + private JsonObject Last() => (JsonObject)JsonNode.Parse(File.ReadAllText(Path.Combine(fx.BranchDir, "seeds-last.json")))!; + + [Fact] + public void An_unchanged_probe_is_skipped_and_says_why() + { + CaughtOnce(); + var builds = adapter.Worktrees.Count; + + var (exit, output, error) = Seeds(); + + exit.ShouldBe(0, output + error); + output.ShouldContain(" skips P: every seed caught at " + fx.Sh("git", "rev-parse", "--short=7", "HEAD").Trim() + ", and neither its identity nor its product footprint changed since"); + output.ShouldContain("0 seeds of 0 probes"); + output.ShouldContain("1 probes skipped"); + adapter.Worktrees.Count.ShouldBe(builds, "nothing built or ran"); + Last()["skipped"]!.AsArray().Single()!["probe"]!.GetValue<string>().ShouldBe("P"); + Last()["probes"]!["P"]!["commit"]!.GetValue<string>().ShouldBe(fx.Sh("git", "rev-parse", "HEAD").Trim(), "a skipped probe's record is carried forward"); + } + + [Fact] + public void A_changed_identity_reruns() + { + CaughtOnce(); + fx.Write("tools/probe/corpus/harness/P.cs", "// probe P, changed\n"); + fx.Commit("the harness changed"); + + var (_, output, _) = Seeds(); + + output.ShouldContain(" runs P: its identity or its seeds changed since they were caught at "); + output.ShouldContain("P (C1): caught"); + } + + [Fact] + public void A_changed_seed_reruns() + { + CaughtOnce(); + Seed(edit: "class A { /* DEFECT, another */ }\n"); + + Seeds().Out.ShouldContain(" runs P: its identity or its seeds changed since they were caught at "); + } + + [Fact] + public void A_changed_product_footprint_reruns() + { + CaughtOnce(); + fx.Write("src/b.cs", "class B { int changed; }\n"); + fx.Commit("the product changed where P runs"); + + var (_, output, _) = Seeds(); + + output.ShouldContain(" runs P: its product footprint changed since "); + output.ShouldContain("P (C1): caught"); + } + + /// <summary>The seed's own file may be outside what the probe ran; a change there may leave the seed behind, so it reruns.</summary> + [Fact] + public void A_change_to_the_file_the_seed_patches_reruns() + { + CaughtOnce("src/b.cs"); + fx.Write("src/a.cs", "class A {}\n// moved on\n"); + fx.Commit("the seeded file changed"); + + Seeds().Out.ShouldContain(" runs P: a file its seeds patch changed since "); + } + + [Fact] + public void A_change_outside_the_footprint_skips() + { + CaughtOnce(); + fx.Write("src/c.cs", "class C {}\n"); + fx.Commit("the product changed where P does not run"); + + Seeds().Out.ShouldContain(" skips P: "); + } + + [Fact] + public void A_probe_without_a_recorded_footprint_runs() + { + Seed(); + Seeds().Exit.ShouldBe(0); + + Seeds().Out.ShouldContain(" runs P: no product footprint recorded for it (a replay records one)"); + } + + [Fact] + public void All_ignores_the_record() + { + CaughtOnce(); + + var (exit, output, _) = Seeds("--all"); + + exit.ShouldBe(0); + output.ShouldNotContain("skips P"); + output.ShouldContain("P (C1): caught"); + Last()["all"]!.GetValue<bool>().ShouldBeTrue(); + } + + [Fact] + public void A_probe_named_runs_whatever_its_record_says() + { + CaughtOnce(); + + Seeds("--probe", "P").Out.ShouldContain("P (C1): caught"); + } + + [Fact] + public void A_missed_seed_is_never_skipped_next_time() + { + fx.Write("src/b.cs", "class B {}\n"); + fx.Commit("a second product file"); + Seed(edit: "class A { /* harmless */ }\n"); + Footprint("src/a.cs", "src/b.cs"); + Seeds().Exit.ShouldBe(1); + Last()["probes"]!.AsObject().ShouldBeEmpty(); + + var (exit, output, _) = Seeds(); + + exit.ShouldBe(1); + output.ShouldContain(" runs P: no run caught every seed of it yet"); + output.ShouldContain("P (C1): missed"); + } + + [Fact] + public void A_seed_missed_after_a_catch_drops_the_record() + { + CaughtOnce(); + Seed(edit: "class A { /* harmless */ }\n"); + Seeds().Exit.ShouldBe(1); + + Last()["probes"]!.AsObject().ContainsKey("P").ShouldBeFalse(); + Seeds().Out.ShouldContain(" runs P: no run caught every seed of it yet"); + } + + [Fact] + public void The_record_is_what_its_schema_says() + { + CaughtOnce(); + Seeds(); + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "CodoMetis.TypeKit.slnx"))) root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("no CodoMetis.TypeKit.slnx above the test output"); + var schema = (JsonObject)JsonNode.Parse(File.ReadAllText(Path.Combine(root, "tools", "Prober", "Schemas", "seeds-run.schema.json")))!["properties"]!; + + foreach (var field in new[] { "probes", "skipped", "all" }) + SchemaCheck.Problems(Last()[field], (JsonObject)schema[field]!, field).ShouldBeEmpty(field); + Last()["probes"]!["P"]!["identity"]!.GetValue<string>().ShouldBe(global::Prober.Seeds.SeedsIdentity(new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null), "P")); + } + + private List<string> Problems() => global::Prober.Seeds.Problems(new Ledger(new Context(fx.Root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)); + + [Fact] + public void The_check_without_a_build_names_a_probe_without_a_seed() + { + Problems().ShouldBe(["P: no seed, its registry entry names none"]); + Seed(); + Problems().ShouldBeEmpty(); + } + + [Fact] + public void The_check_without_a_build_names_a_seed_the_product_left_behind() + { + Seed(); + fx.Write("src/a.cs", "class B {}\n"); + fx.Commit("the product moved on"); + Problems().ShouldHaveSingleItem().ShouldStartWith("P: does not apply, "); + } + + [Fact] + public void Every_probe_in_the_repository_has_a_seed_that_applies() + { + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "CodoMetis.TypeKit.slnx"))) root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("no CodoMetis.TypeKit.slnx above the test output"); + global::Prober.Seeds.Problems(new Ledger(new Context(root, new Dictionary<string, string?>(), TextWriter.Null, TextWriter.Null), null)).ShouldBeEmpty(); + } + + /// <summary>The probe P over C1's two rows, without a build: row 1 violates where src/a.cs holds DEFECT or SEED_DEFECT is set.</summary> + private sealed class FakeAdapter : IProbeAdapter + { + public int? AlwaysViolates { get; set; } + public string? BreaksOn { get; set; } + public List<string> Worktrees { get; } = []; + + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) + { + Worktrees.Add(worktree); + return BreaksOn is { } marker && File.ReadAllText(Path.Combine(worktree, "src", "a.cs")).Contains(marker, StringComparison.Ordinal) + ? new Ran(1, "Build: FAILED", "") + : new Ran(0, "Build: OK", ""); + } + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + Worktrees.Add(worktree); + var defect = File.ReadAllText(Path.Combine(worktree, "src", "a.cs")).Contains("DEFECT", StringComparison.Ordinal) + || environment.GetValueOrDefault("SEED_DEFECT") == "1"; + var outDir = environment["PROBE_OUT"]!; + Directory.CreateDirectory(outDir); + var lines = Enumerable.Range(0, 2).Select(idx => + { + var violates = idx == AlwaysViolates || (defect && idx == 1); + return new JsonObject + { + ["idx"] = idx, ["category"] = violates ? "violation" : "pass", + ["reasons"] = violates ? new JsonArray("violation:A holds the defect") : new JsonArray(), + }.ToJsonString(); + }); + File.WriteAllText(Path.Combine(outDir, "C1.jsonl"), string.Join('\n', lines) + "\n"); + return new Ran(lines.Any(l => l.Contains("violation", StringComparison.Ordinal)) ? 1 : 0, "", ""); + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/SourceLintTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/SourceLintTests.cs new file mode 100644 index 00000000..7e28922b --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/SourceLintTests.cs @@ -0,0 +1,231 @@ +using System.Text.Json.Nodes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// `register` and `publish` read the staged sources before anything is built (decision 103): a note names a +/// key of the catalogue as <c>NoteKeys.Name</c>, the harness's two-argument skip names an open finding of +/// this ledger as a string literal, and a check's <c>Inapplicable</c> or <c>Refused</c> cites by a <c>Cite</c> +/// factory with a string literal something that exists. Each refusal names the file and the line. +/// </summary> +public sealed class SourceLintTests : IDisposable +{ + private readonly Fixture fx = new(); + + public SourceLintTests() + { + fx.Write("tools/probe/corpus/harness/Row.cs", """ + public sealed class NoteKeys + { + // public static readonly NoteKeys InAComment = new("in-a-comment", Pass); + public static readonly NoteKeys Compared = new("compared", Pass); + public static readonly NoteKeys BothThrew = new("both-threw", ExpectedRefusal); + } + """); + // C1's not-promised entry, C2's, and what a citation may name besides: a file of the docs, a knowledge entry, a record. + fx.Write("tools/probe/corpus/claims.json", new JsonArray( + new JsonObject { ["id"] = "C1", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["not-promised"] = new JsonArray(new JsonObject { ["id"] = "c1-limit", ["source"] = "README.md#a", ["sentence"] = "The claim." }) }, + new JsonObject { ["id"] = "C2", ["source"] = "README.md#a", ["surfaces"] = new JsonArray("s"), + ["not-promised"] = new JsonArray(new JsonObject { ["id"] = "c2-limit", ["source"] = "README.md#a", ["sentence"] = "The claim." }) }).ToJsonString()); + fx.Write("design/knowledge/KB-007-a-fact.md", "# KB-007\n"); + fx.Write("design/adr/0094-a-record.md", "# 94\n"); + fx.Commit("the catalogue"); + } + + public void Dispose() => fx.Dispose(); + + private string Staged => Path.Combine(fx.BranchDir, "staging"); + + private void Stage(string body) => + fx.Write(Path.GetRelativePath(fx.Root, Path.Combine(Staged, "probes", "C1Probe.cs")), $$""" + public sealed class C1Probe + { + static string? Check(Row r, string x) + { + var rest = new[] { 1, 2 }.Skip(x.Length); + // r.Note("a comment is no call", x); + var text = "r.Note(\"a string is no call\")"; + {{body}} + return null; + } + } + """); + + /// <summary>Stages a source of the probe P (C1's), or a harness source no one probe owns, holding <paramref name="body"/> in a check.</summary> + private void StageCheck(string body, string file = "probes/P.cs") => + fx.Write(Path.GetRelativePath(fx.Root, Path.Combine(Staged, file)), $$""" + public sealed class P + { + static string? Check(Check c, string x) + { + var refused = new Entry().Refused(); + // c.Inapplicable(Cite.NotPromised("in-a-comment")); + var text = "c.Refused(Cite.Docs(\"in-a-string.md\"))"; + {{body}} + return null; + } + } + """); + + private (int Exit, string Out, string Err) Register() => fx.Ledger("register", "--probe", "P", "--claim", "C1", "--filter", "*C1Probe"); + + private void Finding(string id, string status) => + File.AppendAllText(Path.Combine(fx.BranchDir, "findings.jsonl"), + new JsonObject { ["id"] = id, ["claim"] = "C1", ["status"] = status }.ToJsonString() + "\n"); + + [Fact] + public void A_probe_whose_notes_and_skips_are_written_out_registers() + { + Finding("F7", "open"); + Stage(""" + r.Note(NoteKeys.Compared, $"{x}: {(x.Length > 1 ? "long" : "short")}"); + r.Note(NoteKeys.BothThrew); + r.Skip("F7", "the open finding"); + """); + var (exit, output, error) = Register(); + exit.ShouldBe(0, output + error); + } + + [Fact] + public void A_note_key_built_from_text_is_refused_by_file_and_line() + { + Stage(""" r.Note("msg:" + x, "detail");"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("staging/probes/C1Probe.cs:8: Note(\"msg:\" + x, ...) does not name a key of NoteKeys"); + } + + [Fact] + public void A_note_key_the_catalogue_does_not_declare_is_refused() + { + Stage(""" r.Note(NoteKeys.InAComment);"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("C1Probe.cs:8: Note(NoteKeys.InAComment, ...) names no key of NoteKeys"); + } + + [Fact] + public void A_finding_built_from_text_is_refused() + { + Stage(""" r.Skip("F" + x, "why");"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("C1Probe.cs:8: Skip(\"F\" + x, ...) does not name its finding as a string literal"); + } + + [Fact] + public void A_skip_for_a_finding_that_is_not_open_is_refused() + { + Finding("F7", "open"); + Finding("F7", "withdrawn"); + Stage(""" + r.Skip("F7", "withdrawn since"); + r.Skip("F8", "never filed"); + """); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("C1Probe.cs:8: Skip(\"F7\", ...) names finding F7, which is withdrawn"); + error.ShouldContain("C1Probe.cs:9: Skip(\"F8\", ...) names no finding this ledger has"); + } + + [Fact] + public void Publish_refuses_what_register_refuses_and_moves_nothing() + { + Stage(""" r.Note("msg:" + x, "detail");"""); + var (exit, _, error) = fx.Ledger("publish"); + exit.ShouldBe(1); + error.ShouldContain("publish: "); + error.ShouldContain("C1Probe.cs:8: Note(\"msg:\" + x, ...)"); + File.Exists(Path.Combine(fx.Root, "tools/probe/corpus/probes/C1Probe.cs")).ShouldBeFalse(); + } + + [Fact] + public void A_check_citing_what_exists_registers() + { + StageCheck(""" + c.Inapplicable(Cite.NotPromised("c1-limit"), x); + c.Refused(Cite.Docs("README.md#a")); + c.Refused(Cite.Docs("README.md")); + c.Inapplicable(Cite.Fact("KB-007")); + c.Refused(Cite.Record( "0094" ), "detail"); + """); + var (exit, output, error) = Register(); + exit.ShouldBe(0, output + error); + } + + [Fact] + public void A_citation_that_is_not_a_factory_with_a_string_literal_is_refused() + { + StageCheck(""" + var cite = Cite.NotPromised("c1-limit"); + c.Inapplicable(cite); + c.Refused(Cite.NotPromised("c1-" + x)); + c.Inapplicable(Cite.Text("c1-limit")); + """); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("staging/probes/P.cs:9: Inapplicable(cite, ...) does not cite by a Cite factory with a string literal"); + error.ShouldContain("P.cs:10: Refused(Cite.NotPromised(\"c1-\" + x), ...) does not cite by a Cite factory with a string literal"); + error.ShouldContain("P.cs:11: Inapplicable(Cite.Text(\"c1-limit\"), ...) does not cite by a Cite factory with a string literal"); + } + + [Fact] + public void A_not_promised_id_the_probes_claim_does_not_list_is_refused() + { + StageCheck(""" c.Inapplicable(Cite.NotPromised("c2-limit"));"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("P.cs:8: Inapplicable(Cite.NotPromised(\"c2-limit\"), ...) names no not-promised entry of its claim C1"); + } + + [Fact] + public void A_harness_source_cites_any_claims_not_promised_entry_and_none_that_no_claim_lists() + { + StageCheck(""" + c.Inapplicable(Cite.NotPromised("c2-limit")); + c.Inapplicable(Cite.NotPromised("no-claim-lists-it")); + """, "harness/Shared.cs"); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldNotContain("Shared.cs:8:"); + error.ShouldContain("staging/harness/Shared.cs:9: Inapplicable(Cite.NotPromised(\"no-claim-lists-it\"), ...) names no not-promised entry of any claim"); + } + + [Fact] + public void A_docs_source_naming_no_file_is_refused() + { + StageCheck(""" c.Refused(Cite.Docs("docs/gone.md#a"));"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("P.cs:8: Refused(Cite.Docs(\"docs/gone.md#a\"), ...) names no file docs/gone.md in the repository"); + } + + [Fact] + public void A_fact_with_no_knowledge_entry_is_refused() + { + StageCheck(""" c.Refused(Cite.Fact("KB-070"));"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("P.cs:8: Refused(Cite.Fact(\"KB-070\"), ...) names no entry under design/knowledge/"); + } + + [Fact] + public void A_record_with_no_file_under_design_adr_is_refused() + { + StageCheck(""" c.Inapplicable(Cite.Record("0095"));"""); + var (exit, _, error) = Register(); + exit.ShouldBe(1); + error.ShouldContain("P.cs:8: Inapplicable(Cite.Record(\"0095\"), ...) names no record under design/adr/"); + } + + [Fact] + public void Publish_refuses_a_citation_that_names_nothing() + { + StageCheck(""" c.Inapplicable(Cite.Record("0095"));"""); + var (exit, _, error) = fx.Ledger("publish"); + exit.ShouldBe(1); + error.ShouldContain("publish: "); + error.ShouldContain("P.cs:8: Inapplicable(Cite.Record(\"0095\"), ...) names no record under design/adr/"); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/ToolchainTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/ToolchainTests.cs new file mode 100644 index 00000000..3c22bc0d --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/ToolchainTests.cs @@ -0,0 +1,142 @@ +using System.Text.Json.Nodes; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// A result is bound to what the axes file's toolchain commands print. A command that cannot say it refuses, by +/// name: hashed into the identity, its error text made every result read as stale, so `status` showed 0% current +/// when the dotnet first on PATH had no SDK global.json accepts. +/// </summary> +public sealed class ToolchainTests : IDisposable +{ + private readonly Fixture fx = new(); + public void Dispose() => fx.Dispose(); + + private static Dictionary<string, string> R(string x, string y) => new(StringComparer.Ordinal) { ["x"] = x, ["y"] = y }; + + private void Toolchain(params string[] command) + { + var path = Path.Combine(fx.Root, ".claude/probe-axes.md"); + var text = File.ReadAllText(path); + var block = text[(text.IndexOf('\n') + 1)..text.LastIndexOf("```", StringComparison.Ordinal)]; + var axes = JsonNode.Parse(block)!.AsObject(); + axes["replay"] = new JsonObject { ["toolchain"] = new JsonObject { ["sdk"] = new JsonArray([.. command.Select(c => (JsonNode)c)]) } }; + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + axes.ToJsonString() + "\n```\n"); + fx.Commit("toolchain"); + } + + /// <summary>A pass recorded under a toolchain that names itself, then read under one that cannot.</summary> + private void RecordedThenBroken(params string[] command) + { + Toolchain("git", "--version"); + RecordPass(); + Toolchain(command); + } + + private void RecordPass() + { + fx.Footprint(); + fx.Ledger("record-run", "--probe", "P", "--result", "pass", "--evidence", fx.Evidence([R("1", "1"), R("2", "2")])).Exit.ShouldBe(0); + } + + /// <summary>The ways a toolchain command fails to name the toolchain, each with what the refusal must say.</summary> + public static TheoryData<string[], string> Failing() => new() + { + { ["git", "no-such-subcommand"], "exited 1" }, + { ["cmtk-no-such-program"], "did not start" }, + { ["git", "diff", "--quiet"], "printed nothing" }, + { [], "is empty" }, + }; + + [Theory, MemberData(nameof(Failing))] + public void A_toolchain_command_that_cannot_name_the_toolchain_refuses(string[] command, string reason) + { + RecordedThenBroken(command); + var (exit, output, error) = fx.Ledger("status"); + exit.ShouldBe(1, output); + error.ShouldContain("the toolchain command sdk"); + error.ShouldContain(reason); + output.ShouldNotContain("pairs current", Case.Sensitive, "a refusal, never a status of 0% current"); + } + + /// <summary> + /// The case that was found: a dotnet with no SDK global.json accepts lists the installed SDKs on stdout, which + /// reads like a version, and says why on stderr. The refusal quotes the why. + /// </summary> + [Fact] + public void A_dotnet_without_the_pinned_sdk_refuses_with_its_error_not_its_sdk_list() + { + fx.Write("global.json", """{"sdk": {"version": "0.0.1"}}"""); + RecordedThenBroken("dotnet", "--version"); + var (exit, output, error) = fx.Ledger("status"); + exit.ShouldBe(1, output); + error.ShouldContain("the toolchain command sdk (`dotnet --version`) exited"); + error.ShouldNotContain("sdk]", Case.Insensitive, "the installed SDKs on stdout are not the reason"); + } + + /// <summary>Every command that reads an identity, not only status.</summary> + [Theory] + [InlineData("status")] + [InlineData("gate")] + [InlineData("stale")] + [InlineData("rows", "--claim", "C1")] + [InlineData("identity", "--probe", "P")] + public void Every_reader_of_an_identity_refuses(params string[] command) + { + RecordedThenBroken("git", "no-such-subcommand"); + var (exit, _, error) = fx.Ledger(command); + exit.ShouldBe(1, error); + error.ShouldContain("the toolchain command sdk"); + } + + /// <summary> + /// The case that was found next: Homebrew's dotnet first on PATH (10.0.401 against the 10.0.402 pin) made every + /// `dotnet` the prober starts fail, though the SDK installer's had the pinned SDK. Here the dotnet that runs the + /// built prober has no SDK the pin accepts, and a stub in the installer's place (TOOLS_DOTNET_FALLBACK_DIR) answers: + /// the prober falls back to it, records the pass under what it prints, and keeps the pass current. + /// </summary> + [Fact] + public void A_dotnet_without_the_pinned_sdk_falls_back_to_the_installers() + { + Assert.SkipWhen(OperatingSystem.IsWindows(), "the stub installer is a shell script"); + Toolchain("dotnet", "--version"); + fx.Write("global.json", """{"sdk": {"version": "0.0.1", "rollForward": "disable"}}"""); + fx.Commit("a pin no dotnet here has"); + fx.Footprint(); + var evidence = fx.Evidence([R("1", "1"), R("2", "2")]); + + var installer = Directory.CreateTempSubdirectory("prober-installer-").FullName; + try + { + var stub = Path.Combine(installer, "dotnet"); + File.WriteAllText(stub, "#!/bin/sh\necho 0.0.1\n"); + if (!OperatingSystem.IsWindows()) File.SetUnixFileMode(stub, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + using var sandbox = new ToolSandbox("Prober", "prober"); + // The prober runs as its own process, so the caller's state settings would reach it: under a PROBE_STATE_DIR it + // recorded the run there, not in this repository's state, and status found no claim (#58). None reaches it. + var environment = new Dictionary<string, string?> + { + ["TOOLS_DOTNET_FALLBACK_DIR"] = installer, ["PROBE_STATE_DIR"] = null, ["PROBE_CACHE_DIR"] = null, ["PROBE_HOLDER"] = null, + }; + (int, string, string) Prober(params string[] arguments) => ToolSandbox.RunProcess("dotnet", [sandbox.Dll, .. arguments], fx.Root, environment); + + var (recorded, _, why) = Prober("record-run", "--probe", "P", "--result", "pass", "--evidence", evidence); + recorded.ShouldBe(0, why); + var (exit, output, error) = Prober("status", "--json"); + exit.ShouldBe(0, error); + ((int)JsonNode.Parse(output)!["claims"]!["C1"]!["current"]!.GetValue<double>()).ShouldBe(2, "the pass, recorded under the fallback, is current"); + } + finally + { + Directory.Delete(installer, recursive: true); + } + } + + [Fact] + public void A_toolchain_that_names_itself_keeps_the_result_current() + { + Toolchain("git", "--version"); + RecordPass(); + fx.Covered().ShouldBe(2); + } +} diff --git a/test/CodoMetis.TypeKit.Prober.Tests/TryProbeTests.cs b/test/CodoMetis.TypeKit.Prober.Tests/TryProbeTests.cs new file mode 100644 index 00000000..7b0076f4 --- /dev/null +++ b/test/CodoMetis.TypeKit.Prober.Tests/TryProbeTests.cs @@ -0,0 +1,265 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json.Nodes; +using Prober; + +namespace CodoMetis.TypeKit.Prober.Tests; + +/// <summary> +/// `try` is the writer's loop for one probe: it builds once in the replay's worktree, runs that probe alone, +/// prints how each row ended and exits with the probe's code, and records nothing. The probes here are a fake +/// adapter: P runs C1's three rows (row 1 violates, and the run then exits 7) and Q runs C2's. +/// </summary> +public sealed class TryProbeTests : IDisposable +{ + private readonly Fixture fx = new(); + private readonly string cache; + private readonly FakeAdapter adapter = new(); + + public TryProbeTests() + { + cache = fx.Root + "-cache"; + fx.Sh("git", "config", "core.autocrlf", "false"); + fx.Write(".claude/probe-axes.md", "```json probe-axes\n" + new JsonObject + { + ["axes"] = new JsonObject { ["x"] = new JsonArray("1", "2"), ["y"] = new JsonArray("1", "2") }, + ["surfaces"] = new JsonObject { ["s"] = new JsonObject { ["axes"] = new JsonArray("x", "y"), ["watched"] = new JsonArray("src") } }, + ["replay"] = new JsonObject { ["probeDir"] = "probes", ["include"] = new JsonArray("src") }, + }.ToJsonString() + "\n```\n"); + fx.Write("tools/probe/corpus/claims.json", """[{"id": "C1", "source": "README.md#a", "surfaces": ["s"]}, {"id": "C2", "source": "README.md#a", "surfaces": ["s"]}]"""); + fx.Write("tools/probe/corpus/rows/Q.json", """{"claim": "C2", "rows": [{"row": {"x": "1", "y": "1"}}]}"""); + fx.Write("tools/probe/corpus/harness/Q.cs", "// probe Q\n"); + Registry(new JsonObject { ["claim"] = "C1", ["filter"] = "*P" }); + fx.Rows([("1", "1"), ("2", "2"), ("1", "2")]); + fx.Commit("two probes, C1 of three rows"); + } + + public void Dispose() + { + fx.Dispose(); + try { Directory.Delete(cache, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + + private void Registry(JsonObject p) => + fx.Write("tools/probe/corpus/registry.json", new JsonObject + { + ["probes"] = new JsonObject { ["P"] = p, ["Q"] = new JsonObject { ["claim"] = "C2", ["filter"] = "*Q" } }, + }.ToJsonString()); + + private (int Exit, string Out, string Err) Try(params string[] args) => TryEnding(Environment.NewLine, args); + + /// <summary>As <see cref="Try"/>, with the line end the output writers use, so a test sees Windows' output on any OS.</summary> + private (int Exit, string Out, string Err) TryEnding(string newLine, params string[] args) + { + var output = new StringWriter { NewLine = newLine }; var error = new StringWriter { NewLine = newLine }; + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, output, error); + int exit; + try { exit = TryProbe.Run(context, new Args(args, new HashSet<string>(StringComparer.Ordinal) { "no-build" }), adapter); } + catch (Refusal refusal) { error.WriteLine(refusal.Message); exit = refusal.ExitCode; } + return (exit, output.ToString(), error.ToString()); + } + + /// <summary>The output's lines, whatever line end the writer used.</summary> + private static string[] Lines(string output) => output.ReplaceLineEndings("\n").Split('\n'); + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void It_runs_the_one_probe_and_prints_how_each_row_ended(string newLine) + { + var (exit, output, error) = TryEnding(newLine, "--probe", "P"); + exit.ShouldBe(7, output + error); + adapter.Builds.ShouldBe(1); + adapter.Runs.ShouldHaveSingleItem().Filter.ShouldBe("*P"); + adapter.Runs[0].Rows.ShouldBeNull("every row, as the registry runs it"); + var lines = Lines(output); + lines.ShouldContain(l => l.StartsWith(" P: Tests: 1 failed of 3 (exit 7, ", StringComparison.Ordinal)); + lines.ShouldContain(" #0 pass"); + lines.ShouldContain(" #1 violation: row 1 breaks the claim"); + lines.ShouldContain(" #2 pass"); + lines.ShouldContain("prober try: P: pass 2, violation 1; exit 7"); + } + + [Fact] + public void A_probe_that_passes_exits_0() + { + adapter.Violates = null; + var (exit, output, _) = Try("--probe", "P"); + exit.ShouldBe(0, output); + output.ShouldContain("prober try: P: pass 3; exit 0"); + } + + [Fact] + public void It_records_nothing_under_the_state_directory() + { + // A staged probe and a ledger with runs and evidence, as a round leaves them. + fx.Write(".claude/agent-memory-local/prober/branches/main/staging/probes/R.cs", "// staged\n"); + fx.Write(".claude/agent-memory-local/prober/branches/main/runs.jsonl", """{"probe": "P", "result": "pass"}""" + "\n"); + var before = Hash(fx.Memory); + Try("--probe", "P").Exit.ShouldBe(7); + Try("--probe", "P", "--rows", "0").Exit.ShouldBe(0); + Hash(fx.Memory).ShouldBe(before); + } + + [Fact] + public void Rows_runs_the_rows_given_one_stretch_at_a_time() + { + var (exit, output, _) = Try("--probe", "P", "--rows", "0,2"); + exit.ShouldBe(0, output); + adapter.Runs.Select(r => r.Rows).ShouldBe(["0-0", "2-2"]); + output.ShouldContain("#0 pass"); + output.ShouldContain("#2 pass"); + output.ShouldNotContain("#1 "); + Try("--probe", "P", "--rows", "1,2").Exit.ShouldBe(7); + adapter.Runs.Skip(2).Select(r => r.Rows).ShouldBe(["1-2"]); + } + + [Fact] + public void Rows_runs_each_row_in_the_variant_that_selects_it() + { + Registry(new JsonObject + { + ["claim"] = "C1", ["filter"] = "*P", + ["variants"] = new JsonArray(new JsonObject { ["PROBE_ONLY"] = "x=1" }, new JsonObject { ["PROBE_ONLY"] = "x=2" }), + }); + fx.Commit("variants"); + var (exit, output, _) = Try("--probe", "P", "--rows", "1,2"); + exit.ShouldBe(7, output); + adapter.Runs.Select(r => (r.Only, r.Rows)).ShouldBe([("x=1", "2-2"), ("x=2", "1-1")]); + output.ShouldContain(" P [PROBE_ONLY=x=1] rows 2-2: "); + } + + [Fact] + public void A_row_the_claim_does_not_have_is_refused_before_anything_runs() + { + var (exit, _, error) = Try("--probe", "P", "--rows", "3"); + exit.ShouldBe(2); + error.ShouldContain("--rows 3: the claim has 3 row(s), 0 to 2"); + adapter.Builds.ShouldBe(0); + adapter.Runs.ShouldBeEmpty(); + } + + [Fact] + public void A_probe_not_in_the_registry_is_refused() + { + var (exit, _, error) = Try("--probe", "Nope"); + exit.ShouldBe(2); + error.ShouldContain("no probe Nope in the registry"); + } + + [Fact] + public void No_build_runs_without_building() + { + Try("--probe", "P", "--no-build").Exit.ShouldBe(2, "no worktree yet, so nothing was ever built"); + Try("--probe", "P").Exit.ShouldBe(7); + Try("--probe", "P", "--no-build").Exit.ShouldBe(7); + adapter.Builds.ShouldBe(1); + adapter.Runs.Count.ShouldBe(2); + } + + [Fact] + public void A_failed_build_runs_nothing() + { + adapter.BuildFails = true; + var (exit, output, _) = Try("--probe", "P"); + exit.ShouldBe(1); + output.ShouldContain("Build: FAILED"); + adapter.Runs.ShouldBeEmpty(); + } + + [Fact] + public void The_staging_is_overlaid_where_the_probe_runs() + { + fx.Write(".claude/agent-memory-local/prober/branches/main/staging/probes/R.cs", "// staged\n"); + Try("--probe", "P").Exit.ShouldBe(7); + var worktree = Path.Combine(Directory.GetDirectories(cache).Single(), "wt"); + adapter.Worktrees.ShouldAllBe(w => w == worktree); + File.ReadAllText(Path.Combine(worktree, "probes", "R.cs")).ShouldBe("// staged\n"); + File.Exists(Path.Combine(worktree, "probes", "P.cs")).ShouldBeTrue(); + } + + /// <summary>Every file and directory under <paramref name="dir"/>, by relative path and content.</summary> + private static string Hash(string dir) => + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join("\n", + Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories).Order(StringComparer.Ordinal) + .Select(p => Path.GetRelativePath(dir, p) + ":" + (File.Exists(p) ? Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(p))) : "dir")))))); + + [Fact] + public void It_holds_the_replay_worktree_while_it_runs() + { + var (exit, output, error) = Try("--probe", "P"); + exit.ShouldBe(7, output + error); + adapter.WorktreeFreeDuringRun.ShouldBe([false], "a replay of the same checkout must not take the worktree while a try runs in it"); + } + + [Fact] + public void It_refuses_while_a_replay_holds_the_worktree_and_runs_nothing() + { + var context = new Context(fx.Root, new Dictionary<string, string?> { ["PROBE_CACHE_DIR"] = cache }, TextWriter.Null, TextWriter.Null); + var home = global::Prober.Replay.Home(context, new Ledger(context, null)); + using (global::Prober.Replay.HoldWorktree(home).ShouldNotBeNull()) + { + var (exit, _, error) = Try("--probe", "P"); + exit.ShouldBe(1); + error.ShouldContain($"a replay in this checkout is using {Path.Combine(home, "wt")}; run `prober try` when it ends"); + adapter.Runs.ShouldBeEmpty(); + } + Try("--probe", "P").Exit.ShouldBe(7, "the worktree is free again once the replay ends"); + } + + /// <summary> + /// Runs the probe the filter names over its claim's rows in the worktree, as the harness does: PROBE_ONLY and + /// PROBE_ROWS select, and each row it ran is one line of PROBE_OUT's jsonl. Row 1 of C1 violates. + /// </summary> + private sealed class FakeAdapter : IProbeAdapter + { + public int? Violates { get; set; } = 1; + public bool BuildFails { get; set; } + public int Builds { get; private set; } + public List<(string Filter, string? Only, string? Rows)> Runs { get; } = []; + public List<string> Worktrees { get; } = []; + /// <summary>For each run, whether a replay could have taken the worktree (<c>replay.lock</c> beside it) while the probe ran.</summary> + public List<bool> WorktreeFreeDuringRun { get; } = []; + + public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environment) + { + Builds++; + Worktrees.Add(worktree); + return BuildFails ? new Ran(1, "Build: FAILED\nerror X", "") : new Ran(0, "Build: OK", ""); + } + + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + Worktrees.Add(worktree); + using (var replay = global::Prober.Replay.HoldWorktree(Path.GetDirectoryName(worktree)!)) WorktreeFreeDuringRun.Add(replay is not null); + var only = environment.GetValueOrDefault("PROBE_ONLY"); + var range = environment.GetValueOrDefault("PROBE_ROWS"); + Runs.Add((filter, only, range)); + coverageFile.ShouldBeEmpty("try asks for no coverage"); + // The probe's own rows: P's are C1's, Q's C2's. + var probe = filter.TrimStart('*'); + var rows = JsonNode.Parse(File.ReadAllText(Path.Combine(worktree, "probes", "rows", probe + ".json")))!["rows"]!.AsArray(); + var (lo, hi) = range is null ? (0, int.MaxValue) : (int.Parse(range.Split('-')[0]), int.Parse(range.Split('-')[1])); + var lines = new List<string>(); var failed = 0; + for (var i = 0; i < rows.Count; i++) + { + if (i < lo || i > hi || !global::Prober.Replay.Selected(Files.StringMap(rows[i]!["row"]), only ?? "")) continue; + var violates = probe == "P" && i == Violates; + if (violates) failed++; + lines.Add(new JsonObject + { + ["idx"] = i, ["category"] = violates ? "violation" : "pass", + ["reasons"] = violates ? new JsonArray("violation:row 1 breaks the claim") : new JsonArray(), ["note"] = "compared", + }.ToJsonString()); + } + var outDir = environment["PROBE_OUT"]!; + Directory.CreateDirectory(outDir); + File.WriteAllText(Path.Combine(outDir, probe + ".jsonl"), string.Join('\n', lines) + "\n"); + return failed > 0 ? new Ran(7, $"Tests: {failed} failed of {lines.Count}\n", "") : new Ran(0, $"Tests: {lines.Count} passed\n", ""); + } + + public (JsonObject? Axes, string? Error) DeriveAxes(string checkout, string workDirectory) => (null, "none"); + + public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) => new Dictionary<string, string?>(); + } +} diff --git a/test/CodoMetis.TypeKit.Samples/CodoMetis.TypeKit.Samples.csproj b/test/CodoMetis.TypeKit.Samples/CodoMetis.TypeKit.Samples.csproj index 12da029c..8116a63d 100644 --- a/test/CodoMetis.TypeKit.Samples/CodoMetis.TypeKit.Samples.csproj +++ b/test/CodoMetis.TypeKit.Samples/CodoMetis.TypeKit.Samples.csproj @@ -35,4 +35,27 @@ <ProjectReference Include="..\..\src\CodoMetis.TypeKit.Analyzers\CodoMetis.TypeKit.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> + <!-- + For the analyzer probes of tools/probe/corpus (CMTK0010, CMTK0014): they run the CMTK analyzers + in-process over a CSharpCompilation, as the analyzer tests do, so the analyzer assembly is also + referenced as a library, with the compiler it runs on. The type-argument variant catalogues are + the analyzer tests' own, compiled in rather than copied, so a site the catalogue gains reaches + the probes too. + --> + <ItemGroup> + <ProjectReference Include="..\..\src\CodoMetis.TypeKit.Analyzers\CodoMetis.TypeKit.Analyzers.csproj" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" /> + <Compile Include="..\CodoMetis.TypeKit.Analyzers.Tests\Variants\TypeArgumentSites.cs" Link="Variants\TypeArgumentSites.cs" /> + <Compile Include="..\CodoMetis.TypeKit.Analyzers.Tests\Variants\TypeArgumentKinds.cs" Link="Variants\TypeArgumentKinds.cs" /> + </ItemGroup> + + <!-- + The probes' side (1), the framework without TypeKit, is read from a fact's fixture where one covers a row (the harness's Sides, + design/adr/0107): the facts' reader is compiled in as it is, BCL only, rather than the Facts project referenced, which is a test + project of its own and references no TypeKit project. The fixtures are read from the repository, beside the facts' tests. + --> + <ItemGroup> + <Compile Include="..\CodoMetis.TypeKit.Facts\FactFixture.cs" Link="Facts\FactFixture.cs" /> + </ItemGroup> + </Project> diff --git a/test/CodoMetis.TypeKit.Tests/PatchTests.cs b/test/CodoMetis.TypeKit.Tests/PatchTests.cs index a9dd0f41..0c085c58 100644 --- a/test/CodoMetis.TypeKit.Tests/PatchTests.cs +++ b/test/CodoMetis.TypeKit.Tests/PatchTests.cs @@ -247,6 +247,31 @@ public void A_class_the_source_generated_context_never_saw_is_refused_by_the_ser refusal.Message.ShouldContain(nameof(WithoutAddressJson)); } + /// <summary> + /// A primitive the source-generated context never saw is read and written with the serializer's + /// own converter, for every type that has one: byte for byte what the reflection resolver writes, + /// and read back to the same JSON. + /// </summary> + [Fact] + public void Every_built_in_type_the_context_never_saw_is_the_serializers_own() + { + var generated = new JsonSerializerOptions(JsonSerializerDefaults.Web) { TypeInfoResolver = BuiltInsJson.Default }; + var value = new EveryBuiltIn( + Patch.Value("Ada"), Patch.Value(1), Patch.Value(2L), Patch.Value(true), Patch.Value(3.5m), Patch.Value(4.5d), + Patch.Value(5.5f), Patch.Value(new Guid("8d1c4c8e-2f0a-4b36-9a43-6f6f0b6c1a2e")), + Patch.Value(new DateTime(2026, 10, 16, 7, 19, 3, DateTimeKind.Utc)), + Patch.Value(new DateTimeOffset(2026, 10, 16, 7, 19, 3, TimeSpan.FromHours(2))), + Patch.Value(new DateOnly(2026, 10, 16)), Patch.Value(new TimeOnly(7, 19, 3)), Patch.Value(TimeSpan.FromMinutes(90)), + Patch.Value((short)-6), Patch.Value((ushort)7), Patch.Value(8u), Patch.Value(9UL), Patch.Value((byte)10), + Patch.Value((sbyte)-11), Patch.Value('c'), Patch.Value((Half)12.5), Patch.Value((Int128)13), Patch.Value((UInt128)14), + Patch.Value(new Uri("https://example.org/a")), Patch.Value(new Version(1, 2, 3)), Patch.Value(new byte[] { 1, 2, 3 })); + + var json = JsonSerializer.Serialize(value, generated); + + json.ShouldBe(JsonSerializer.Serialize(value, Reflection)); + JsonSerializer.Serialize(JsonSerializer.Deserialize<EveryBuiltIn>(json, generated), generated).ShouldBe(json); + } + public sealed record Address(string City); /// <summary>A value object's shape: a struct with a converter of its own that refuses, naming the type and never the input.</summary> @@ -276,6 +301,15 @@ public sealed record Outgoing( [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] Patch<int> Quantity); public sealed record BareOutgoing(Patch<string> Name); + + /// <summary>A patch of every type <c>BuiltInConverters</c> knows, none of them listed in its context.</summary> + public sealed record EveryBuiltIn( + Patch<string> String, Patch<int> Int32, Patch<long> Int64, Patch<bool> Boolean, Patch<decimal> Decimal, + Patch<double> Double, Patch<float> Single, Patch<Guid> Guid, Patch<DateTime> DateTime, + Patch<DateTimeOffset> DateTimeOffset, Patch<DateOnly> DateOnly, Patch<TimeOnly> TimeOnly, Patch<TimeSpan> TimeSpan, + Patch<short> Int16, Patch<ushort> UInt16, Patch<uint> UInt32, Patch<ulong> UInt64, Patch<byte> Byte, + Patch<sbyte> SByte, Patch<char> Char, Patch<Half> Half, Patch<Int128> Int128, Patch<UInt128> UInt128, + Patch<Uri> Uri, Patch<Version> Version, Patch<byte[]> Bytes); } // A class a patch wraps is listed, since the patch's converter hides it from the generator; a primitive @@ -290,3 +324,6 @@ internal partial class PatchJson : JsonSerializerContext; [JsonSerializable(typeof(PatchTests.UpdateWithoutAddress))] internal partial class WithoutAddressJson : JsonSerializerContext; + +[JsonSerializable(typeof(PatchTests.EveryBuiltIn))] +internal partial class BuiltInsJson : JsonSerializerContext; diff --git a/tools/AgentTools.slnf b/tools/AgentTools.slnf new file mode 100644 index 00000000..be0719e0 --- /dev/null +++ b/tools/AgentTools.slnf @@ -0,0 +1,11 @@ +{ + "solution": { + "path": "../CodoMetis.TypeKit.slnx", + "projects": [ + "tools/BuildVerdict/BuildVerdict.csproj", + "tools/ConsumerSmokeTest/ConsumerSmokeTest.csproj", + "tools/DocsQuery/DocsQuery.csproj", + "tools/Prober/Prober.csproj" + ] + } +} diff --git a/tools/BashGuard/Program.cs b/tools/BashGuard/Program.cs index afbd874b..78f5f3ad 100644 --- a/tools/BashGuard/Program.cs +++ b/tools/BashGuard/Program.cs @@ -7,9 +7,11 @@ // verdict, and a failing solution test run 8,404 bytes against 609. // // Refused, as a command anywhere in the line: -// - `dotnet build`, `dotnet test`, `dotnet msbuild`, by any path to dotnet, except with --help and +// - `dotnet build`, `dotnet test`, `dotnet msbuild`, by any path to dotnet, except with --help, // except `dotnet build` of a project under tools/: a tool is built on its own, never through the -// verdict tool, which would overwrite itself while it runs; +// verdict tool, which would overwrite itself while it runs, and except `dotnet msbuild` that only +// reads a project: -getProperty or -getItem with no target, -getTargetResult or restore. MSBuild then +// stops after evaluation and writes nothing (measured 2026-10-17), and the verdict has no such form; // - the verdict tool piped into head, tail, less or more: the verdict is already the short form, and // the pipe hides the tool's exit code. // @@ -343,6 +345,24 @@ static bool IsToolBuild(List<string> words) => words.Count > 2 && Program(words[0]) == "dotnet" && words[1] == "build" && words.Skip(2).FirstOrDefault(w => !w.StartsWith('-')) is { } target && IsToolPath(target); + // An MSBuild switch's name: -name, --name or /name, up to its : or =, lower-cased; null for a word that is none. + static string? Switch(string word) + { + var body = word.StartsWith("--", StringComparison.Ordinal) ? word[2..] : word.StartsWith('-') || word.StartsWith('/') ? word[1..] : null; + if (string.IsNullOrEmpty(body)) return null; + var end = body.IndexOfAny([':', '=']); + return (end < 0 ? body : body[..end]).ToLowerInvariant(); + } + + static readonly HashSet<string> Queries = ["getproperty", "getitem"]; + static readonly HashSet<string> Runs = ["t", "target", "gettargetresult", "r", "restore"]; + + // `dotnet msbuild` that reads a project without building it: a property or item query and no target. + static bool IsQuery(List<string> words) => + words.Count > 2 && words[1] == "msbuild" && + words.Skip(2).Select(Switch).Any(s => s is not null && Queries.Contains(s)) && + !words.Skip(2).Select(Switch).Any(s => s is not null && Runs.Contains(s)); + // Whether any command in the text builds a tool project: what a stale guard still lets through. public static bool BuildsATool(string text) => new Scanner(text).Scan().Segments.Any(s => IsToolBuild(CommandWords(s.Words))); @@ -412,7 +432,7 @@ public static bool BuildsATool(string text) => foreach (var f in Findings(words[2], depth + 1)) yield return f; continue; } - if (program == "dotnet" && words.Count > 1 && RefusedVerbs.Contains(words[1]) && !IsToolBuild(words)) + if (program == "dotnet" && words.Count > 1 && RefusedVerbs.Contains(words[1]) && !IsToolBuild(words) && !IsQuery(words)) { if (!words.Skip(2).Any(a => a is "--help" or "-h" or "-?" or "/?")) yield return ("toolchain", (words[1], words.Skip(2).ToList())); diff --git a/tools/BuildVerdict/Program.cs b/tools/BuildVerdict/Program.cs index 06cad93a..5429fbee 100644 --- a/tools/BuildVerdict/Program.cs +++ b/tools/BuildVerdict/Program.cs @@ -5,10 +5,12 @@ // // Usage: // dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll build [<project-dir|csproj|slnx>] [-- <dotnet build flags>] -// dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--project <dir|csproj>] [--filter-class <C>] [--filter-method <M>] [-- <dotnet test flags>] +// dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--no-build] [--project <dir|csproj>] [--filter-class <C>] [--filter-method <M>] [-- <dotnet test flags>] // // Every --filter-* option of the test hosts is passed on with its value, and may repeat (the filters -// of one kind OR together). Without --project the whole solution builds and runs. +// of one kind OR together). Without --project the whole solution builds and runs. With --no-build the +// build is the caller's: the probe replay builds the probes once and runs many of them at a time, and +// two builds of one project at once would race in its obj directory. // // The verdict's first line is one of BUILD OK, BUILD FAILED, BUILD DID NOT REPORT, TESTS OK, TESTS // FAILED, TESTS REJECTED, TESTS NONE, TESTS ABORTED, TESTS DID NOT REPORT, or TOOL STALE. Exit 0 is a @@ -27,6 +29,10 @@ // - A filter on the whole solution exits 8 even when it matched tests and they passed, because // each assembly it matched nothing in reports 8. With a total above zero and no failure that is a // pass, said with the count of such assemblies; with a failure among them the run exits 1. +// - Under GitHub Actions the test platform colours its output although it is redirected, and an +// escape sequence opens the summary's lines ("ESC[m total: 177"), so a passing run read as DID +// NOT REPORT (2026-10-15, the first CI replay of the probes). The log keeps the colours; the +// parsers read the lines without them. // - An empty parse is never a pass: an exit 0 without the summary block the parsers read is a // DID NOT REPORT verdict, exit 1. // - MSBuild prints a diagnostic once inline and once in its summary, and a project's errors again @@ -34,7 +40,11 @@ // - An agent's shell may find a dotnet first on PATH that cannot satisfy global.json (exit 155). // Then the one the SDK installer puts in /usr/local/share/dotnet or %ProgramFiles%\dotnet is // used, if it can, and the verdict says so. -// - The log is written beside and renamed into place, so a reader never sees half of it. +// - Each process writes and reads a log of its own and replaces the shared one when it ends, so a +// reader never sees half of it, and several at once (the probe replay) never read each other's: +// measured 2026-10-16, six at once each reported the same duration, the last one's. +// BUILD_VERDICT_LOG_DIR=<dir> keeps each process's log there as build-verdict-<pid>.log instead, +// for a caller that runs many and wants each. // // Parse-only mode, for BuildVerdict.Tests: BUILD_VERDICT_LOG=<file> BUILD_VERDICT_EXIT=<n> reads // that fixture as the output of the one dotnet command the mode would run (for `test`, the test @@ -53,7 +63,7 @@ static class Verdict const string Usage = """ Usage: dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll build [<project-dir|csproj|slnx>] [-- <dotnet build flags>] - dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--project <dir|csproj>] [--filter-class <C>] [--filter-method <M>] [-- <dotnet test flags>] + dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--no-build] [--project <dir|csproj>] [--filter-class <C>] [--filter-method <M>] [-- <dotnet test flags>] """; const string TestMarker = "==== build-verdict: dotnet test"; @@ -61,6 +71,7 @@ dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--project static string solution = ""; static string remedies = ""; static string log = ""; + static string? sharedLog; static bool parseOnly; static string dotnet = "dotnet"; static string note = ""; @@ -89,9 +100,24 @@ public static int Start(string[] args) } else { - var (code, output) = Capture("git", ["-C", root, "rev-parse", "--absolute-git-dir"]); - var gitDir = code == 0 ? output.Trim() : Path.GetTempPath(); - log = Path.Combine(gitDir, "build-verdict-last.log"); + // Each process writes a log of its own and reads its own: the probe replay runs several at once, + // and one that read the shared log after replacing it could read another's. The shared one, + // $GIT_DIR/build-verdict-last.log, is replaced with this process's when it ends. Under + // BUILD_VERDICT_LOG_DIR each process's log is kept there under its process id instead, for a + // caller that runs many and wants each. + var keep = Environment.GetEnvironmentVariable("BUILD_VERDICT_LOG_DIR"); + if (!string.IsNullOrEmpty(keep)) + { + Directory.CreateDirectory(keep); + log = Path.Combine(keep, $"build-verdict-{Environment.ProcessId}.log"); + } + else + { + var (code, output) = Capture("git", ["-C", root, "rev-parse", "--absolute-git-dir"]); + var gitDir = code == 0 ? output.Trim() : Path.GetTempPath(); + sharedLog = Path.Combine(gitDir, "build-verdict-last.log"); + log = TemporaryBeside(sharedLog); + } } var overrideDotnet = Environment.GetEnvironmentVariable("BUILD_VERDICT_DOTNET"); if (!string.IsNullOrEmpty(overrideDotnet)) dotnet = overrideDotnet; @@ -105,6 +131,17 @@ public static int Start(string[] args) Console.Error.WriteLine(Usage); return 2; } + finally + { + Publish(); + } + } + + // This process's log becomes the shared one, once, when the process is done with it. + static void Publish() + { + if (sharedLog is { } shared && File.Exists(log)) SharedFile.Replace(log, shared); + sharedLog = null; } static int Dispatch(string[] args) @@ -132,6 +169,7 @@ static int Dispatch(string[] args) case "test": { string? project = null; + var noBuild = false; var filters = new List<string>(); var extra = new List<string>(); for (var i = 0; i < rest.Count; i++) @@ -142,6 +180,10 @@ static int Dispatch(string[] args) if (i + 1 >= rest.Count) throw new UsageException(); project = Absolute(rest[++i]); } + else if (a == "--no-build") + { + noBuild = true; + } else if (a.StartsWith("--filter", StringComparison.Ordinal)) { if (i + 1 >= rest.Count) throw new UsageException(); @@ -163,8 +205,9 @@ static int Dispatch(string[] args) string[] selector = project is null ? ["--solution", solution] : ["--project", project]; ResolveSdk(); - // Parse-only mode carries one fixture and one exit code, and they belong to the test step. - if (!parseOnly) + // Parse-only mode carries one fixture and one exit code, and they belong to the test step. With + // --no-build the caller built; the log then holds the SDK line and the test step alone. + if (!parseOnly && !noBuild) { var code = Run(["build", project ?? solution], append: false); if (code != 0 || !BuildSection().Any(l => l.StartsWith("Build succeeded", StringComparison.Ordinal))) @@ -210,11 +253,14 @@ static List<string> LogLines() { if (!File.Exists(log)) return []; var text = SharedFile.ReadAllText(log); - var lines = text.Split('\n').Select(l => l.TrimEnd('\r')).ToList(); + var lines = Ansi.Replace(text, "").Split('\n').Select(l => l.TrimEnd('\r')).ToList(); if (lines.Count > 0 && lines[^1].Length == 0) lines.RemoveAt(lines.Count - 1); return lines; } + // A terminal control sequence (CSI): ESC [, parameters, intermediates, a final byte. + static readonly Regex Ansi = new(@"\e\[[0-?]*[ -/]*[@-~]"); + // The test step's part of the log: after the marker Run writes, or the whole fixture. static List<string> TestSection() { @@ -231,7 +277,7 @@ static List<string> BuildSection() return i < 0 ? lines : lines.Take(i).ToList(); } - static void LogLine() => Console.Write($"\nlog: {log}\n"); + static void LogLine() => Console.Write($"\nlog: {sharedLog ?? log}\n"); // A tail of the log for the reader: repository paths relative, indented. static void Excerpt(List<string> lines, int n) @@ -293,11 +339,9 @@ static void ResolveSdk() { if (parseOnly) return; var (code, output) = Capture(dotnet, ["--version"]); - WriteAtomically(log, output); + File.WriteAllText(log, output); if (code == 0) return; - var fallbackDir = OperatingSystem.IsWindows() - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet") - : "/usr/local/share/dotnet"; + var fallbackDir = Dotnet.InstallerDirectory; var fallback = Path.Combine(fallbackDir, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BUILD_VERDICT_DOTNET")) && File.Exists(fallback) && Capture(fallback, ["--version"]).Code == 0) @@ -309,6 +353,7 @@ static void ResolveSdk() return; } ReportBuild(1); + Publish(); Environment.Exit(1); } @@ -330,22 +375,11 @@ static int Run(string[] args, bool append) { if (parseOnly) return int.TryParse(Environment.GetEnvironmentVariable("BUILD_VERDICT_EXIT"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var e) ? e : 0; - var temporary = TemporaryBeside(log); - if (append && File.Exists(log)) File.Copy(log, temporary, overwrite: true); - var code = RunInto(temporary, args, append); - SharedFile.Replace(temporary, log); - return code; + return RunInto(log, args, append); } static string TemporaryBeside(string path) => $"{path}.{Environment.ProcessId}.tmp"; - static void WriteAtomically(string path, string text) - { - var temporary = TemporaryBeside(path); - File.WriteAllText(temporary, text); - SharedFile.Replace(temporary, path); - } - static int RunInto(string file, string[] args, bool append) { using var writer = new StreamWriter(file, append, new UTF8Encoding(false)) { NewLine = "\n" }; @@ -564,7 +598,9 @@ static string Bare(string s) static string Short(string full) { - var i = full.IndexOf('('); + // A theory's arguments, or a data row's label ("Row [#6 shape.wrapped=int]"), end the name: their dots are not namespaces. + var paren = full.IndexOf('('); var label = full.IndexOf(" [", StringComparison.Ordinal); + var i = paren < 0 ? label : label < 0 ? paren : Math.Min(paren, label); var head = i >= 0 ? full[..i] : full; var tail = i >= 0 ? full[i..] : ""; var p = head.Split('.'); diff --git a/tools/BuildVerdict/build-verdict-remedies.tsv b/tools/BuildVerdict/build-verdict-remedies.tsv index beef822a..600a2da5 100644 --- a/tools/BuildVerdict/build-verdict-remedies.tsv +++ b/tools/BuildVerdict/build-verdict-remedies.tsv @@ -38,3 +38,5 @@ exit:sdk The dotnet on PATH cannot satisfy global.json's SDK pin An environment exit:test:5 MTP refused the command line and ran nothing An option the test hosts do not know. An MSBuild switch (-nodeReuse:false, -p:..., -v:...) after -- is forwarded to every test host, which exits 5: put it on the build instead. The filters are --filter-class, --filter-method, --filter-namespace, --filter-trait, their --filter-not-... forms, --filter-query, or VSTest's --filter; dotnet test --project <P> --no-build --help lists them. exit:test:8 The filter matched no test --filter-method and --filter-class match the fully-qualified name: use Namespace.Class.Method or a wildcard (*ProbeTests). A bare method name is a valid filter that matches nothing. exit:test:filter-mix xunit.v3 refused a mix of filter kinds and the host crashed Use one kind per run: the simple filters (--filter-class, --filter-method, ...), --filter-query, or VSTest's --filter. Mixing them throws before any test runs (measured: exit 134). +CA1416 A call to an API a platform lacks, reachable on that platform Guard the call with the platform check the analyzer reads (if (!OperatingSystem.IsWindows()) ...) or mark the member [SupportedOSPlatform]; a test skip such as Assert.SkipWhen is not read as one. +ASP0020 A minimal-API parameter of a value object declared in the same project looks unparsable ASP.NET's analyzer reads the value object before the generators add IParsable; the running application binds it through the generated TryParse. Suppress it at the endpoint with #pragma warning disable ASP0020, as the AspNetCore README's "A route parameter declared in the same project" shows. diff --git a/tools/ConsumerSmokeTest/Program.cs b/tools/ConsumerSmokeTest/Program.cs index bb5119b7..df978a2b 100644 --- a/tools/ConsumerSmokeTest/Program.cs +++ b/tools/ConsumerSmokeTest/Program.cs @@ -60,6 +60,7 @@ internal sealed partial class Smoke(string repoRoot, string? givenFeed) private readonly DirectoryInfo _created = Directory.CreateTempSubdirectory("consumer-smoke-test-"); private string Work => _created.FullName; private string _cwd = ""; + private string _dotnet = "dotnet"; private string _version = ""; private bool _failed; @@ -100,6 +101,18 @@ private void Main() Environment.SetEnvironmentVariable("NUGET_PACKAGES", Path.Combine(Work, "packages")); _cwd = repoRoot; + // Every dotnet below, the consumers' too, is the one that satisfies global.json: the installer's when + // the one first on PATH cannot (Homebrew's on macOS), as the verdict tool does (design/adr/0105). Started + // by its full path: .NET looks for a bare `dotnet` beside the running host before it reads PATH. + var (dotnet, dotnetDirectory) = Dotnet.For(repoRoot); + _dotnet = dotnet; + if (dotnetDirectory is not null) + { + Environment.SetEnvironmentVariable("PATH", dotnetDirectory + Path.PathSeparator + Environment.GetEnvironmentVariable("PATH")); + Environment.SetEnvironmentVariable("DOTNET_ROOT", dotnetDirectory); + Console.WriteLine($"note: the dotnet on PATH cannot satisfy global.json; using {dotnet}"); + } + _version = Run("dotnet", "msbuild", Path.Combine(repoRoot, "src", "CodoMetis.TypeKit", "CodoMetis.TypeKit.csproj"), "-getProperty:Version").Trim(); // Every shipping project is a package, discovered from src/ as the release workflow does, so a new @@ -529,7 +542,7 @@ private string RunFailing(string file, params string[] arguments) // stdout and stderr together, in order of arrival, trailing newlines trimmed. private (int Code, string Output) Exec(string file, string[] arguments) { - var psi = new ProcessStartInfo(file) { RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = _cwd }; + var psi = new ProcessStartInfo(file == "dotnet" ? _dotnet : file) { RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = _cwd }; foreach (var a in arguments) psi.ArgumentList.Add(a); var text = new StringBuilder(); var gate = new object(); diff --git a/tools/DocsQuery/Citations.cs b/tools/DocsQuery/Citations.cs new file mode 100644 index 00000000..ef97fba4 --- /dev/null +++ b/tools/DocsQuery/Citations.cs @@ -0,0 +1,260 @@ +using System.Globalization; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace DocsQuery; + +/// <summary> +/// Where the probe corpus leans on a verified fact to prune a row or excuse one (design/adr/0107): a <c>refused</c> +/// entry of the axes file whose reason names <c>KB-NNN</c>, a <c>not-promised</c> entry of claims.json whose +/// <c>source</c> is a fact's file or which says <c>fact:KB-NNN</c>, an obligation's declaration of where it does not +/// apply whose <c>cite</c> is <c>fact:KB-NNN</c>, and a <c>Cite.Fact("KB-NNN")</c> or <c>Baseline.Fact("KB-NNN")</c> +/// literal in the harness and probe sources, read as text with comments blanked out. Each is read from the checkout +/// at every <c>check</c>, never from the index. +/// </summary> +internal static partial class FactCitations +{ + public const string AxesFile = ".claude/probe-axes.md", ClaimsFile = "tools/probe/corpus/claims.json"; + + public static readonly string[] SourceDirectories = ["tools/probe/corpus/harness", "tools/probe/corpus/probes"]; + + /// <summary>The four places, in the order <c>check</c> counts them.</summary> + public static readonly string[] Kinds = [Refused, NotPromised, Obligation, Harness]; + + public const string Refused = "refused", NotPromised = "not-promised", Obligation = "obligation", Harness = "harness"; + + /// <summary> + /// One citation: the kind of place, where it is written (<c>path:line</c>), the fact's id, the place in words, and + /// whether it reads the fact's fixture as a baseline (<c>Baseline.Fact</c>), which takes a fixture as well as a test. + /// </summary> + internal sealed record Citation(string Kind, string At, string Fact, string Place, bool Baseline = false); + + /// <summary>What a scan read: every citation, and how many places of each kind it looked at, cited or not.</summary> + internal sealed record Scan(IReadOnlyList<Citation> Citations, IReadOnlyDictionary<string, int> Read) + { + public int Of(string kind) => Citations.Count(citation => citation.Kind == kind); + } + + public static Scan Find(string root) + { + var citations = new List<Citation>(); + var read = Kinds.ToDictionary(kind => kind, _ => 0, StringComparer.Ordinal); + + ReadAxes(root, citations, read); + ReadClaims(root, citations, read); + ReadSources(root, citations, read); + + return new Scan(citations, read); + } + + /// <summary>The axes file's <c>refused</c> entries: axis to value to the reason the framework refuses it.</summary> + private static void ReadAxes(string root, List<Citation> citations, Dictionary<string, int> read) + { + var path = Path.Combine(root, AxesFile); + if (!File.Exists(path)) return; + + const string marker = "```json probe-axes"; + var text = File.ReadAllText(path).ReplaceLineEndings("\n"); + var start = text.IndexOf(marker, StringComparison.Ordinal); + if (start < 0) return; + start += marker.Length; + var end = text.IndexOf("```", start, StringComparison.Ordinal); + if (end < 0) return; + + using var block = JsonDocument.Parse(text[start..end]); + if (!block.RootElement.TryGetProperty("refused", out var refused) || refused.ValueKind != JsonValueKind.Object) return; + + var cursor = new Cursor(AxesFile, text, text.IndexOf("\"refused\"", start, StringComparison.Ordinal)); + foreach (var axis in refused.EnumerateObject().Where(axis => axis.Value.ValueKind == JsonValueKind.Object)) + { + foreach (var value in axis.Value.EnumerateObject()) + { + read[Refused]++; + var place = $"the refused entry {axis.Name}={value.Name}"; + cursor.Seek($"\"{value.Name}\""); + foreach (var fact in FactIds(value.Value.ValueKind == JsonValueKind.String ? value.Value.GetString()! : value.Value.GetRawText())) + citations.Add(new Citation(Refused, cursor.At(fact), fact, place)); + } + } + } + + /// <summary>Each claim's <c>not-promised</c> entries and its obligations' <c>inapplicable</c> declarations.</summary> + private static void ReadClaims(string root, List<Citation> citations, Dictionary<string, int> read) + { + var path = Path.Combine(root, ClaimsFile); + if (!File.Exists(path)) return; + + var text = File.ReadAllText(path).ReplaceLineEndings("\n"); + using var claims = JsonDocument.Parse(text); + if (claims.RootElement.ValueKind != JsonValueKind.Array) return; + + // Walked in the file's order, so each citation's line is the next occurrence of its id from the last one. + var cursor = new Cursor(ClaimsFile, text, 0); + foreach (var claim in claims.RootElement.EnumerateArray()) + { + var id = Str(claim, "id"); + cursor.Seek($"\"{id}\""); + + foreach (var property in claim.EnumerateObject()) + { + if (property.NameEquals("obligations") && property.Value.ValueKind == JsonValueKind.Array) + { + foreach (var obligation in property.Value.EnumerateArray()) + { + cursor.Seek($"\"{Str(obligation, "id")}\""); + var number = 0; + foreach (var declaration in Array(obligation, "inapplicable")) + { + read[Obligation]++; + number++; + if (FactOf(Str(declaration, "cite")) is not { } fact) continue; + citations.Add(new Citation(Obligation, cursor.At("fact:" + fact), fact, + $"claim {id}, obligation {Str(obligation, "id")}, inapplicable declaration {number.ToString(CultureInfo.InvariantCulture)}")); + } + } + } + else if (property.NameEquals("not-promised") && property.Value.ValueKind == JsonValueKind.Array) + { + foreach (var entry in property.Value.EnumerateArray()) + { + read[NotPromised]++; + cursor.Seek($"\"{Str(entry, "id")}\""); + var place = $"claim {id}, not-promised {Str(entry, "id")}"; + var facts = new List<string>(); + if (KnowledgeSource().Match(Str(entry, "source")) is { Success: true } source) facts.Add(source.Groups["id"].Value); + foreach (var field in entry.EnumerateObject().Where(field => field.Value.ValueKind == JsonValueKind.String)) + facts.AddRange(CiteFact().Matches(field.Value.GetString()!).Select(match => match.Groups["id"].Value)); + + foreach (var fact in facts.Distinct(StringComparer.Ordinal)) + citations.Add(new Citation(NotPromised, cursor.At(fact), fact, place)); + } + } + } + } + } + + /// <summary>The harness's and the probes' <c>Cite.Fact</c> and <c>Baseline.Fact</c> literals, comments blanked out.</summary> + private static void ReadSources(string root, List<Citation> citations, Dictionary<string, int> read) + { + foreach (var directory in SourceDirectories.Select(directory => Path.Combine(root, directory)).Where(Directory.Exists)) + { + foreach (var file in Directory.EnumerateFiles(directory, "*.cs").Order(StringComparer.Ordinal)) + { + read[Harness]++; + var path = Corpus.Relative(root, file); + var source = File.ReadAllText(file).ReplaceLineEndings("\n"); + foreach (Match match in FactLiteral().Matches(WithoutComments(source))) + { + var line = 1 + source.AsSpan(0, match.Index).Count('\n'); + citations.Add(new Citation(Harness, $"{path}:{line.ToString(CultureInfo.InvariantCulture)}", match.Groups["id"].Value, + $"{match.Groups["factory"].Value}.Fact(\"{match.Groups["id"].Value}\")", match.Groups["factory"].Value == "Baseline")); + } + } + } + } + + /// <summary> + /// The source with each comment's content replaced by spaces, newlines kept, and string literals as they are, so a + /// citation written in a comment does not count and a line number still holds. + /// </summary> + internal static string WithoutComments(string source) + { + var result = new StringBuilder(source); + for (var i = 0; i < source.Length; i++) + { + if (source[i] == '/' && i + 1 < source.Length && source[i + 1] == '/') + { + for (; i < source.Length && source[i] != '\n'; i++) result[i] = ' '; + } + else if (source[i] == '/' && i + 1 < source.Length && source[i + 1] == '*') + { + var close = source.IndexOf("*/", i + 2, StringComparison.Ordinal); + var stop = close < 0 ? source.Length : close + 2; + for (; i < stop; i++) if (source[i] != '\n') result[i] = ' '; + i--; + } + else if (source[i] == '"') + { + i = AfterString(source, i); + } + else if (source[i] == '\'') + { + // A character literal: '"' must not open a string. + var j = i + 1; + if (j < source.Length && source[j] == '\\') j++; + if (j + 1 < source.Length && source[j + 1] == '\'') i = j + 1; + } + } + + return result.ToString(); + } + + /// <summary>The index of the closing quote of the string literal opened at <paramref name="i"/>: raw, verbatim or regular.</summary> + private static int AfterString(string source, int i) + { + var quotes = 0; + while (i + quotes < source.Length && source[i + quotes] == '"') quotes++; + + if (quotes >= 3) + { + var close = source.IndexOf(new string('"', quotes), i + quotes, StringComparison.Ordinal); + return close < 0 ? source.Length : close + quotes - 1; + } + + if (quotes == 2) return i + 1; // "" + + var verbatim = i > 0 && source[i - 1] == '@' || i > 1 && source[i - 1] == '$' && source[i - 2] == '@'; + for (var j = i + 1; j < source.Length; j++) + { + if (verbatim && source[j] == '"' && j + 1 < source.Length && source[j + 1] == '"') { j++; continue; } + if (!verbatim && source[j] == '\\') { j++; continue; } + if (source[j] == '"' || !verbatim && source[j] == '\n') return j; + } + + return source.Length; + } + + /// <summary>The ids a refused entry's reason names, as <c>fact:KB-NNN</c> or plain <c>KB-NNN</c>.</summary> + private static IEnumerable<string> FactIds(string text) => AnyFact().Matches(text).Select(match => match.Value).Distinct(StringComparer.Ordinal); + + private static string? FactOf(string cite) => cite.StartsWith("fact:", StringComparison.Ordinal) ? cite["fact:".Length..] : null; + + private static string Str(JsonElement element, string name) => + element.ValueKind == JsonValueKind.Object && element.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String ? value.GetString()! : ""; + + private static IEnumerable<JsonElement> Array(JsonElement element, string name) => + element.ValueKind == JsonValueKind.Object && element.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.Array ? value.EnumerateArray() : []; + + /// <summary>Line numbers for places read in a file's order: each needle is looked for from the last place found.</summary> + private sealed class Cursor(string path, string text, int offset) + { + private int at = Math.Max(0, offset); + + public void Seek(string needle) + { + var found = text.IndexOf(needle, at, StringComparison.Ordinal); + if (found >= 0) at = found; + } + + public string At(string needle) + { + var found = text.IndexOf(needle, at, StringComparison.Ordinal); + var line = 1 + text.AsSpan(0, found >= 0 ? found : at).Count('\n'); + if (found >= 0) at = found + needle.Length; + return $"{path}:{line.ToString(CultureInfo.InvariantCulture)}"; + } + } + + [GeneratedRegex(@"\bKB-\d{3}\b")] + private static partial Regex AnyFact(); + + [GeneratedRegex(@"\bfact:\s?(?<id>KB-\d{3})\b")] + private static partial Regex CiteFact(); + + [GeneratedRegex(@"^design/knowledge/(?<id>KB-\d{3})-[^/#]*\.md(#.*)?$")] + private static partial Regex KnowledgeSource(); + + [GeneratedRegex(@"\b(?<factory>Cite|Baseline)\s*\.\s*Fact\s*\(\s*""(?<id>KB-\d{3})""\s*\)")] + private static partial Regex FactLiteral(); +} diff --git a/tools/DocsQuery/Facts.cs b/tools/DocsQuery/Facts.cs index 05f2ae3e..9f5d81ec 100644 --- a/tools/DocsQuery/Facts.cs +++ b/tools/DocsQuery/Facts.cs @@ -124,7 +124,10 @@ public static int CompareVersions(string left, string right) /// <summary> /// The rules a verified fact is held to (design/adr/0093): its frontmatter is complete and plain, each pin /// matches what is installed, and its <c>test:</c> names the characterization test that observes it, or says -/// <c>none</c> and why. A test of the facts project that no fact names is a problem too. +/// <c>none</c> and why. A test of the facts project that no fact names is a problem too. A <c>fixture:</c>, the +/// measured answer of the fact's test as data, sits beside that test and exists (design/adr/0107). A fact the probe +/// corpus cites to prune or excuse a row (<see cref="FactCitations"/>) is executable: its test exists, and where the +/// harness reads it as a baseline, its fixture too. /// </summary> internal static partial class FactRules { @@ -134,11 +137,19 @@ internal static partial class FactRules internal sealed record Problem(string At, string Kind, string Target, string Detail); - public static IReadOnlyList<Problem> Check(DocIndex index, string root, out int facts) + public const string FixtureSuffix = ".fixture.json"; + + public static IReadOnlyList<Problem> Check(DocIndex index, string root, out int facts) => Check(index, root, out facts, out _); + + public static IReadOnlyList<Problem> Check(DocIndex index, string root, out int facts, out FactCitations.Scan cited) { var problems = new List<Problem>(); var installed = Installed.Read(root); var named = new HashSet<string>(StringComparer.Ordinal); + var fixtures = new HashSet<string>(StringComparer.Ordinal); + // Why each fact cannot settle a pruning or an excuse, or null where its test exists; and its fixture, where one exists. + var notExecutable = new Dictionary<string, string?>(StringComparer.Ordinal); + var fixtureOf = new Dictionary<string, string?>(StringComparer.Ordinal); facts = 0; for (var d = 0; d < index.Docs.Count; d++) @@ -174,28 +185,73 @@ public static IReadOnlyList<Problem> Check(DocIndex index, string root, out int }, pin.Value, detail)); } + var className = doc.Id!.Replace("-", "", StringComparison.Ordinal); + notExecutable[doc.Id!] = "it has no test: line"; + if (doc.Meta.FirstOrDefault(field => field.Key == "test") is { } test) { - var className = doc.Id!.Replace("-", "", StringComparison.Ordinal); - - if (test.Value.StartsWith("none, ", StringComparison.Ordinal) && test.Value.Length > "none, ".Length) continue; - - if (!test.Value.StartsWith(FactsProject + "/", StringComparison.Ordinal)) + if (test.Value.StartsWith("none, ", StringComparison.Ordinal) && test.Value.Length > "none, ".Length) + { + notExecutable[doc.Id!] = $"its test: is '{test.Value}'"; + } + else if (TestPath(test.Value) is var path && !path.StartsWith(FactsProject + "/", StringComparison.Ordinal)) { problems.Add(new Problem(At(test.Line), "fact-untested", test.Value, $"write the path of its test in {FactsProject}/, or 'none, ' and why no test can observe it")); - continue; + notExecutable[doc.Id!] = $"its test: '{test.Value}' is no test of {FactsProject}/"; } + else + { + named.Add(path); + var file = Path.Combine(root, path); + + if (!File.Exists(file)) + { + problems.Add(new Problem(At(test.Line), "fact-untested", path, "the file does not exist")); + notExecutable[doc.Id!] = $"its test {path} does not exist"; + } + else if (!Regex.IsMatch(File.ReadAllText(file), $@"\bclass\s+{className}\b")) + { + problems.Add(new Problem(At(test.Line), "fact-untested", path, $"declares no class {className}, the test named by the fact's id")); + notExecutable[doc.Id!] = $"its test {path} declares no class {className}"; + } + else + { + notExecutable[doc.Id!] = null; + } + } + } - named.Add(test.Value); - var file = Path.Combine(root, test.Value); - - if (!File.Exists(file)) - problems.Add(new Problem(At(test.Line), "fact-untested", test.Value, "the file does not exist")); - else if (!Regex.IsMatch(File.ReadAllText(file), $@"\bclass\s+{className}\b")) - problems.Add(new Problem(At(test.Line), "fact-untested", test.Value, $"declares no class {className}, the test named by the fact's id")); + fixtureOf[doc.Id!] = null; + if (doc.Meta.FirstOrDefault(field => field.Key == "fixture") is { } fixture) + { + var expected = $"{FactsProject}/{className}{FixtureSuffix}"; + fixtures.Add(fixture.Value); + + if (NotPlain(fixture.Value) is { } why) + problems.Add(new Problem(At(fixture.Line), "fact-frontmatter", fixture.Key, why)); + else if (notExecutable[doc.Id!] is { } untested) + problems.Add(new Problem(At(fixture.Line), "fact-fixture", fixture.Value, $"a fixture is the measured answer of the fact's test, and {untested}")); + else if (fixture.Value != expected) + problems.Add(new Problem(At(fixture.Line), "fact-fixture", fixture.Value, $"write it beside the test, as {expected}")); + else if (!File.Exists(Path.Combine(root, fixture.Value))) + problems.Add(new Problem(At(fixture.Line), "fact-fixture-missing", fixture.Value, "the file does not exist")); + else + fixtureOf[doc.Id!] = fixture.Value; } } + cited = FactCitations.Find(root); + foreach (var citation in cited.Citations) + { + var why = !notExecutable.TryGetValue(citation.Fact, out var reason) ? "there is no such fact under design/knowledge/" + : reason is not null ? reason + : citation.Baseline && fixtureOf[citation.Fact] is null ? "the harness reads its fixture as a baseline, and it names no fixture: that exists" + : null; + if (why is not null) + problems.Add(new Problem(citation.At, "fact-not-executable-cited", citation.Fact, + $"{citation.Place} cites {citation.Fact}, and {why}: a fact without its test explains only, never prunes or excuses a row; write its test in {FactsProject}/, or cite a docs sentence")); + } + var project = Path.Combine(root, FactsProject); if (Directory.Exists(project)) { @@ -204,11 +260,24 @@ public static IReadOnlyList<Problem> Check(DocIndex index, string root, out int if (TestFile().IsMatch(Path.GetFileName(path)) && !named.Contains(path)) problems.Add(new Problem($"{path}:1", "fact-orphan-test", path, "no fact in design/knowledge/ names this test")); } + + foreach (var path in Directory.EnumerateFiles(project, "KB*" + FixtureSuffix, SearchOption.TopDirectoryOnly).Select(path => Corpus.Relative(root, path)).Order(StringComparer.Ordinal)) + { + if (!fixtures.Contains(path)) + problems.Add(new Problem($"{path}:1", "fact-orphan-fixture", path, "no fact in design/knowledge/ names this fixture")); + } } return problems; } + /// <summary> + /// The test's path, without a note in parentheses after it, such as where the test runs: + /// <c>test/CodoMetis.TypeKit.Facts/KB028.cs (runs only with TYPEKIT_PG set, until WS6's CI service)</c>. + /// </summary> + private static string TestPath(string value) => + value.EndsWith(')') && value.IndexOf(" (", StringComparison.Ordinal) is > 0 and var note ? value[..note] : value; + /// <summary>Why a frontmatter value would not read back as the plain YAML string it is meant to be, or null.</summary> private static string? NotPlain(string value) => value.Length > 0 && "`\"'[]{}*&!|>%@#".Contains(value[0], StringComparison.Ordinal) ? $"a value starting with '{value[0]}' is not a plain YAML string" diff --git a/tools/DocsQuery/Queries.cs b/tools/DocsQuery/Queries.cs index cd7ba897..ec174ae0 100644 --- a/tools/DocsQuery/Queries.cs +++ b/tools/DocsQuery/Queries.cs @@ -206,7 +206,8 @@ public Answer Claims(string? page, string? kind) /// <summary> /// Links whose file or anchor does not exist, ids that name no record, diagnostic or fact, and the facts' /// rules (<see cref="FactRules"/>): a pin behind or ahead of what is installed, or naming nothing installed, - /// a fact without its test, a test without its fact. + /// a fact without its test, a test without its fact, a fixture missing, and a fact the probe corpus cites to prune + /// or excuse a row that has no test. /// </summary> public Answer Check() { @@ -238,9 +239,12 @@ public Answer Check() } } - rows.AddRange(FactRules.Check(index, root, out var facts).Select(problem => Row.Of(("at", problem.At), ("kind", problem.Kind), ("target", problem.Target), ("detail", problem.Detail)))); + rows.AddRange(FactRules.Check(index, root, out var facts, out var cited).Select(problem => Row.Of(("at", problem.At), ("kind", problem.Kind), ("target", problem.Target), ("detail", problem.Detail)))); - return new Answer(Answer.Exact, "check", "", $"{Count(rows.Count, "problem")} in {links} links, {ids} ids and {Count(facts, "fact")}", rows, rows.Count == 0 ? 0 : 1); + // Each kind of citation counted, so a scan that found none of a kind it should have is seen in the summary. + var citations = string.Join(", ", FactCitations.Kinds.Select(kind => $"{cited.Of(kind).ToString(CultureInfo.InvariantCulture)} {kind}")); + + return new Answer(Answer.Exact, "check", "", $"{Count(rows.Count, "problem")} in {links} links, {ids} ids, {Count(cited.Citations.Count, "fact citation")} ({citations}) and {Count(facts, "fact")}", rows, rows.Count == 0 ? 0 : 1); Row Problem(Edge edge, string kind, string detail) => Row.Of(("at", At(edge.Doc, edge.Line)), ("kind", kind), ("target", edge.Written), ("detail", detail)); diff --git a/tools/DocsQuery/README.md b/tools/DocsQuery/README.md index 602e5307..6e8e666e 100644 --- a/tools/DocsQuery/README.md +++ b/tools/DocsQuery/README.md @@ -41,18 +41,24 @@ line moves. Its `hash` changes with its content. ## Verified facts A fact (`design/knowledge/KB-NNN-slug.md`, design/adr/0093) has YAML frontmatter, read as fields: `verified:`, -`pins:` (one field per list item), `source:`, `turned-on:` and `test:`. `list kind=fact` shows each with the date it -was verified, and `check` holds every fact to these rules: +`pins:` (one field per list item), `source:`, `turned-on:`, `test:` and, where its measured answer is data, `fixture:` +(design/adr/0107). `list kind=fact` shows each with the date it was verified, and `check` holds every fact to these +rules: | Problem | When | |---|---| | `fact-behind`, `fact-ahead` | A pin `{PackageId} {version}` differs from `Directory.Packages.props`, or `sdk {band}` (`sdk 10.0.4xx`) from the band of the SDK `global.json` pins. `external {name} {version}` is never compared | | `fact-not-installed`, `fact-bad-pin` | A pin names a package neither file installs, or is not one of those forms | | `fact-incomplete`, `fact-frontmatter` | A field is missing, `verified:` is no `YYYY-MM-DD`, or a value is not a plain YAML string (a `: ` or ` #` inside, a quote or backtick first) | -| `fact-untested` | `test:` is neither `none, ` and a reason nor a file in `test/CodoMetis.TypeKit.Facts/` that declares the class named by the id (`KB007`) | +| `fact-untested` | `test:` is neither `none, ` and a reason nor a file in `test/CodoMetis.TypeKit.Facts/` that declares the class named by the id (`KB007`); a note in parentheses after the path, such as where the test runs, is not part of it | | `fact-orphan-test` | A `KBNNN.cs` in the facts project that no fact names | +| `fact-fixture-missing`, `fact-fixture` | A `fixture:` whose file does not exist; one that is not `KBNNN.fixture.json` beside the test, or belongs to a fact without a test | +| `fact-orphan-fixture` | A `KBNNN.fixture.json` in the facts project that no fact names | +| `fact-not-executable-cited` | The probe corpus cites a fact that has no test: a `refused` entry of `.claude/probe-axes.md` whose reason names `KB-NNN`, a `not-promised` entry of `tools/probe/corpus/claims.json` whose `source` is the fact's file or which says `fact:KB-NNN`, an obligation's `inapplicable` declaration citing `fact:KB-NNN`, or a `Cite.Fact("KB-NNN")` or `Baseline.Fact("KB-NNN")` literal in the harness and probe sources (comments blanked out). No test means `none, …`, no file, a file that does not exist or declares no class of the id; a `Baseline.Fact` takes a fixture as well. Reported at the citing place | -The pins are read from the checkout at each `check`, never from the index. +The pins and the corpus's citations are read from the checkout at each `check`, never from the index. The summary +counts the citations of each kind (`4 fact citations (0 refused, 2 not-promised, 0 obligation, 2 harness)`), and +`RepositoryTests` holds floors on them, so a scan that finds nothing cannot pass for a clean one. ## Freshness diff --git a/tools/Prober/Axes.cs b/tools/Prober/Axes.cs index 20d1757d..e37f6a46 100644 --- a/tools/Prober/Axes.cs +++ b/tools/Prober/Axes.cs @@ -17,6 +17,42 @@ public Pair(string x, string y) public override string ToString() => $"{A} {B}"; } +/// <summary>Pairs a claim excludes, and the one sentence that says why no row can hold them or the documents do not promise them.</summary> +internal sealed record Exclusion(string Reason, IReadOnlyList<Pair> Pairs); + +/// <summary>One thing a claim promises, checkable on its own: a sentence quoted from the claim's source, under an id a seed names (decision 104).</summary> +internal sealed record Obligation(string Id, string Sentence, IReadOnlyDictionary<string, List<string>> Axes) +{ + /// <summary>Where the obligation does not apply, each declaration with its citation; the harness's <c>Row.Check</c> reads the same ones.</summary> + public IReadOnlyList<Inapplicability> Inapplicable { get; init; } = []; +} + +/// <summary> +/// Where an obligation does not apply: every axis of the declaration at one of the values it lists, and the citation +/// that says why (<c>not-promised:id</c>, <c>docs:file#anchor</c>, <c>fact:KB-NNN</c>, <c>record:NNNN</c>). A row +/// matches when it holds each axis at a listed value; a pair matches when every row holding it does, so when each +/// axis of the declaration is one of the pair's two at a listed value. +/// </summary> +internal sealed record Inapplicability(IReadOnlyDictionary<string, List<string>> Axes, string Cite) +{ + public bool Matches(Pair pair) => Axes.All(axis => Holds(pair.A, axis) || Holds(pair.B, axis)); + + private static bool Holds(string setting, KeyValuePair<string, List<string>> axis) + { + var at = setting.IndexOf('=', StringComparison.Ordinal); + return at > 0 && string.Equals(setting[..at], axis.Key, StringComparison.Ordinal) && axis.Value.Contains(setting[(at + 1)..]); + } + + public JsonObject ToJson() => new() + { + ["axes"] = new JsonObject(Axes.Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)new JsonArray([.. kv.Value.Select(v => (JsonNode)v)])))), + ["cite"] = Cite, + }; +} + +/// <summary>One thing a claim does not promise: the sentence of the document or record that makes the difference intended, and what it concerns.</summary> +internal sealed record NotPromised(string Id, string Source, string Sentence, IReadOnlyDictionary<string, List<string>> Axes); + /// <summary>One claim of the universe: its axes and values, its watched paths, and the pairs it excludes.</summary> internal sealed class Claim { @@ -27,6 +63,66 @@ internal sealed class Claim public required SortedDictionary<string, List<string>> Axes { get; init; } public required HashSet<Pair> Excluded { get; init; } + /// <summary>The claim's exclusions with their reasons, as claims.json holds them; universe.json keeps only <see cref="Excluded"/>.</summary> + public IReadOnlyList<Exclusion> Exclusions { get; init; } = []; + + /// <summary>Each axis the claim drops, with the document sentence that limits it; universe.json does not keep these.</summary> + public IReadOnlyDictionary<string, string> Narrowed { get; init; } = new SortedDictionary<string, string>(StringComparer.Ordinal); + + /// <summary>What the claim promises, each checkable on its own, as claims.json lists them (empty: one implicit obligation, the claim); universe.json does not keep these.</summary> + public IReadOnlyList<Obligation> Obligations { get; init; } = []; + + /// <summary>What the claim does not promise, each with the sentence that makes it intended; universe.json does not keep these.</summary> + public IReadOnlyList<NotPromised> NotPromised { get; init; } = []; + + /// <summary>The id of a claim's one obligation where claims.json lists none: the claim itself.</summary> + public const string ImplicitObligation = "claim"; + + /// <summary>The obligation ids of a claim as claims.json holds it: the listed ones, or the implicit one.</summary> + public static List<string> ObligationIds(JsonObject claim) + { + var ids = (claim["obligations"] as JsonArray ?? []).OfType<JsonObject>().Select(o => Files.Str(o["id"])).Where(id => id.Length > 0).ToList(); + return ids.Count > 0 ? ids : [ImplicitObligation]; + } + + /// <summary>The ids of the claim's obligations as built: the listed ones, or the implicit one.</summary> + public IEnumerable<string> ObligationIdsBuilt => Obligations.Count > 0 ? Obligations.Select(o => o.Id) : [ImplicitObligation]; + + /// <summary> + /// The pairs <paramref name="obligation"/> wants: the claim's pairs less those a declaration of that obligation + /// matches on either side. An obligation that declares nothing, the implicit one among them, wants them all. + /// </summary> + public HashSet<Pair> PairsWantedBy(string obligation) + { + var declared = Obligations.FirstOrDefault(o => o.Id == obligation)?.Inapplicable ?? []; + var result = Pairs(); + if (declared.Count > 0) result.RemoveWhere(p => declared.Any(d => d.Matches(p))); + return result; + } + + /// <summary>The pairs at least one obligation wants: the union of <see cref="PairsWantedBy"/>, each pair once.</summary> + public HashSet<Pair> PairsWanted() + { + if (!Obligations.Any(o => o.Inapplicable.Count > 0)) return Pairs(); + var result = new HashSet<Pair>(); + foreach (var id in ObligationIdsBuilt) result.UnionWith(PairsWantedBy(id)); + return result; + } + + /// <summary> + /// The pairs no obligation wants, each with the citations of the declarations that match it: declared + /// inapplicable, so no row is generated for them and the gate leaves them out as it leaves out an exclusion. + /// </summary> + public Dictionary<Pair, SortedSet<string>> DeclaredInapplicable() + { + var result = new Dictionary<Pair, SortedSet<string>>(); + var wanted = PairsWanted(); + foreach (var pair in Pairs()) + if (!wanted.Contains(pair)) + result[pair] = new SortedSet<string>(Obligations.SelectMany(o => o.Inapplicable).Where(d => d.Matches(pair)).Select(d => d.Cite), StringComparer.Ordinal); + return result; + } + public HashSet<Pair> Pairs() { var result = new HashSet<Pair>(); @@ -63,6 +159,15 @@ public JsonObject ToJson() ["axes"] = new JsonObject(Axes.Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)new JsonArray([.. kv.Value.Select(v => (JsonNode)v)])))), }; if (Excluded.Count > 0) o["exclude"] = new JsonArray([.. Excluded.Select(p => (JsonNode)new JsonArray(p.A, p.B))]); + // Every obligation's id with its declarations, written only where one declares something, so the covering + // generator and the gate, which read universe.json, know which pairs each wants. + if (Obligations.Any(ob => ob.Inapplicable.Count > 0)) + o["obligations"] = new JsonArray([.. Obligations.Select(ob => + { + var item = new JsonObject { ["id"] = ob.Id }; + if (ob.Inapplicable.Count > 0) item["inapplicable"] = new JsonArray([.. ob.Inapplicable.Select(d => (JsonNode)d.ToJson())]); + return (JsonNode)item; + })]); return o; } @@ -72,6 +177,10 @@ public JsonObject ToJson() Watched = [.. Files.Strings(o["watched"])], Axes = new((o["axes"] as JsonObject ?? []).ToDictionary(kv => kv.Key, kv => Files.Strings(kv.Value).ToList(), StringComparer.Ordinal), StringComparer.Ordinal), Excluded = [.. (o["exclude"] as JsonArray ?? []).OfType<JsonArray>().Where(p => p.Count == 2).Select(p => new Pair(Files.Str(p[0]), Files.Str(p[1])))], + Obligations = [.. (o["obligations"] as JsonArray ?? []).OfType<JsonObject>().Select(ob => new Obligation(Files.Str(ob["id"]), "", new SortedDictionary<string, List<string>>(StringComparer.Ordinal)) + { + Inapplicable = [.. (ob["inapplicable"] as JsonArray ?? []).OfType<JsonObject>().Select(Prober.Axes.Declaration)], + })], }; } @@ -117,6 +226,40 @@ public static bool Matches(string pattern, string name) => private static string Collapse(string s) => Regex.Replace(s, @"\s+", " "); + /// <summary>An axis-to-values object of claims.json (an obligation's or a not-promised entry's `axes`), or none.</summary> + private static SortedDictionary<string, List<string>> AxesOf(JsonNode? node) + { + var result = new SortedDictionary<string, List<string>>(StringComparer.Ordinal); + foreach (var (name, values) in node as JsonObject ?? []) result[name] = [.. Files.Strings(values)]; + return result; + } + + /// <summary>A declaration of where an obligation does not apply, as claims.json and universe.json hold it.</summary> + internal static Inapplicability Declaration(JsonObject d) => new(AxesOf(d["axes"]), Files.Str(d["cite"])); + + private static readonly Regex CiteId = new("^[a-z0-9][a-z0-9-]*$"); + private static readonly Regex CiteSource = new(@"^(?!/)(?!.*(^|/)\.\.?(/|#|$))[A-Za-z0-9_./-]+\.[A-Za-z0-9]+(#[A-Za-z0-9][A-Za-z0-9._-]*)?$"); + private static readonly Regex CiteFact = new(@"^KB-\d{3}$"); + private static readonly Regex CiteRecord = new(@"^\d{4}$"); + + /// <summary> + /// What is wrong with a citation's form, or null: the forms the harness's <c>Cite</c> (harness/Row.cs) accepts, so + /// every declaration the universe reads, the harness can cite (<c>CiteFormTests</c> holds the two alike). + /// </summary> + internal static string? CiteProblem(string cite) + { + var at = cite.IndexOf(':', StringComparison.Ordinal); + var (kind, value) = at < 0 ? ("", cite) : (cite[..at], cite[(at + 1)..]); + return kind switch + { + "not-promised" => CiteId.IsMatch(value) ? null : "is not a not-promised id (not-promised:closed-set-has-no-text)", + "docs" => CiteSource.IsMatch(value) ? null : "is not a docs source (docs:docs/json.md#decimals-in-one-spelling)", + "fact" => CiteFact.IsMatch(value) ? null : "is not a knowledge entry's id (fact:KB-031)", + "record" => CiteRecord.IsMatch(value) ? null : "is not a decision record's number (record:0094)", + _ => "is not a citation (not-promised:id, docs:file#anchor, fact:KB-NNN or record:NNNN)", + }; + } + /// <summary>Builds the universe: each claim takes every axis of its surfaces, minus quoted narrowings, plus its own axes.</summary> public static (Universe Universe, List<string> Problems) Build(Ledger ledger, string baseRef, List<JsonObject>? claimsOverride = null, JsonObject? generatedOverride = null) { @@ -169,18 +312,67 @@ public static (Universe Universe, List<string> Problems) Build(Ledger ledger, st } var sourcePath = Path.Combine(ledger.Top, sourceFile); var sourceText = File.Exists(sourcePath) ? Collapse(File.ReadAllText(sourcePath)) : ""; + var narrowed = new SortedDictionary<string, string>(StringComparer.Ordinal); foreach (var (name, quote) in c["narrow"] as JsonObject ?? []) { if (!sourceText.Contains(Collapse(Files.Str(quote)), StringComparison.Ordinal)) throw new Refusal($"claim {id} narrows {name} by a quote not found in {sourceFile}"); mine.Remove(name); + narrowed[name] = Files.Str(quote); } foreach (var (name, values) in c["axes"] as JsonObject ?? []) mine[name] = [.. Files.Strings(values)]; + // What the claim promises and what it does not, each a sentence quoted from its document, checked as a + // narrowing is: a sentence the document no longer holds is a refusal, never a stale promise (decision 104). + var obligations = new List<Obligation>(); + var notPromisedIds = (c["not-promised"] as JsonArray ?? []).OfType<JsonObject>().Select(n => Files.Str(n["id"])).ToHashSet(StringComparer.Ordinal); + foreach (var o in (c["obligations"] as JsonArray ?? []).OfType<JsonObject>()) + { + var oid = Files.Str(o["id"]); + if (obligations.Any(x => x.Id == oid)) throw new Refusal($"claim {id}: obligation {oid} is listed twice"); + if (!sourceText.Contains(Collapse(Files.Str(o["sentence"])), StringComparison.Ordinal)) + throw new Refusal($"claim {id}: obligation {oid} quotes a sentence not found in {sourceFile}"); + // Where the obligation does not apply, said by the claim's own axes and values and cited: the covering + // generator, the gate and the harness read the same declarations, so they never disagree. + var declared = new List<Inapplicability>(); + foreach (var (d, i) in (o["inapplicable"] as JsonArray ?? []).OfType<JsonObject>().Select((d, i) => (d, i))) + { + var at = $"claim {id}: obligation {oid}: inapplicable[{i}]"; + var declaration = Declaration(d); + if (declaration.Axes.Count == 0) throw new Refusal($"{at} names no axis"); + foreach (var (name, values) in declaration.Axes) + { + if (!mine.TryGetValue(name, out var listed)) throw new Refusal($"{at} names axis {name}, which the claim does not have"); + if (values.Count == 0) throw new Refusal($"{at} names no value of {name}"); + foreach (var value in values.Where(v => !listed.Contains(v))) + throw new Refusal($"{at} names {name}={value}, a value the claim does not list for {name}"); + } + if (CiteProblem(declaration.Cite) is { } problem) throw new Refusal($"{at}: cite {declaration.Cite} {problem}"); + if (declaration.Cite.StartsWith("not-promised:", StringComparison.Ordinal) && !notPromisedIds.Contains(declaration.Cite["not-promised:".Length..])) + throw new Refusal($"{at} cites {declaration.Cite}, which the claim's not-promised list does not hold"); + declared.Add(declaration); + } + obligations.Add(new Obligation(oid, Files.Str(o["sentence"]), AxesOf(o["axes"])) { Inapplicable = declared }); + } + var notPromised = new List<NotPromised>(); + foreach (var n in (c["not-promised"] as JsonArray ?? []).OfType<JsonObject>()) + { + var nid = Files.Str(n["id"]); var nsource = Files.Str(n["source"]); var nfile = nsource.Split('#')[0]; + if (notPromised.Any(x => x.Id == nid)) throw new Refusal($"claim {id}: not-promised {nid} is listed twice"); + var npath = Path.Combine(ledger.Top, nfile); + var ntext = File.Exists(npath) ? Collapse(File.ReadAllText(npath)) : ""; + if (!ntext.Contains(Collapse(Files.Str(n["sentence"])), StringComparison.Ordinal)) + throw new Refusal($"claim {id}: not-promised {nid} quotes a sentence not found in {nfile}"); + notPromised.Add(new NotPromised(nid, nsource, Files.Str(n["sentence"]), AxesOf(n["axes"]))); + } + // The claims file's schema holds the form (Ledger.ReadClaims): each entry a reason and its pairs. + List<Exclusion> exclusions = [.. (c["exclude"] as JsonArray ?? []).OfType<JsonObject>().Select(e => new Exclusion(Files.Str(e["reason"]), + [.. (e["pairs"] as JsonArray ?? []).OfType<JsonArray>().Select(p => new Pair(Files.Str(p[0]), Files.Str(p[1])))]))]; built.Add(new Claim { Id = id, Source = source, Surfaces = [.. Files.Strings(c["surfaces"])], Watched = [.. watched.Append(sourceFile).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)], Axes = mine, - Excluded = [.. (c["exclude"] as JsonArray ?? []).OfType<JsonArray>().Select(p => new Pair(Files.Str(p[0]), Files.Str(p[1])))], + Excluded = [.. exclusions.SelectMany(e => e.Pairs)], Exclusions = exclusions, Narrowed = narrowed, + Obligations = obligations, NotPromised = notPromised, }); } var universe = new Universe diff --git a/tools/Prober/Commands.cs b/tools/Prober/Commands.cs index e54f1faf..91cc7df3 100644 --- a/tools/Prober/Commands.cs +++ b/tools/Prober/Commands.cs @@ -27,7 +27,7 @@ public Args(IEnumerable<string> argv, IReadOnlySet<string> flags) } } - private static readonly HashSet<string> Multi = new(StringComparer.Ordinal) { "cobertura", "include", "compile-time" }; + private static readonly HashSet<string> Multi = new(StringComparer.Ordinal) { "cobertura", "include", "compile-time", "from", "seed-env" }; private void Add(string name, string value) { if (!options.TryGetValue(name, out var l)) options[name] = l = []; l.Add(value); } public string? Get(string name) => options.TryGetValue(name, out var l) ? l[^1] : null; public string Need(string name) => Get(name) is { Length: > 0 } v ? v : throw new Refusal($"--{name} is required", 2); @@ -41,11 +41,45 @@ internal static class Commands private static string Today => DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); public static int Rows(Ledger l, Args a) + { + if (a.Get("probe") is not { Length: > 0 } probe) + { + l.Ctx.Out.WriteLine(CoveringRows(l, a.Need("claim"), a.Flag("full"), a.Get("max"), a.Get("seed")).ToJsonString(Pretty)); + return 0; + } + if (!l.Registry().TryGetValue(probe, out var entry)) throw new Refusal($"no probe {probe} in the registry, tracked or staged; `prober new` stages one", 2); + var claimId = Files.Str(entry["claim"]); + if (a.Get("claim") is { Length: > 0 } named && named != claimId) throw new Refusal($"{probe} is a probe of {claimId}, not of {named}", 2); + var (rows, uncovered) = StageRows(l, probe, claimId, a.Flag("full"), a.Get("max"), a.Get("seed")); + l.Ctx.Out.WriteLine($"rows/{probe}.json: {rows} rows for claim {claimId} over its pairs{(a.Flag("full") ? "" : " its other probes have not covered")}" + + $"{(uncovered > 0 ? $", {uncovered} left uncovered" : "")}; staged in {l.StagingDir}, the file written whole. " + + "`prober publish` moves it into the corpus"); + return 0; + } + + /// <summary> + /// Writes a probe's rows file in the staging, whole, never appended: a covering array over its claim's pairs (all of + /// them with <paramref name="full"/>) less those the claim's other probes have credited. The probe's own credit is + /// not subtracted, since it came from the rows this file replaces and ends with them. The stored rows are + /// definitions; the evidence is in the state. + /// </summary> + internal static (int Rows, int Uncovered) StageRows(Ledger l, string probe, string claimId, bool full, string? max = null, string? seed = null) + { + var covering = CoveringRows(l, claimId, full, max, seed, except: probe); + var rows = new JsonArray([.. (covering["rows"] as JsonArray ?? []).Select(r => (JsonNode)new JsonObject { ["row"] = r?["row"]?.DeepClone() })]); + Files.WriteJson(Path.Combine(l.StagingDir, "rows", probe + ".json"), new JsonObject { ["claim"] = claimId, ["rows"] = rows }); + return (rows.Count, (int)Files.Num(covering["uncovered_after"])); + } + + /// <summary> + /// What `rows` prints: a pairwise covering array over the claim's pairs not yet covered (all of them with + /// <paramref name="full"/>), each row with the pairs it covers. Inside a capped round the claim counts toward + /// the round's cap, and past the deadline or the cap this refuses. + /// </summary> + public static JsonObject CoveringRows(Ledger l, string claimId, bool full, string? max = null, string? seed = null, string? except = null) { var universe = Universe.Load(l.P("universe.json")) ?? throw new Refusal("no universe.json for this branch yet; run `universe` first"); - var claimId = a.Need("claim"); if (!universe.ById.TryGetValue(claimId, out var claim)) throw new Refusal($"no claim {claimId} in the universe"); - var full = a.Flag("full"); if (!full && ActiveRound(l) is { } round) { if (RoundLock.Now() > Files.Num(round["deadline"])) @@ -60,11 +94,11 @@ public static int Rows(Ledger l, Args a) Files.WriteJson(l.P("round.json"), round); } } - var covered = full ? [] : l.Covered(universe, claimId); - var todo = claim.Pairs().Where(p => !covered.Contains((claimId, p))).ToHashSet(); - var max = int.Parse(a.Get("max") ?? "2000", CultureInfo.InvariantCulture); - var rows = Covering.Rows(claim, todo, max, int.Parse(a.Get("seed") ?? "1", CultureInfo.InvariantCulture)); - var output = new JsonObject + var covered = full ? [] : l.Covered(universe, claimId, except); + // The pairs some obligation wants: a pair declared inapplicable for every obligation is asked for by no row. + var todo = claim.PairsWanted().Where(p => !covered.Contains((claimId, p))).ToHashSet(); + var rows = Covering.Rows(claim, todo, int.Parse(max ?? "2000", CultureInfo.InvariantCulture), int.Parse(seed ?? "1", CultureInfo.InvariantCulture)); + return new JsonObject { ["claim"] = claimId, ["rows"] = new JsonArray([.. rows.Select(r => (JsonNode)new JsonObject @@ -74,13 +108,11 @@ public static int Rows(Ledger l, Args a) })]), ["uncovered_after"] = todo.Count - rows.Sum(r => r.Pairs.Count), }; - l.Ctx.Out.WriteLine(output.ToJsonString(Pretty)); - return 0; } public static int UniverseCommand(Ledger l, Args a) { - var claims = a.Get("claims") is { } cp ? (Files.ReadJson(cp) as JsonArray ?? []).OfType<JsonObject>().ToList() : null; + var claims = a.Get("claims") is { } cp ? Ledger.ReadClaims(cp, cp) : null; var generated = a.Get("generated") is { } gp ? Files.ReadJson(gp) as JsonObject : null; var (universe, problems) = Axes.Build(l, a.Get("base") ?? "origin/main", claims, generated); var total = 0; @@ -88,31 +120,99 @@ public static int UniverseCommand(Ledger l, Args a) { var n = c.Pairs().Count; total += n; l.Ctx.Out.WriteLine($"{c.Id}: {c.Axes.Count} axes, {n} pairs"); + // What the claim leaves out and why: a narrowing's quote is checked against its document, an exclusion's reason by a reviewer only. + foreach (var (axis, quote) in c.Narrowed) l.Ctx.Out.WriteLine($" narrow {axis}: \"{quote}\""); + foreach (var e in c.Exclusions) + l.Ctx.Out.WriteLine($" exclude {e.Pairs.Count} pair(s), first {e.Pairs[0]}: {e.Reason}"); + if (Inapplicable(c) is { } line) l.Ctx.Out.WriteLine($" {line}"); } l.Ctx.Out.WriteLine($"universe: {universe.Claims.Count} claims, {total} pairs"); foreach (var p in problems) l.Ctx.Out.WriteLine($" {p}"); return 0; } + /// <summary> + /// The pairs of a claim no obligation wants, as one line: their count, the first, and the citations of the + /// declarations that leave them out; null where there are none. + /// </summary> + private static string? Inapplicable(Claim c) + { + var dead = c.DeclaredInapplicable(); + if (dead.Count == 0) return null; + var first = dead.Keys.OrderBy(p => p.A, StringComparer.Ordinal).ThenBy(p => p.B, StringComparer.Ordinal).First(); + var cites = new SortedSet<string>(dead.Values.SelectMany(x => x), StringComparer.Ordinal); + return $"inapplicable {dead.Count} pair(s), wanted by no obligation, first {first}: {string.Join(", ", cites)}"; + } + public static JsonObject ComputeStatus(Ledger l) { var universe = Universe.Load(l.P("universe.json")); if (universe is null) return new JsonObject { ["branch"] = l.Branch, ["done"] = false, ["reason"] = "no universe.json: run `universe` (no round has run here)" }; - var done = l.Covered(universe); - var claims = new JsonObject(); var total = 0; var current = 0; + var credits = l.Credits(universe); + var done = credits.Select(x => (x.Claim, x.Pair)).ToHashSet(); + // How the rows and the checks of each claim's probes ended at their last replay: only a passing check earns + // pairs, so the rest is what the claim's coverage loses, and a skip for a finding is an open finding the corpus + // records. A row without checks counts as its one check. + var outcomes = new Dictionary<string, SortedDictionary<string, int>>(StringComparer.Ordinal); + var checkOutcomes = new Dictionary<string, SortedDictionary<string, int>>(StringComparer.Ordinal); + var passed = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); + var skips = new SortedDictionary<string, int>(StringComparer.Ordinal); + foreach (var (probe, entry) in l.Registry()) + { + var evidence = Files.ReadJson(l.EvidencePath(probe)) as JsonObject; + var claim = Files.Str(entry["claim"]); + var sum = outcomes.TryGetValue(claim, out var o) ? o : outcomes[claim] = new(StringComparer.Ordinal); + foreach (var (category, n) in Ledger.Outcomes(evidence)) sum[category] = sum.GetValueOrDefault(category) + n; + var checkSum = checkOutcomes.TryGetValue(claim, out var co) ? co : checkOutcomes[claim] = new(StringComparer.Ordinal); + if (evidence is not null) foreach (var (category, n) in Ledger.CheckOutcomes(evidence)) checkSum[category] = checkSum.GetValueOrDefault(category) + n; + (passed.TryGetValue(claim, out var p) ? p : passed[claim] = new(StringComparer.Ordinal)).UnionWith(Ledger.PassedObligations(evidence)); + foreach (var (finding, n) in Ledger.FindingSkips(evidence)) skips[finding] = skips.GetValueOrDefault(finding) + n; + } + // Each claim's obligations (decision 104), the probes registered for the claim, and the probes whose seeds + // break each obligation: the claim's promise is checked only where a probe can be shown to fail on it. + var listed = l.Claims().ToDictionary(c => Files.Str(c["id"]), Claim.ObligationIds, StringComparer.Ordinal); + var registry = l.Registry(); + var claims = new JsonObject(); var total = 0; var current = 0; var inapplicableTotal = 0; var wantedTotal = 0; var wantedCurrent = 0; foreach (var c in universe.Claims) { var pairs = c.Pairs(); var hit = pairs.Count(p => done.Contains((c.Id, p))); - claims[c.Id] = new JsonObject { ["pairs"] = pairs.Count, ["current"] = hit }; + // The pairs no obligation wants are declared inapplicable: left out of what is still to cover, as the gate leaves them out. + var wanted = c.PairsWanted(); var inapplicable = pairs.Count - wanted.Count; + inapplicableTotal += inapplicable; wantedTotal += wanted.Count; wantedCurrent += wanted.Count(p => done.Contains((c.Id, p))); + var probes = registry.Where(kv => Files.Str(kv.Value["claim"]) == c.Id).ToList(); + var ids = listed.GetValueOrDefault(c.Id) ?? [Claim.ImplicitObligation]; + var obligations = new JsonArray(); + foreach (var id in ids) + { + var seededBy = probes.Where(kv => Seeds.SeedsOf(kv.Value).Any(s => (s.Obligation ?? (ids is [Claim.ImplicitObligation] ? Claim.ImplicitObligation : null)) == id)) + .Select(kv => (JsonNode)kv.Key).ToList(); + // current: the claim's pairs credited by current checks naming this obligation; passed: whether any check + // named it and passed at the last replay, current or not (status's "K uncredited" counts those that did not). + obligations.Add(new JsonObject + { + ["id"] = id, ["seeded_by"] = new JsonArray([.. seededBy]), + ["current"] = pairs.Count(p => credits.Contains((c.Id, id, p))), + ["passed"] = passed.GetValueOrDefault(c.Id)?.Contains(id) ?? false, + }); + } + claims[c.Id] = new JsonObject + { + ["pairs"] = pairs.Count, ["current"] = hit, ["inapplicable_pairs"] = inapplicable, + ["outcomes"] = new JsonObject((outcomes.GetValueOrDefault(c.Id) ?? []).Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), + ["check_outcomes"] = new JsonObject((checkOutcomes.GetValueOrDefault(c.Id) ?? []).Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), + ["probes"] = new JsonArray([.. probes.Select(kv => (JsonNode)kv.Key)]), ["obligations"] = obligations, + }; total += pairs.Count; current += hit; } var open = l.OpenFindings(); return new JsonObject { ["branch"] = l.Branch, ["head"] = l.Head, ["universe_commit"] = universe.Commit, ["pairs"] = total, ["current"] = current, + ["inapplicable_pairs"] = inapplicableTotal, ["share"] = total > 0 ? Math.Round((double)current / total, 4) : 1.0, - ["open_findings"] = new JsonArray([.. open.Select(f => (JsonNode)f)]), ["claims"] = claims, - ["done"] = total > 0 && current == total && open.Count == 0, + ["open_findings"] = new JsonArray([.. open.Select(f => (JsonNode)f)]), + ["skipped_for_findings"] = new JsonObject(skips.Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), ["claims"] = claims, + ["done"] = total > 0 && wantedCurrent == wantedTotal && open.Count == 0, }; } @@ -125,9 +225,105 @@ public static int Status(Ledger l, Args a) l.Ctx.Out.WriteLine($"{l.Branch} at {Files.Str(s["head"])[..7]}: {Files.Num(s["current"])} of {Files.Num(s["pairs"])} pairs current " + $"({(Files.Num(s["share"]) * 100).ToString("F1", CultureInfo.InvariantCulture)}%), {open.Count} open finding(s)" + (open.Count > 0 ? ": " + string.Join(", ", open) : "") + $" -> {(s["done"]!.GetValue<bool>() ? "DONE" : "not done")}"); + foreach (var (finding, rows) in (s["skipped_for_findings"] as JsonObject)!) + l.Ctx.Out.WriteLine($" {rows} row(s) skip for finding {finding}"); foreach (var (id, c) in (s["claims"] as JsonObject)!.OrderBy(kv => kv.Key, StringComparer.Ordinal)) - l.Ctx.Out.WriteLine($" {id}: {Files.Num(c!["current"])}/{Files.Num(c["pairs"])}"); + { + var rows = (c!["outcomes"] as JsonObject)!.Select(kv => $"{kv.Key} {Files.Num(kv.Value)}").ToList(); + var checks = (c["check_outcomes"] as JsonObject ?? []).Select(kv => $"{kv.Key} {Files.Num(kv.Value)}").ToList(); + var obligations = (c["obligations"] as JsonArray ?? []).OfType<JsonObject>().ToList(); + var unseeded = obligations.Where(o => (o["seeded_by"] as JsonArray ?? []).Count == 0).Select(o => Files.Str(o["id"])).ToList(); + // K uncredited: the obligations no passing check named at the last replay. + var uncredited = obligations.Count(o => o["passed"] is not JsonValue v || !v.GetValue<bool>()); + var promise = obligations.Count == 0 ? "" : $" obligations {obligations.Count}" + (unseeded.Count > 0 ? $" ({unseeded.Count} without a seed: {string.Join(", ", unseeded)})" : ", each seeded") + + $", {uncredited} uncredited"; + var inapplicable = Files.Num(c["inapplicable_pairs"]) is var n and > 0 ? $", {n} inapplicable" : ""; + l.Ctx.Out.WriteLine($" {id}: {Files.Num(c["current"])}/{Files.Num(c["pairs"])}{inapplicable}" + (rows.Count > 0 ? $" (rows: {string.Join(", ", rows)}; checks: {string.Join(", ", checks)})" : "") + promise); + } + return 0; + } + + /// <summary> + /// What the corpus leaves out, each with its recorded reason, from the tracked corpus and the generated axes + /// alone: the settings no surface reaches and nothing ignores, the settings without values, the declarations + /// the generators refuse, the values the frameworks refuse on their own, what the claims say they do not + /// promise (their not-promised entries and narrowings), and the surfaces the axes file declares out of reach. + /// A report for the release checklist and a feature-research round; it decides nothing (decision 104). + /// </summary> + public static int Gaps(Ledger l, Args a) + { + var (universe, problems) = Axes.Build(l, a.Get("base") ?? "origin/main"); + var block = l.AxesBlock(); + var generated = Files.ReadJson(Path.Combine(l.Memory, "axes-generated.json")) as JsonObject ?? []; + var versions = string.Join(", ", (generated["versions"] as JsonObject ?? []).Select(kv => $"{kv.Key} {Files.Str(kv.Value)}")); + l.Ctx.Out.WriteLine($"prober gaps: {l.Branch} at {l.Head[..7]}: what the corpus leaves out, each with its recorded reason; a report, no decision"); + Section("settings no surface reaches and nothing ignores" + (versions.Length > 0 ? $" (enumerated at {versions})" : ""), + problems.Where(p => p.StartsWith("unassigned: ", StringComparison.Ordinal)).Select(p => p["unassigned: ".Length..])); + Section("settings without values", problems.Where(p => p.StartsWith("needs values: ", StringComparison.Ordinal)).Select(p => p["needs values: ".Length..])); + Section("declarations the generators refuse (shape.refused-kind)", Files.Strings(block["axes"]?["shape.refused-kind"])); + Section("values the frameworks refuse on their own", + (block["refused"] as JsonObject ?? []).SelectMany(axis => (axis.Value as JsonObject ?? []).Select(v => $"{axis.Key}={v.Key}: {Files.Str(v.Value)}"))); + Section("not promised, by what it concerns", + universe.Claims.SelectMany(c => c.NotPromised.Select(n => (Key: Concerns(n.Axes), Line: $"{Concerns(n.Axes)}: {c.Id}/{n.Id} \"{n.Sentence}\" ({n.Source})")) + .Concat(c.Narrowed.Select(kv => (Key: kv.Key, Line: $"{kv.Key}: {c.Id} narrows it, \"{kv.Value}\" ({c.Source})")))) + .OrderBy(x => x.Key, StringComparer.Ordinal).ThenBy(x => x.Line, StringComparer.Ordinal).Select(x => x.Line)); + Section("pairs no obligation wants (declared inapplicable)", + universe.Claims.Select(c => Inapplicable(c) is { } line ? $"{c.Id}: {line}" : null).OfType<string>()); + Section("pairs no passing check read both axes of (the last replay's evidence)", Unread(l, universe)); + Section("out of reach", (block["out-of-reach"] as JsonObject ?? []).Select(kv => $"{kv.Key}: {Files.Str(kv.Value)}")); return 0; + + void Section(string title, IEnumerable<string> items) + { + var list = items.ToList(); + l.Ctx.Out.WriteLine($"{title}: {(list.Count == 0 ? "none" : list.Count.ToString(CultureInfo.InvariantCulture))}"); + foreach (var item in list) l.Ctx.Out.WriteLine($" {item}"); + } + + static string Concerns(IReadOnlyDictionary<string, List<string>> axes) => + axes.Count == 0 ? "the whole claim" : string.Join(" ", axes.Select(kv => $"{kv.Key}={string.Join("|", kv.Value)}")); + } + + /// <summary> + /// The wanted pairs of each claim with evidence that no passing check could earn: no obligation that wants the pair + /// has a passing check, in its probes' last replay, that read both of the pair's axes. More rows cannot cover such a + /// pair; a check that reads both axes, an exclusion or a declaration can (#54's four C11 pairs, wanted by obligations + /// whose checks never read shape.wrapped). A claim whose probes have no evidence is left out. + /// </summary> + private static IEnumerable<string> Unread(Ledger l, Universe universe) + { + var registry = l.Registry(); + foreach (var claim in universe.Claims.OrderBy(c => c.Id, StringComparer.Ordinal)) + { + // The axes each obligation's passing checks read together, as "a\nb" with a before b. + var read = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); + var any = false; + foreach (var (probe, entry) in registry) + { + if (Files.Str(entry["claim"]) != claim.Id || Files.ReadJson(l.EvidencePath(probe)) is not JsonObject evidence) continue; + any = true; + foreach (var (_, row) in evidence["rows"] as JsonObject ?? []) + if (row is JsonObject r) + foreach (var check in Ledger.ChecksOf(r).Where(c => c.Category == "pass")) + { + if (!read.TryGetValue(check.Obligation, out var axes)) read[check.Obligation] = axes = new(StringComparer.Ordinal); + var names = check.Applied.Keys.Order(StringComparer.Ordinal).ToList(); + for (var i = 0; i < names.Count; i++) + for (var j = i + 1; j < names.Count; j++) axes.Add(names[i] + "\n" + names[j]); + } + } + if (!any) continue; + var wantedBy = claim.ObligationIdsBuilt.Concat(read.Keys).Distinct(StringComparer.Ordinal).ToDictionary(o => o, claim.PairsWantedBy, StringComparer.Ordinal); + foreach (var pair in claim.PairsWanted().OrderBy(p => p.A, StringComparer.Ordinal).ThenBy(p => p.B, StringComparer.Ordinal)) + { + var (a, b) = (Axis(pair.A), Axis(pair.B)); + var both = string.CompareOrdinal(a, b) <= 0 ? a + "\n" + b : b + "\n" + a; + if (!wantedBy.Any(o => o.Value.Contains(pair) && read.TryGetValue(o.Key, out var axes) && axes.Contains(both))) + yield return $"{claim.Id}: {pair.A} {pair.B}"; + } + } + + static string Axis(string setting) => setting[..setting.IndexOf('=', StringComparison.Ordinal)]; } public static int Gate(Ledger l, Args a) => ReleaseGate.Run(l, a.Get("since"), a.Flag("json")); @@ -156,17 +352,24 @@ public static int Footprint(Ledger l, Args a) } } } - if (hit.Count == 0) - throw new Refusal("the coverage report shows no executed file under " + string.Join(", ", include) + "; refusing to record an empty footprint (it would never go stale)"); + // A probe whose own process runs nothing under the include paths (one that builds a consumer in a child process, as C22 + // does) is recorded by what its registry entry watches; with nothing watched either, the footprint would never go stale. + var watchedPaths = l.Watched(a.Need("probe")); + if (hit.Count == 0 && watchedPaths.Count == 0) + throw new Refusal("the coverage report shows no executed file under " + string.Join(", ", include) + + " and the registry entry watches nothing; refusing to record an empty footprint (it would never go stale): name what the probe depends on in its entry's watched"); var rev = Git.Out(worktree, "rev-parse", "HEAD"); var footprint = new JsonObject(hit.Select(p => KeyValuePair.Create(p, (JsonNode?)Git.ObjectId(worktree, p)))); var compileTime = new JsonObject(a.All("compile-time").Select(p => KeyValuePair.Create(p, (JsonNode?)Git.ObjectId(worktree, p)))); + // What the probe's registry entry watches beyond what ran: recorded with the ids it ran at, so a change stales it. + var watched = new JsonObject(watchedPaths.Select(p => KeyValuePair.Create(p, (JsonNode?)Git.ObjectId(worktree, p)))); Files.AppendLine(l.P("footprints.jsonl"), new JsonObject { ["schemaVersion"] = Files.SchemaVersion, ["probe"] = a.Need("probe"), ["commit"] = rev, ["footprint"] = footprint, - ["compileTime"] = compileTime, ["toolchain"] = l.Toolchain().DeepClone(), ["date"] = a.Get("date") ?? Today, + ["compileTime"] = compileTime, ["watched"] = watched, ["toolchain"] = l.Toolchain().DeepClone(), ["date"] = a.Get("date") ?? Today, }); - l.Ctx.Out.WriteLine($"{a.Need("probe")}: {footprint.Count} src files, {compileTime.Count} compile-time paths at {rev[..Math.Min(7, rev.Length)]}"); + l.Ctx.Out.WriteLine($"{a.Need("probe")}: {footprint.Count} src files, {compileTime.Count} compile-time paths" + + $"{(watched.Count > 0 ? $", {watched.Count} watched" : "")} at {rev[..Math.Min(7, rev.Length)]}"); return 0; } @@ -205,7 +408,7 @@ public static int RecordRun(Ledger l, Args a) if (a.Get("evidence") is { } evidencePath) { var evidence = Files.ReadJson(evidencePath) as JsonObject ?? []; - evidence["schemaVersion"] = Files.SchemaVersion; evidence["probe"] = probe; evidence["identity"] = identity; + evidence["schemaVersion"] = Files.EvidenceSchemaVersion; evidence["probe"] = probe; evidence["identity"] = identity; evidence["commit"] = rev; evidence["result"] = result; evidence["date"] = a.Get("date") ?? Today; Files.WriteJson(l.EvidencePath(probe), evidence); } @@ -224,12 +427,39 @@ public static int RecordRun(Ledger l, Args a) public static int Register(Ledger l, Args a) { + // A note or a skip built from text fails at run time after a build and a replay; the staged sources are read first. + SourceLint.Refuse(l, "register"); var path = Path.Combine(l.StagingDir, "registry.json"); var staged = Files.ReadJson(path) as JsonObject ?? new JsonObject { ["probes"] = new JsonObject() }; var probes = staged["probes"] as JsonObject ?? []; var entry = l.Registry().GetValueOrDefault(a.Need("probe")) ?? []; entry["claim"] = a.Need("claim"); entry["filter"] = a.Need("filter"); if (a.Get("variants") is { Length: > 0 } v) entry["variants"] = JsonNode.Parse(v); + // The defect `seeds` applies to prove the probe can fail (decision 100): a patch under the corpus, an + // environment toggle, or both, and what it breaks. + var seed = new JsonObject(); + if (a.Get("seed") is { Length: > 0 } patch) seed["patch"] = patch; + if (a.All("seed-env") is { Count: > 0 } toggles) seed["env"] = SeedEnv(toggles); + if (a.Get("obligation") is { } obligation) + { + // A seed per obligation (decision 104): into `seeds`, in place of any seed that names the same obligation. + if (obligation.Length == 0) throw new Refusal("--obligation needs the id of one of the claim's obligations", 2); + if (seed.Count == 0) throw new Refusal("--obligation names the obligation of a seed: give the seed with --seed PATCH or --seed-env NAME=VALUE", 2); + var claim = l.Claims().FirstOrDefault(c => Files.Str(c["id"]) == a.Need("claim")) + ?? throw new Refusal($"no claim {a.Need("claim")} in claims.json (tracked or staged), so obligation {obligation} cannot be checked"); + var ids = Claim.ObligationIds(claim); + if (!ids.Contains(obligation)) + throw new Refusal($"claim {a.Need("claim")} lists no obligation {obligation} (it lists {string.Join(", ", ids)})", 2); + var named = new JsonObject { ["obligation"] = obligation }; + foreach (var (key, value) in seed) named[key] = value?.DeepClone(); + named["defect"] = a.Need("defect"); + var kept = (entry["seeds"] as JsonArray ?? []).Where(s => Files.Str(s?["obligation"]) != obligation).Select(s => s?.DeepClone()).ToList(); + var replaced = (entry["seeds"] as JsonArray ?? []).Count - kept.Count; + entry["seeds"] = new JsonArray([.. kept, named]); + if (entry["seed"] is JsonObject one && Files.Str(one["obligation"]) == obligation) { entry.Remove("seed"); replaced++; } + l.Ctx.Out.WriteLine($"seed for obligation {obligation} of {a.Need("probe")}" + (replaced > 0 ? $", in place of the {replaced} it had" : "")); + } + else if (seed.Count > 0) { seed["defect"] = a.Need("defect"); entry["seed"] = seed; } probes[a.Need("probe")] = entry; staged["probes"] = probes.DeepClone(); Files.WriteJson(path, staged); @@ -237,9 +467,23 @@ public static int Register(Ledger l, Args a) return 0; } + /// <summary>A seed's environment toggle from <c>--seed-env NAME=VALUE ...</c>; a toggle without a name or an <c>=</c> is refused.</summary> + private static JsonObject SeedEnv(List<string> toggles) + { + var env = new JsonObject(); + foreach (var toggle in toggles) + { + var eq = toggle.IndexOf('=', StringComparison.Ordinal); + if (eq <= 0) throw new Refusal($"--seed-env {toggle}: a toggle is NAME=VALUE", 2); + env[toggle[..eq]] = toggle[(eq + 1)..]; + } + return env; + } + public static int Publish(Ledger l, Args a) { if (!Directory.Exists(l.StagingDir)) { l.Ctx.Out.WriteLine("nothing staged"); return 0; } + SourceLint.Refuse(l, "publish"); var moved = new List<string>(); foreach (var src in Directory.EnumerateFiles(l.StagingDir, "*", SearchOption.AllDirectories).Order(StringComparer.Ordinal)) { @@ -256,13 +500,17 @@ public static int Publish(Ledger l, Args a) else if (rel == "claims.json") { var merged = new SortedDictionary<string, JsonNode>(StringComparer.Ordinal); - foreach (var c in (Files.ReadJson(dst) as JsonArray ?? []).Concat(Files.ReadJson(src) as JsonArray ?? []).OfType<JsonObject>()) + foreach (var c in Ledger.ReadClaims(dst, "the corpus's claims.json").Concat(Ledger.ReadClaims(src, "the staged claims.json"))) merged[Files.Str(c["id"])] = c.DeepClone(); Files.WriteJson(dst, new JsonArray([.. merged.Values])); } else if (rel.StartsWith("rows/", StringComparison.Ordinal) && rel.EndsWith(".json", StringComparison.Ordinal)) { - var claim = Path.GetFileNameWithoutExtension(rel); + // A probe's rows, by probe: the file keeps the claim it names, else its probe's registry entry's. + var probe = Path.GetFileNameWithoutExtension(rel); + var claim = Files.Str(Files.ReadJson(src)?["claim"]) is { Length: > 0 } named ? named + : l.Registry().GetValueOrDefault(probe)?["claim"] is { } registered ? Files.Str(registered) + : throw new Refusal($"the staged {rel} names no claim and {probe} is no registered probe; a rows file is a probe's, its claim inside"); Files.WriteRowsGzipped(dst + ".gz", claim, Files.ReadRows(src).OfType<JsonObject>().Select(r => r["row"] as JsonObject ?? r)); rel += ".gz"; } @@ -306,6 +554,32 @@ public static int Dismiss(Ledger l, Args a) return 0; } + /// <summary> + /// Records an open finding fixed at a commit: the fixed line of findings.jsonl (finding-line.schema.json), with the + /// open line's claim and probe and the commit's full id. It refuses an id this ledger does not hold open and a + /// commit the repository does not have; a commit outside HEAD's history is recorded and counts once it is in it. + /// </summary> + public static int Fix(Ledger l, Args a) + { + var id = a.Positional is [var only] ? only : throw new Refusal("fix takes one finding id: fix F<n> --commit <sha>", 2); + var given = a.Need("commit"); + var resolved = Git.Run(l.Top, "rev-parse", "-q", "--verify", given + "^{commit}"); + if (resolved.ExitCode != 0) throw new Refusal($"--commit {given} is no commit of this repository"); + var commit = resolved.Stdout.Trim(); + if (!l.Findings().TryGetValue(id, out var open)) throw new Refusal($"{id} is no finding of this ledger"); + if (Files.Str(open["status"]) is var status and not "open") throw new Refusal($"{id} is {status}, not open"); + var line = new JsonObject + { + ["schemaVersion"] = Files.SchemaVersion, ["id"] = id, ["claim"] = open["claim"]?.DeepClone(), ["status"] = "fixed", ["commit"] = commit, + }; + if (Files.Str(open["probe"]) is { Length: > 0 } probe) line["probe"] = probe; + if (a.Get("reason") is { Length: > 0 } reason) line["reason"] = reason; + Files.AppendLine(l.P("findings.jsonl"), line); + var inHistory = Git.Run(l.Top, "merge-base", "--is-ancestor", commit, "HEAD").ExitCode == 0; + l.Ctx.Out.WriteLine($"{id} fixed at {commit[..7]}" + (inHistory ? "" : "; it counts once that commit is in HEAD's history")); + return 0; + } + public static int Lock(Ledger l, Args a) { var holder = a.Need("holder"); var mode = a.Need("mode"); @@ -397,4 +671,77 @@ public static int Abandon(Ledger l, Args a) l.Ctx.Out.WriteLine(held is null ? "no lock was held" : $"lock of {Files.Str(held["holder"])} ({Files.Str(held["mode"])}) released"); return 0; } + + /// <summary> + /// Joins the states of a replay split by family (<c>replay --family F</c>, one state directory per leg) into + /// this branch's state: each leg's evidence files are copied in, its run, footprint, finding and ledger lines + /// are added where this state lacks them (a line a leg carried over from a restored state is not added + /// twice), and one replay-last.json sums the legs. It refuses while a probe the registry lists was neither + /// replayed nor skipped by any leg, which is how a matrix that leaves a family out fails instead of passing + /// on what it ran, and it fails (exit 1) when any leg failed, so the job that merges fails as the legs did. + /// </summary> + public static int Merge(Ledger l, Args a) + { + var sources = a.All("from").Where(x => x.Length > 0).ToList(); + if (sources.Count == 0) throw new Refusal("--from DIR... is required: the state directories of the legs to join into this one", 2); + var branchDir = Path.GetFileName(l.Dir); + Directory.CreateDirectory(Path.Combine(l.Dir, "evidence")); + var legs = new List<JsonObject>(); + var seen = new SortedSet<string>(StringComparer.Ordinal); + foreach (var from in sources) + { + var root = Path.GetFullPath(from); + var dir = Path.Combine(root, "branches", branchDir); + var last = Files.ReadJson(Path.Combine(dir, "replay-last.json")) as JsonObject + ?? throw new Refusal($"{from} holds no replay of {l.Branch}: no branches/{branchDir}/replay-last.json"); + if (legs.Count > 0 && Files.Str(last["commit"]) != Files.Str(legs[0]["commit"])) + throw new Refusal($"{from} replayed {Files.Str(last["commit"])[..7]}, another leg {Files.Str(legs[0]["commit"])[..7]}; the legs of one replay share a commit"); + legs.Add(last); + foreach (var name in new[] { "runs.jsonl", "footprints.jsonl", "findings.jsonl", "ledger.jsonl" }) UnionLines(Path.Combine(dir, name), l.P(name)); + foreach (var sub in new[] { "evidence", "logs" }) + { + var source = Path.Combine(dir, sub); + if (!Directory.Exists(source)) continue; + Directory.CreateDirectory(l.P(sub)); + foreach (var file in Directory.EnumerateFiles(source)) File.Copy(file, l.P(Path.Combine(sub, Path.GetFileName(file))), overwrite: true); + } + foreach (var name in new[] { "universe.json" }) + if (File.Exists(Path.Combine(dir, name)) && !File.Exists(l.P(name))) File.Copy(Path.Combine(dir, name), l.P(name)); + var axes = Path.Combine(root, "axes-generated.json"); + if (File.Exists(axes) && !File.Exists(Path.Combine(l.Memory, "axes-generated.json"))) File.Copy(axes, Path.Combine(l.Memory, "axes-generated.json")); + foreach (var probe in Files.Strings(last["selected"]).Concat(Files.Strings(last["skipped"]))) seen.Add(probe); + } + + var all = legs.Any(x => x["all"] is JsonValue v && v.TryGetValue<bool>(out var b) && b); + var selected = new SortedSet<string>(legs.SelectMany(x => Files.Strings(x["selected"])), StringComparer.Ordinal); + var skipped = new SortedSet<string>(legs.SelectMany(x => Files.Strings(x["skipped"])).Where(p => !selected.Contains(p)), StringComparer.Ordinal); + var failed = new SortedSet<string>(legs.SelectMany(x => Files.Strings(x["failed"])), StringComparer.Ordinal); + static JsonArray Names(IEnumerable<string> names) => new([.. names.Select(x => (JsonNode)x)]); + Files.WriteJson(l.P("replay-last.json"), new JsonObject + { + ["branch"] = l.Branch, ["commit"] = Files.Str(legs[0]["commit"]), ["replayed"] = selected.Count, ["failed"] = Names(failed), + ["seconds"] = Math.Round(legs.Max(x => Files.Num(x["seconds"])), 1), ["build_seconds"] = Math.Round(legs.Max(x => Files.Num(x["build_seconds"])), 1), + ["all"] = all, ["legs"] = Names(legs.Select(x => Files.Str(x["family"], "all"))), ["footprint"] = legs.All(x => x["footprint"] is not JsonValue f || !f.TryGetValue<bool>(out var ft) || ft), + ["selected"] = Names(selected), ["skipped"] = Names(skipped), + }); + l.Ctx.Out.WriteLine($"prober merge: {legs.Count} leg(s) into {l.Dir}: {selected.Count} replayed, {failed.Count} failed, {skipped.Count} current"); + + var missing = l.Registry().Where(kv => all || kv.Value["retired"] is not JsonValue r || !r.TryGetValue<bool>(out var retired) || !retired) + .Select(kv => kv.Key).Where(p => !seen.Contains(p)).ToList(); + if (missing.Count > 0) + throw new Refusal($"no leg replayed or skipped {string.Join(", ", missing)}: every probe's family is a leg of the matrix, or the merged state is short"); + if (failed.Count > 0) { l.Ctx.Out.WriteLine($" failed: {string.Join(", ", failed)}"); return 1; } + return 0; + } + + /// <summary>Adds to <paramref name="target"/> every line of <paramref name="source"/> it does not already hold, in order.</summary> + private static void UnionLines(string source, string target) + { + if (!File.Exists(source)) return; + var have = File.Exists(target) ? SharedFile.ReadAllLines(target).Where(x => x.Trim().Length > 0).ToList() : []; + var known = new HashSet<string>(have, StringComparer.Ordinal); + var added = SharedFile.ReadAllLines(source).Where(x => x.Trim().Length > 0 && known.Add(x)).ToList(); + if (added.Count == 0 && File.Exists(target)) return; + Files.WriteAtomically(target, string.Join('\n', have.Concat(added)) + (have.Count + added.Count > 0 ? "\n" : "")); + } } diff --git a/tools/Prober/Context.cs b/tools/Prober/Context.cs index 7be084c3..605654a5 100644 --- a/tools/Prober/Context.cs +++ b/tools/Prober/Context.cs @@ -30,10 +30,15 @@ internal sealed record Ran(int ExitCode, string Stdout, string Stderr); internal static class Proc { - /// <summary>Runs a program to completion, capturing its output. Never through a shell.</summary> + /// <summary> + /// Runs a program to completion, capturing its output. Never through a shell. `dotnet` is the one that + /// satisfies the directory's global.json, the installer's when the one on PATH cannot (Dotnet). + /// </summary> public static Ran Run(string fileName, IEnumerable<string> arguments, string workingDirectory, IReadOnlyDictionary<string, string?>? environment = null, TimeSpan? timeout = null) { + string? dotnetDirectory = null; + if (fileName == "dotnet") (fileName, dotnetDirectory) = Dotnet.For(workingDirectory); var start = new ProcessStartInfo(fileName) { WorkingDirectory = workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, @@ -43,6 +48,7 @@ public static Ran Run(string fileName, IEnumerable<string> arguments, string wor if (environment is not null) foreach (var (name, value) in environment) if (value is null) start.Environment.Remove(name); else start.Environment[name] = value; + Dotnet.Apply(dotnetDirectory, start.Environment); using var process = Process.Start(start) ?? throw new InvalidOperationException($"could not start {fileName}"); var stdout = process.StandardOutput.ReadToEndAsync(); diff --git a/tools/Prober/Covering.cs b/tools/Prober/Covering.cs index a330493f..d4cfd13c 100644 --- a/tools/Prober/Covering.cs +++ b/tools/Prober/Covering.cs @@ -2,13 +2,22 @@ namespace Prober; /// <summary> /// A greedy pairwise covering array: each row is the best of a few randomized greedy candidates, and the -/// rows together cover every pair asked for. Deterministic for a seed. +/// rows together cover every pair asked for that at least one of the claim's obligations wants. A pair no +/// obligation wants (declared inapplicable) is never asked for, and neither it nor an excluded pair is ever put +/// in a row, not even to fill an axis: every check of such a row would be inapplicable, and an excluded pair is +/// one the claim says no row can run. An axis none of whose values can join a row without one is left out of +/// that row: shape.containing beside shape.position=root, and shape.refused-kind wherever the claim excludes each of +/// its pairs. Deterministic for a seed. /// </summary> internal static class Covering { public static List<(SortedDictionary<string, string> Row, List<Pair> Pairs)> Rows(Claim claim, IReadOnlySet<Pair> wanted, int max, int seed) { + var dead = claim.DeclaredInapplicable(); + bool Blocked(Pair pair) => claim.Excluded.Contains(pair) || dead.ContainsKey(pair); + // The pairs asked for, each once however many obligations want it, so a row's gain counts it once. var todo = new HashSet<Pair>(wanted); + todo.RemoveWhere(Blocked); var names = claim.Axes.Keys.ToList(); var random = new Random(seed); var rows = new List<(SortedDictionary<string, string>, List<Pair>)>(); @@ -22,19 +31,20 @@ internal static class Covering foreach (var name in order) { var options = claim.Axes[name].OrderBy(_ => random.Next()).ToList(); - string? pick = null; var pickGain = int.MinValue; + string? pick = null; var pickGain = -1; foreach (var v in options) { var gain = 0; foreach (var (n, w) in row) { var pair = new Pair($"{name}={v}", $"{n}={w}"); - if (claim.Excluded.Contains(pair)) { gain = -1; break; } + if (Blocked(pair)) { gain = -1; break; } if (todo.Contains(pair)) gain++; } if (gain > pickGain) { pick = v; pickGain = gain; } } - row[name] = pick!; + // Every value of this axis puts a blocked pair in the row: the axis is left out of it. + if (pick is not null) row[name] = pick; } var g = Gained(row, todo).Count; if (g > bestGain) { best = row; bestGain = g; } @@ -42,9 +52,7 @@ internal static class Covering if (bestGain <= 0) { // Seed a row from one uncovered pair so progress never stalls. - var pair = todo.OrderBy(p => p.A, StringComparer.Ordinal).ThenBy(p => p.B, StringComparer.Ordinal).First(); - best = new SortedDictionary<string, string>(names.ToDictionary(n => n, n => claim.Axes[n][0]), StringComparer.Ordinal); - foreach (var item in new[] { pair.A, pair.B }) { var at = item.IndexOf('='); best[item[..at]] = item[(at + 1)..]; } + best = Seeded(claim, todo.OrderBy(p => p.A, StringComparer.Ordinal).ThenBy(p => p.B, StringComparer.Ordinal).First(), dead); } var newly = Gained(best!, todo); todo.ExceptWith(newly); @@ -53,6 +61,23 @@ internal static class Covering return rows; } + static string Axis(string setting) => setting[..setting.IndexOf('=', StringComparison.Ordinal)]; + + /// <summary> + /// A row seeded from one pair: the pair's two settings, and each other axis at its first value that puts neither an excluded pair + /// nor a pair no obligation wants (<paramref name="dead"/>) in the row, or left out where none does. + /// </summary> + internal static SortedDictionary<string, string> Seeded(Claim claim, Pair pair, IReadOnlyDictionary<Pair, SortedSet<string>> dead) + { + bool Blocked(Pair p) => claim.Excluded.Contains(p) || dead.ContainsKey(p); + var row = new SortedDictionary<string, string>(StringComparer.Ordinal); + foreach (var item in new[] { pair.A, pair.B }) row[Axis(item)] = item[(item.IndexOf('=', StringComparison.Ordinal) + 1)..]; + foreach (var name in claim.Axes.Keys.Where(n => !row.ContainsKey(n)).ToList()) + if (claim.Axes[name].FirstOrDefault(v => !row.Any(kv => Blocked(new Pair($"{name}={v}", $"{kv.Key}={kv.Value}")))) is { } value) + row[name] = value; + return row; + } + private static List<Pair> Gained(SortedDictionary<string, string> row, HashSet<Pair> todo) { var result = new List<Pair>(); diff --git a/tools/Prober/Files.cs b/tools/Prober/Files.cs index b9755ac2..51f83769 100644 --- a/tools/Prober/Files.cs +++ b/tools/Prober/Files.cs @@ -1,5 +1,6 @@ using System.IO.Compression; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; @@ -16,7 +17,20 @@ internal static class Files /// <summary>The schema version this tool writes. A file or line without one is version 1.</summary> public const int SchemaVersion = 1; - private static readonly JsonSerializerOptions Indented = new() { WriteIndented = true }; + /// <summary> + /// The version of evidence/<probe>.json this tool writes: 2 records each row's checks, one entry per check + /// with its own obligation, baseline, outcome and settings. A version-1 file reads as rows without checks, each one + /// check of the obligation <c>claim</c>. + /// </summary> + public const int EvidenceSchemaVersion = 2; + + /// <summary> + /// Indented, and with only what JSON itself requires escaped: the tracked corpus is read in a diff, where + /// the default encoder's six-character escapes for <c>+</c>, a backtick and <c><</c> made every publish a + /// rewrite (decision 102). No reader here or in the harness parses these files as + /// anything but JSON, and an identity hashes <see cref="Canonical"/>, not the file's text. + /// </summary> + private static readonly JsonSerializerOptions Indented = new() { WriteIndented = true, NewLine = "\n", Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; public static JsonNode? ReadJson(string path) { @@ -27,7 +41,10 @@ internal static class Files catch (UnauthorizedAccessException) { return null; } // Windows: a file being replaced this instant } - public static void WriteJson(string path, JsonNode value) => WriteAtomically(path, value.ToJsonString(Indented) + "\n"); + public static void WriteJson(string path, JsonNode value) => WriteAtomically(path, Format(value)); + + /// <summary>The text <see cref="WriteJson"/> writes: a tracked file in this form is unchanged by a publish that changes nothing in it.</summary> + public static string Format(JsonNode value) => value.ToJsonString(Indented) + "\n"; public static List<JsonObject> ReadLines(string path) { diff --git a/tools/Prober/Ledger.cs b/tools/Prober/Ledger.cs index 657a358a..50673244 100644 --- a/tools/Prober/Ledger.cs +++ b/tools/Prober/Ledger.cs @@ -15,6 +15,7 @@ internal sealed class Ledger public Context Ctx { get; } public string Root { get; } // the main checkout, also from a linked worktree public string Top { get; } // this checkout + public bool Linked { get; } // a linked worktree (git worktree add), not the main checkout public string Branch { get; } public string Memory { get; } // PROBE_STATE_DIR, else the main checkout's prober memory public string Dir { get; } // the branch's directory under Memory @@ -25,9 +26,12 @@ internal sealed class Ledger public Ledger(Context ctx, string? branch) { Ctx = ctx; - var common = Git.Run(ctx.WorkingDirectory, "rev-parse", "--path-format=absolute", "--git-common-dir"); - if (common.ExitCode != 0) throw new Refusal("not inside a git repository"); - Root = Path.GetDirectoryName(common.Stdout.Trim().TrimEnd('/', '\\'))!; + var dirs = Git.Run(ctx.WorkingDirectory, "rev-parse", "--path-format=absolute", "--git-common-dir", "--git-dir"); + if (dirs.ExitCode != 0) throw new Refusal("not inside a git repository"); + var lines = dirs.Stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(d => d.TrimEnd('/', '\\')).ToArray(); + if (lines is not [var common, var own]) throw new Refusal("git rev-parse named no git directory"); + Root = Path.GetDirectoryName(common)!; + Linked = own != common; Top = Git.Out(ctx.WorkingDirectory, "rev-parse", "--show-toplevel"); Branch = branch ?? Git.Out(ctx.WorkingDirectory, "rev-parse", "--abbrev-ref", "HEAD"); Memory = ctx.Env("PROBE_STATE_DIR") ?? Path.Combine(Root, ".claude", "agent-memory-local", "prober"); @@ -59,27 +63,58 @@ public List<JsonObject> Claims() { var merged = new SortedDictionary<string, JsonObject>(StringComparer.Ordinal); foreach (var path in new[] { Path.Combine(CorpusDir, "claims.json"), Path.Combine(StagingDir, "claims.json") }) - if (Files.ReadJson(path) is JsonArray claims) - foreach (var c in claims.OfType<JsonObject>()) - merged[Files.Str(c["id"])] = (JsonObject)c.DeepClone(); + foreach (var c in ReadClaims(path, Path.GetRelativePath(Top, path).Replace('\\', '/'))) + merged[Files.Str(c["id"])] = (JsonObject)c.DeepClone(); return [.. merged.Values]; } - public JsonArray RowsOf(string claim) + /// <summary> + /// A claims file, refused unless it matches Schemas/claims.schema.json, each problem named by its claim and + /// field (decision 102). No file is no claims. + /// </summary> + public static List<JsonObject> ReadClaims(string path, string shown) { - var relative = Path.Combine("rows", claim + ".json"); + if (!File.Exists(path)) return []; + if (Files.ReadJson(path) is not JsonArray claims) throw new Refusal($"{shown} is not a JSON list of claims"); + var schema = SchemaCheck.Load("claims.schema.json")["items"] as JsonObject ?? throw new InvalidOperationException("claims.schema.json has no items"); + var problems = new List<string>(); + for (var i = 0; i < claims.Count; i++) + { + var id = claims[i] is JsonObject o && Files.Str(o["id"]) is { Length: > 0 } s ? s : $"#{i}"; + problems.AddRange(SchemaCheck.Problems(claims[i], schema).Select(p => $"claim {id}: {p}")); + } + if (problems.Count > 0) + throw new Refusal($"{shown} does not match tools/Prober/Schemas/claims.schema.json:\n " + string.Join("\n ", problems)); + return [.. claims.OfType<JsonObject>()]; + } + + /// <summary> + /// A probe's own rows (rows/P.json, staged, else the corpus's rows/P.json.gz). Each probe has a file of its own, its claim + /// inside, so the rows of one probe of a claim never move another's identity. + /// </summary> + public JsonArray RowsOf(string probe) + { + var relative = Path.Combine("rows", probe + ".json"); var staged = Path.Combine(StagingDir, relative); return File.Exists(staged) ? Files.ReadRows(staged) : Files.ReadRows(Path.Combine(CorpusDir, relative)); } + /// <summary> + /// The paths a probe's registry entry watches: what its result depends on that the footprint cannot see, such as a + /// variant catalogue under test/ compiled into the probes or a build input a child process reads. They stale its + /// footprint like the paths it ran. + /// </summary> + public IReadOnlyList<string> Watched(string probe) => [.. Files.Strings(Registry().GetValueOrDefault(probe)?["watched"])]; + public JsonObject AxesBlock() => Axes.Block(AxesFile); // ---- identity -------------------------------------------------------------------------------------- /// <summary> - /// What a probe's result depends on besides the product footprint: its registry entry, every source - /// compiled with it (the harness and all probes), its claim's rows, the axes file, the build environment - /// (SDK, central package versions, build props, global.json), this tool's version and the schema version. + /// What a probe's result depends on besides the product footprint: its registry entry (not its seed), every source + /// compiled with it (the harness and all probes), its own rows (not a sibling's), the axes file, the build environment + /// (SDK, central package versions, build props, global.json), this tool's version and the schema version, and + /// where its claim declares where an obligation does not apply, those declarations, which the harness reads. /// A result counts only while this is unchanged. /// </summary> public string Identity(string probe) @@ -87,7 +122,11 @@ public string Identity(string probe) using var sha = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); void Add(string s) => sha.AppendData(Encoding.UTF8.GetBytes(s + "\0")); var entry = Registry().GetValueOrDefault(probe) ?? []; - Add(Files.Canonical(entry)); + // The seeds prove the probe; a result of the probe depends on none of them (decisions 100 and 104). + var registered = (JsonObject)entry.DeepClone(); + registered.Remove("seed"); + registered.Remove("seeds"); + Add(Files.Canonical(registered)); var sources = new SortedDictionary<string, string>(StringComparer.Ordinal); foreach (var baseDir in new[] { CorpusDir, StagingDir }) foreach (var sub in new[] { "harness", "probes" }) @@ -97,7 +136,10 @@ public string Identity(string probe) foreach (var file in Directory.EnumerateFiles(folder, "*.cs")) sources[Path.GetFileName(file)] = file; } foreach (var (name, file) in sources) { Add(name); sha.AppendData(File.ReadAllBytes(file)); } - Add(Files.Canonical(RowsOf(Files.Str(entry["claim"])))); + Add(Files.Canonical(RowsOf(probe))); + // The declarations Row.Check applies before a check's body runs: a change to one changes what the probe's rows + // check. Added only where the claim declares something, so a claim without declarations leaves the identity as it was. + if (Declarations(Files.Str(entry["claim"])) is { } declared) Add("inapplicable:" + Files.Canonical(declared)); try { Add(Files.Canonical(AxesBlock())); } catch (Refusal) { Add("no axes file"); } foreach (var path in new[] { "Directory.Packages.props", "Directory.Build.props", "global.json" }) Add(path + (Git.ObjectId(Top, path) ?? "-")); @@ -107,25 +149,52 @@ public string Identity(string probe) return Convert.ToHexStringLower(sha.GetHashAndReset())[..16]; } + private Dictionary<string, JsonArray>? declarations; + + /// <summary>A claim's declarations of where its obligations do not apply, as claims.json holds them, by obligation; null where it declares none.</summary> + private JsonArray? Declarations(string claim) + { + declarations ??= Claims().ToDictionary(c => Files.Str(c["id"]), c => new JsonArray([.. (c["obligations"] as JsonArray ?? []).OfType<JsonObject>() + .Where(o => o["inapplicable"] is JsonArray { Count: > 0 }) + .Select(o => (JsonNode)new JsonObject { ["obligation"] = Files.Str(o["id"]), ["inapplicable"] = o["inapplicable"]!.DeepClone() })]), StringComparer.Ordinal); + return declarations.TryGetValue(claim, out var declared) && declared.Count > 0 ? declared : null; + } + private JsonObject? toolchain; - /// <summary>What no file in the repository records: the SDK a global.json roll-forward picked, say.</summary> + /// <summary> + /// What no file in the repository records: the SDK a global.json roll-forward picked, say. A command that cannot + /// say it (it does not start, exits non-zero or prints nothing) is a refusal: its error text in every identity + /// would read as a toolchain change, and every result as stale, without a word why. + /// </summary> public JsonObject Toolchain() { if (toolchain is not null) return toolchain; + JsonObject? commands; + try { commands = AxesBlock()["replay"]?["toolchain"] as JsonObject; } catch (Refusal) { commands = null; } var result = new JsonObject(); - try + foreach (var (name, argv) in (commands ?? []).OrderBy(kv => kv.Key, StringComparer.Ordinal)) { - if (AxesBlock()["replay"]?["toolchain"] is JsonObject commands) - foreach (var (name, argv) in commands.OrderBy(kv => kv.Key, StringComparer.Ordinal)) - { - var parts = Files.Strings(argv).ToArray(); - var ran = Proc.Run(parts[0], parts[1..], Top); - var text = (ran.Stdout.Trim().Length > 0 ? ran.Stdout : ran.Stderr).Trim(); - result[name] = text.Split('\n')[0].Trim(); - } + var parts = Files.Strings(argv).ToArray(); + var shown = $"the toolchain command {name} (`{string.Join(' ', parts)}`)"; + if (parts.Length == 0) throw new Refusal($"{shown} is empty"); + Ran ran; + try { ran = Proc.Run(parts[0], parts[1..], Top); } + catch (System.ComponentModel.Win32Exception e) { throw new Refusal($"{shown} did not start: {e.Message}"); } + static string FirstLine(string s) => s.Trim().Split('\n')[0].Trim(); + // A failing `dotnet --version` lists the installed SDKs on stdout ("10.0.401 [path]", a version to look at) + // and says why on stderr. + if (ran.ExitCode != 0) + { + var why = FirstLine(ran.Stderr) is { Length: > 0 } err ? err : FirstLine(ran.Stdout); + throw new Refusal($"{shown} exited {ran.ExitCode}: {(why.Length > 0 ? why : "no output")}. A result is bound to what it prints, " + + "so none could be current; put the toolchain global.json asks for first on PATH, or install it where " + + $"the SDK installer puts it ({Dotnet.InstallerDirectory}), which `dotnet` falls back to"); + } + var first = FirstLine(ran.Stdout.Trim().Length > 0 ? ran.Stdout : ran.Stderr); + if (first.Length == 0) throw new Refusal($"{shown} printed nothing, so a result could be bound to no toolchain"); + result[name] = first; } - catch (Exception e) when (e is Refusal or System.ComponentModel.Win32Exception) { } return toolchain = result; } @@ -137,14 +206,20 @@ public Dictionary<string, JsonObject> Footprints() => private readonly Dictionary<string, bool> currentCache = new(StringComparer.Ordinal); - /// <summary>A probe's footprint is current when every path it ran, every compile-time path and the toolchain are unchanged at HEAD.</summary> + /// <summary> + /// A probe's footprint is current when every path it ran, every compile-time path, every path its registry entry + /// watches and the toolchain are unchanged at HEAD. A watched path the footprint did not record (one added to the + /// entry since) is not current. A footprint that ran nothing under <c>src/</c> is current only by watched paths it recorded. + /// </summary> public bool FootprintCurrent(string probe, Dictionary<string, JsonObject> footprints) { if (currentCache.TryGetValue(probe, out var cached)) return cached; var ok = false; - if (footprints.TryGetValue(probe, out var r) && r["footprint"] is JsonObject fp && fp.Count > 0) + if (footprints.TryGetValue(probe, out var r) && r["footprint"] is JsonObject fp && (fp.Count > 0 || r["watched"] is JsonObject { Count: > 0 })) { - ok = fp.Concat(r["compileTime"] as JsonObject ?? []).All(kv => Git.ObjectId(Top, kv.Key) == Files.Str(kv.Value, null!)); + var watched = r["watched"] as JsonObject ?? []; + ok = Watched(probe).All(watched.ContainsKey) + && fp.Concat(r["compileTime"] as JsonObject ?? []).Concat(watched).All(kv => Git.ObjectId(Top, kv.Key) == Files.Str(kv.Value, null!)); if (ok) { var now = Toolchain(); @@ -157,20 +232,28 @@ public bool FootprintCurrent(string probe, Dictionary<string, JsonObject> footpr public bool FootprintChangedSince(string probe, string since, Dictionary<string, JsonObject> footprints) { if (!footprints.TryGetValue(probe, out var r)) return true; - var paths = (r["footprint"] as JsonObject ?? []).Select(kv => kv.Key).Concat((r["compileTime"] as JsonObject ?? []).Select(kv => kv.Key)).ToArray(); + var paths = (r["footprint"] as JsonObject ?? []).Select(kv => kv.Key).Concat((r["compileTime"] as JsonObject ?? []).Select(kv => kv.Key)) + .Concat((r["watched"] as JsonObject ?? []).Select(kv => kv.Key)).Concat(Watched(probe)) + .Distinct(StringComparer.Ordinal).ToArray(); if (paths.Length == 0) return true; return Git.Run(Top, ["diff", "--quiet", since, "HEAD", "--", .. paths]).ExitCode != 0; } public string EvidencePath(string probe) => P(Path.Combine("evidence", probe + ".json")); + /// <summary>A check that earned credit: the obligation it named and the settings it ran under.</summary> + public sealed record Credit(string Obligation, Dictionary<string, string> Applied); + /// <summary> - /// The rows of one probe's evidence that earn credit, as applied settings: only while its identity and - /// product footprint are current, and only rows that were expected, executed and ended as a pass. + /// The checks of one probe's evidence that earn credit, each with its obligation and settings: only while the + /// probe's identity and product footprint are current, and only checks that ended as a pass in rows that were + /// expected and executed. A check's settings are the row's (read outside any check, so every check ran under them) + /// with its own laid over them; a row without checks is one check of the obligation <c>claim</c>, credited when + /// the row passed. /// </summary> - public List<Dictionary<string, string>> CreditedRows(string probe, JsonObject? evidence, Dictionary<string, JsonObject>? footprints) + public List<Credit> CreditedChecks(string probe, JsonObject? evidence, Dictionary<string, JsonObject>? footprints) { - var result = new List<Dictionary<string, string>>(); + var result = new List<Credit>(); if (evidence is null || Files.Str(evidence["identity"]) != Identity(probe)) return result; if (footprints is not null && !FootprintCurrent(probe, footprints)) return result; var expected = new HashSet<int>(); var executed = new HashSet<int>(); @@ -182,29 +265,62 @@ public List<Dictionary<string, string>> CreditedRows(string probe, JsonObject? e foreach (var (key, row) in evidence["rows"] as JsonObject ?? []) { var i = int.Parse(key, System.Globalization.CultureInfo.InvariantCulture); - if (row is JsonObject r && expected.Contains(i) && executed.Contains(i) && Files.Str(r["category"]) == "pass" - && r["applied"] is JsonObject applied && applied.Count > 0) - result.Add(Files.StringMap(applied)); + if (row is not JsonObject r || !expected.Contains(i) || !executed.Contains(i)) continue; + foreach (var check in ChecksOf(r)) + if (check.Category == "pass" && check.Applied.Count > 0) result.Add(new Credit(check.Obligation, check.Applied)); } return result; } - /// <summary>(claim, pair) for every pair current evidence earned. A round's own ledger lines earn nothing.</summary> - public HashSet<(string Claim, Pair Pair)> Covered(Universe universe, string? claimId = null) + /// <summary>One check of an evidence row as the ledger reads it.</summary> + public sealed record CheckRead(string Obligation, string Category, Dictionary<string, string> Applied, string? Baseline); + + /// <summary> + /// The checks of one evidence row: each with its obligation, category and settings (the row's, the check's laid + /// over them). A row without checks, every row of a version-1 file among them, is one check of the implicit + /// obligation <c>claim</c> with the row's category and settings and no baseline. + /// </summary> + public static List<CheckRead> ChecksOf(JsonObject row) + { + var applied = row["applied"] is JsonObject a ? Files.StringMap(a) : new Dictionary<string, string>(StringComparer.Ordinal); + if (row["checks"] is not JsonArray { Count: > 0 } checks) + return [new CheckRead(Claim.ImplicitObligation, Files.Str(row["category"], "violation"), applied, null)]; + var result = new List<CheckRead>(); + foreach (var c in checks.OfType<JsonObject>()) + { + var own = new Dictionary<string, string>(applied, StringComparer.Ordinal); + if (c["applied"] is JsonObject ca) foreach (var (axis, value) in Files.StringMap(ca)) own[axis] = value; + result.Add(new CheckRead(Files.Str(c["obligation"]), Files.Str(c["category"], "violation"), own, c["baseline"] is JsonValue b ? Files.Str(b) : null)); + } + return result; + } + + /// <summary> + /// (claim, obligation, pair) for every pair a current check earned, leaving out the probe <paramref name="except"/> + /// names. Each check's settings alone make its pairs: a pair is never put together from two checks' settings. + /// </summary> + public HashSet<(string Claim, string Obligation, Pair Pair)> Credits(Universe universe, string? claimId = null, string? except = null) { var footprints = Footprints(); - var done = new HashSet<(string, Pair)>(); + var done = new HashSet<(string, string, Pair)>(); foreach (var (name, entry) in Registry()) { var cid = Files.Str(entry["claim"]); - if ((claimId is not null && cid != claimId) || !universe.ById.TryGetValue(cid, out var claim)) continue; + if (name == except || (claimId is not null && cid != claimId) || !universe.ById.TryGetValue(cid, out var claim)) continue; var evidence = Files.ReadJson(EvidencePath(name)) as JsonObject; - foreach (var applied in CreditedRows(name, evidence, footprints)) - foreach (var pair in claim.PairsOfRow(applied)) done.Add((cid, pair)); + foreach (var credit in CreditedChecks(name, evidence, footprints)) + foreach (var pair in claim.PairsOfRow(credit.Applied)) done.Add((cid, credit.Obligation, pair)); } return done; } + /// <summary> + /// (claim, pair) for every pair current evidence earned, leaving out the probe <paramref name="except"/> names. A round's + /// own ledger lines earn nothing. + /// </summary> + public HashSet<(string Claim, Pair Pair)> Covered(Universe universe, string? claimId = null, string? except = null) => + [.. Credits(universe, claimId, except).Select(x => (x.Claim, x.Pair))]; + /// <summary>The last event per finding; a fixed line counts once its commit is in HEAD's history, a dismissal in the corpus everywhere.</summary> public Dictionary<string, JsonObject> Findings() { @@ -223,10 +339,73 @@ public Dictionary<string, JsonObject> Findings() return last; } + /// <summary>The findings one probe's evidence steps around, with the number of rows that skip for each.</summary> + public static SortedDictionary<string, int> FindingSkips(JsonObject? evidence) + { + var skips = new SortedDictionary<string, int>(StringComparer.Ordinal); + foreach (var (_, row) in evidence?["rows"] as JsonObject ?? []) + foreach (var f in Files.Strings(row?["findings"])) skips[f] = skips.GetValueOrDefault(f) + 1; + return skips; + } + + /// <summary> + /// The skips of one probe's evidence that no longer stand: a finding this ledger has fixed (in HEAD's history) + /// or the corpus has dismissed. Such a skip would hide the defect coming back, so the replay fails the probe. + /// A finding this ledger has never seen (a fresh state, as in CI) is left to the gate, which refuses any skip. + /// </summary> + public List<string> StaleSkips(JsonObject? evidence) + { + var findings = Findings(); + var stale = new List<string>(); + foreach (var (id, rows) in FindingSkips(evidence)) + { + if (!findings.TryGetValue(id, out var f)) continue; + var status = Files.Str(f["status"]); + if (status == "fixed" && Files.Str(f["commit"]) is { Length: >= 7 } commit) status += " at " + commit[..7]; + if (status != "open") stale.Add($"{rows} row(s) skip for {id}, which is {status}: remove the skip"); + } + return stale; + } + + /// <summary>How the rows of one probe's evidence ended, by category.</summary> + public static SortedDictionary<string, int> Outcomes(JsonObject? evidence) + { + var outcomes = new SortedDictionary<string, int>(StringComparer.Ordinal); + foreach (var (_, row) in evidence?["rows"] as JsonObject ?? []) + { + var category = Files.Str(row?["category"], "violation"); + outcomes[category] = outcomes.GetValueOrDefault(category) + 1; + } + return outcomes; + } + + /// <summary>How the checks of one probe's evidence ended, by category; a row without checks counts as its one check <c>claim</c>.</summary> + public static SortedDictionary<string, int> CheckOutcomes(JsonObject? evidence) + { + var outcomes = new SortedDictionary<string, int>(StringComparer.Ordinal); + foreach (var (_, row) in evidence?["rows"] as JsonObject ?? []) + if (row is JsonObject r) + foreach (var check in ChecksOf(r)) outcomes[check.Category] = outcomes.GetValueOrDefault(check.Category) + 1; + return outcomes; + } + + /// <summary>The obligations a check of one probe's evidence passed for at its last replay, current or not.</summary> + public static HashSet<string> PassedObligations(JsonObject? evidence) + { + var passed = new HashSet<string>(StringComparer.Ordinal); + foreach (var (_, row) in evidence?["rows"] as JsonObject ?? []) + if (row is JsonObject r) + foreach (var check in ChecksOf(r).Where(c => c.Category == "pass")) passed.Add(check.Obligation); + return passed; + } + public List<string> OpenFindings() => [.. Findings().Where(kv => Files.Str(kv.Value["status"]) == "open").Select(kv => kv.Key).Order(StringComparer.Ordinal)]; public sealed record ProbeState(string Probe, string Claim, string Filter, JsonArray Variants, bool Stale, bool Retired, int Passes, bool Replay); + /// <summary>A registry entry's process variants: its own, or one that sets nothing.</summary> + public static JsonArray VariantsOf(JsonObject entry) => entry["variants"] is JsonArray va && va.Count > 0 ? (JsonArray)va.DeepClone() : [new JsonObject()]; + public List<ProbeState> ProbeStates(bool includeRetired) { var footprints = Footprints(); @@ -238,7 +417,7 @@ public List<ProbeState> ProbeStates(bool includeRetired) var retired = r["retired"] is JsonValue rv && rv.TryGetValue<bool>(out var b) && b; var evidence = Files.ReadJson(EvidencePath(probe)); var stale = !FootprintCurrent(probe, footprints) || evidence is null || Files.Str(evidence["identity"]) != Identity(probe); - var variants = r["variants"] is JsonArray va && va.Count > 0 ? (JsonArray)va.DeepClone() : [new JsonObject()]; + var variants = VariantsOf(r); states.Add(new ProbeState(probe, Files.Str(r["claim"]), Files.Str(r["filter"]), variants, stale, retired, passes, stale && (includeRetired || !retired))); } return states; diff --git a/tools/Prober/NewProbe.cs b/tools/Prober/NewProbe.cs new file mode 100644 index 00000000..97047dcd --- /dev/null +++ b/tools/Prober/NewProbe.cs @@ -0,0 +1,166 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace Prober; + +/// <summary> +/// <c>new</c>: the mechanical half of writing a probe, done the same way every time, into the branch's staging. +/// It writes the probe's skeleton in the harness's form (<c>probes/P.cs</c>), headed by the claim's source and +/// every obligation's id and sentence, with one check per obligation that throws until it is written; stages the +/// probe's own rows (a covering array over its claim's pairs the claim's other probes have not covered, as +/// <c>rows --probe P</c> writes); +/// and registers the probe with an empty <c>seeds</c>, so <c>seeds</c> and <c>Seeds.Problems</c> name each +/// obligation's seed as owed. The skeleton builds, so the other probes keep building beside it, and every row of +/// it ends as a harness error, which fails the row, earns nothing and is never a seed's catch (decision 103). +/// It refuses, writing nothing, a claim that is not in the claims, a name a probe has already, and a claim +/// whose first surface is in no family of the replay configuration (or not in the one <c>--family</c> names). +/// </summary> +internal static partial class NewProbe +{ + [GeneratedRegex("^[A-Za-z_][A-Za-z0-9_]*Probe$")] + private static partial Regex ProbeName(); + + [GeneratedRegex("^([A-Za-z]+[0-9]+)(?:-(.*))?$")] + private static partial Regex ClaimPrefix(); + + [GeneratedRegex("[^A-Za-z0-9]+")] + private static partial Regex NotAlphanumeric(); + + public static int Run(Ledger l, Args a) + { + var claimId = a.Need("claim"); + var claim = l.Claims().FirstOrDefault(c => Files.Str(c["id"]) == claimId) + ?? throw new Refusal($"no claim {claimId} in claims.json (tracked or staged)"); + var probe = a.Get("probe") is { Length: > 0 } named ? named : DefaultName(claimId); + if (!ProbeName().IsMatch(probe)) throw new Refusal($"{probe} is not a probe's name: a C# class name ending in Probe", 2); + if (Taken(l, probe) is { } where) throw new Refusal($"a probe {probe} exists already ({where}); name another with --probe"); + var family = Family(l, claim, a.Get("family")); + var obligations = Obligations(claim); + + var source = Path.Combine(l.StagingDir, "probes", probe + ".cs"); + Files.WriteAtomically(source, Skeleton(claim, probe, family, obligations)); + // The probe's own rows, its claim's other probes' untouched: a covering array over the pairs they have not covered. + var (rows, _) = Commands.StageRows(l, probe, claimId, full: false); + var registry = Path.Combine(l.StagingDir, "registry.json"); + var staged = Files.ReadJson(registry) as JsonObject ?? new JsonObject { ["probes"] = new JsonObject() }; + var probes = staged["probes"] as JsonObject ?? []; + probes[probe] = new JsonObject { ["claim"] = claimId, ["filter"] = "*" + probe, ["seeds"] = new JsonArray() }; + staged["probes"] = probes.DeepClone(); + Files.WriteJson(registry, staged); + + var o = l.Ctx.Out; + o.WriteLine($"prober new: {probe} for claim {claimId}{(family is null ? "" : $" (family {family})")}, staged in {l.StagingDir}"); + o.WriteLine($" probes/{probe}.cs: the skeleton; it builds, and every row ends as a harness error until each obligation's check is written"); + o.WriteLine($" rows/{probe}.json: {rows} rows over the claim's pairs its other probes have not covered" + + (rows == 0 ? "; with no row the probe has no test to run" : "")); + o.WriteLine($" registry.json: {probe}, claim {claimId}, filter *{probe}, no seed yet"); + o.WriteLine("still owed, per obligation:"); + foreach (var (id, sentence, method) in obligations) + o.WriteLine($" {id}: the check {method} ({(sentence is null ? "the claim as a whole" : $"\"{sentence}\"")}), and a seed that breaks it " + + (id == Claim.ImplicitObligation && sentence is null + ? $"(`prober register --probe {probe} --claim {claimId} --filter \"*{probe}\" --seed seeds/{claimId}.patch --defect \"...\"`)" + : $"(`prober register --probe {probe} --claim {claimId} --filter \"*{probe}\" --obligation {id} --seed seeds/{claimId}-{id}.patch --defect \"...\"`)")); + o.WriteLine($"then: `prober try --probe {probe}` until each row ends as it should, `prober seeds --probe {probe}`, `prober register` (it lints the staged sources), `prober publish`"); + return 0; + } + + /// <summary>The claim id's letter-digit prefix, then its words run together, then <c>Probe</c>: C20-testing-shouldhold is C20TestingShouldholdProbe.</summary> + internal static string DefaultName(string claimId) + { + var m = ClaimPrefix().Match(claimId); + var (prefix, rest) = m.Success ? (m.Groups[1].Value, m.Groups[2].Value) : ("", claimId); + return Pascal(prefix) + Pascal(rest) + "Probe"; + } + + private static string Pascal(string text) => + string.Concat(NotAlphanumeric().Split(text).Where(w => w.Length > 0).Select(w => char.ToUpperInvariant(w[0]) + w[1..])); + + /// <summary>Where a probe of that name is already: the registry, a source file of that name, or a class of that name in any corpus source.</summary> + private static string? Taken(Ledger l, string probe) + { + if (l.Registry().ContainsKey(probe)) return "in the registry"; + var declares = new Regex($@"\b(class|record|struct)\s+{probe}\b"); + foreach (var dir in new[] { l.CorpusDir, l.StagingDir }) + foreach (var sub in new[] { "harness", "probes" }) + { + var folder = Path.Combine(dir, sub); + if (!Directory.Exists(folder)) continue; + foreach (var file in Directory.EnumerateFiles(folder, "*.cs")) + if (Path.GetFileNameWithoutExtension(file) == probe || declares.IsMatch(File.ReadAllText(file))) return file; + } + return null; + } + + /// <summary> + /// The family the replay runs the probe in, its claim's first surface's (decision 101), checked against + /// <c>--family</c>. Null where the replay configuration names no families and none was asked for. + /// </summary> + private static string? Family(Ledger l, JsonObject claim, string? asked) + { + var families = (l.AxesBlock()["replay"] as JsonObject)?["families"] as JsonObject; + if (families is null) + return asked is null ? null : throw new Refusal($"--family {asked}: the replay configuration names no families", 2); + var surface = Files.Strings(claim["surfaces"]).FirstOrDefault() ?? ""; + var family = families.FirstOrDefault(kv => Files.Strings(kv.Value).Contains(surface)).Key + ?? throw new Refusal($"the first surface of claim {Files.Str(claim["id"])} ({surface}) is in no family of the replay configuration, so a replay by family would never run its probe"); + if (asked is not null && asked != family) + throw new Refusal($"claim {Files.Str(claim["id"])} is of family {family} (its first surface, {surface}), not {asked}", 2); + return family; + } + + /// <summary>Each obligation of the claim with its sentence and the check method the skeleton gives it; a claim without obligations has one, the claim itself, with no sentence.</summary> + internal static List<(string Id, string? Sentence, string Method)> Obligations(JsonObject claim) + { + var listed = (claim["obligations"] as JsonArray ?? []).OfType<JsonObject>() + .Select(o => (Id: Files.Str(o["id"]), Sentence: (string?)OneLine(Files.Str(o["sentence"])))).ToList(); + if (listed.Count == 0) listed = [(Claim.ImplicitObligation, null)]; + var methods = new HashSet<string>(StringComparer.Ordinal); + var result = new List<(string, string?, string)>(); + foreach (var (id, sentence) in listed) + { + var method = "Check" + Pascal(id); + for (var n = 2; !methods.Add(method); n++) method = "Check" + Pascal(id) + n; + result.Add((id, sentence, method)); + } + return result; + } + + /// <summary>The probe's source: the form of the corpus's probes (<c>Rows.Data</c>, <c>[Theory, MemberData(nameof(Data))]</c>, <c>Rec.Run</c>), one throwing check per obligation.</summary> + internal static string Skeleton(JsonObject claim, string probe, string? family, List<(string Id, string? Sentence, string Method)> obligations) + { + var id = Files.Str(claim["id"]); + var literal = JsonSerializer.Serialize(probe); + var b = new StringBuilder(); + void Line(string text = "") => b.Append(text).Append('\n'); + Line($"// Claim {id} ({Files.Str(claim["source"])}), surfaces {string.Join(", ", Files.Strings(claim["surfaces"]))}{(family is null ? "" : $", family {family}")}."); + Line("// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104):"); + foreach (var (oid, sentence, _) in obligations) + Line($"// {oid}: {(sentence is null ? "the claim as a whole (claims.json lists no obligations for it)" : $"\"{sentence}\"")}"); + Line("// Oracle: TODO: what each row is compared against, and the result it expects."); + Line("// Written by `prober new`: it builds, and every row ends as a harness error until each obligation's check is written."); + Line("using Probes.Shapes;"); + Line("namespace Probes;"); + Line($"public sealed class {probe}"); + Line("{"); + Line($" public static TheoryData<string, int> Data() => Rows.Data({literal});"); + Line(" [Theory, MemberData(nameof(Data))]"); + Line(" public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check);"); + Line(); + Line(" static async Task<string?> Check(Row r) =>"); + for (var i = 0; i < obligations.Count; i++) + Line($" {(i == 0 ? "" : "?? ")}await {obligations[i].Method}(r){(i == obligations.Count - 1 ? ";" : "")}"); + foreach (var (oid, sentence, method) in obligations) + { + Line(); + Line($" // TODO {oid}: {(sentence is null ? "the claim as a whole" : $"\"{sentence}\"")}"); + Line(" // Return the failure text, or null where the row holds it; say how it was compared with r.Note(NoteKeys.Compared, detail)."); + Line($" static Task<string?> {method}(Row r) => throw new NotImplementedException({JsonSerializer.Serialize($"{id}/{oid}: its check is not written yet")});"); + } + Line("}"); + return b.ToString(); + } + + private static string OneLine(string text) => string.Join(" ", text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)); +} diff --git a/tools/Prober/ProbeAdapter.cs b/tools/Prober/ProbeAdapter.cs index 2e9b4f33..fc993e87 100644 --- a/tools/Prober/ProbeAdapter.cs +++ b/tools/Prober/ProbeAdapter.cs @@ -63,8 +63,23 @@ public Ran Build(string worktree, IReadOnlyDictionary<string, string?> environme return Start(worktree, Expand(config["build"], new() { ["project"] = Project }), environment); } - public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) => - Start(worktree, Expand(config["test"], new() { ["project"] = Project, ["filter"] = filter, ["results"] = results, ["cobertura"] = coverageFile }), environment); + /// <summary> + /// The "test" template, then the "coverage" arguments when a coverage file is asked for. A replay without a + /// footprint asks for none, so a template that carries the coverage arguments itself is refused rather than + /// run with an empty file name. + /// </summary> + public Ran RunProbe(string worktree, string filter, string results, string coverageFile, IReadOnlyDictionary<string, string?> environment) + { + var values = new Dictionary<string, string> { ["project"] = Project, ["filter"] = filter, ["results"] = results, ["cobertura"] = coverageFile }; + if (coverageFile.Length == 0 && Files.Strings(config["test"]).Any(arg => arg.Contains("{cobertura}", StringComparison.Ordinal))) + throw new Refusal("the replay's test template carries the coverage arguments itself; move them to a \"coverage\" array of the replay configuration, so a run without a footprint can leave them out"); + string[] argv = coverageFile.Length > 0 ? [.. Expand(config["test"], values), .. Expand(config["coverage"], values)] : Expand(config["test"], values); + // The template runs the verdict tool, which keeps its full log where BUILD_VERDICT_LOG_DIR says: the + // directory the replay offers as PROBE_LOG_DIR, whose files it keeps for a failed run. + if (environment.TryGetValue("PROBE_LOG_DIR", out var logDir) && logDir is { Length: > 0 }) + environment = new Dictionary<string, string?>(environment, StringComparer.Ordinal) { ["BUILD_VERDICT_LOG_DIR"] = logDir }; + return Start(worktree, argv, environment); + } public IReadOnlyDictionary<string, string?> Environment(string cacheDirectory) { diff --git a/tools/Prober/ProbeRun.cs b/tools/Prober/ProbeRun.cs new file mode 100644 index 00000000..484fc507 --- /dev/null +++ b/tools/Prober/ProbeRun.cs @@ -0,0 +1,107 @@ +using System.Text.Json.Nodes; + +namespace Prober; + +/// <summary> +/// What <c>replay</c>, <c>seeds</c> and <c>try</c> share to run a probe: the worktree moved to HEAD, the +/// environment every build and run gets, one run of a probe in one process, and how each row of it ended, read +/// from the jsonl the harness writes under <c>PROBE_OUT</c>. +/// </summary> +internal static class ProbeRun +{ + /// <summary> + /// A row's outcome under one run: its category, the first reason given, whether any reason of the row or of a check + /// of it was a harness error, and its checks. A row that ran no check is one check of the obligation <c>claim</c>. + /// </summary> + internal sealed record RowOutcome(string Category, string Reason, bool HarnessError, IReadOnlyList<CheckOutcome> Checks); + + /// <summary> + /// One check of a row under one run: its obligation, category and first reason, and whether a harness error broke it + /// or its row outside any check. Only a clean violation, one with no harness error, says the check fired (decision 103). + /// </summary> + internal sealed record CheckOutcome(string Obligation, string Category, string Reason, bool HarnessError) + { + public bool CleanViolation => Category == "violation" && !HarnessError; + } + + /// <summary> + /// Adds the worktree at <paramref name="head"/> ("cold"), or moves it there without force ("warm"): files the + /// commits did not change keep their mtimes, so the build stays incremental. + /// </summary> + public static string Ready(Ledger ledger, string worktree, string head) + { + if (!Directory.Exists(worktree)) + { + var added = Git.Run(ledger.Root, "worktree", "add", "--detach", worktree, head); + if (added.ExitCode != 0) throw new Refusal("could not add the worktree: " + added.Stderr.Trim(), 2); + return "cold"; + } + if (Git.Run(worktree, "checkout", "--detach", head).ExitCode != 0) Git.Run(worktree, "reset", "--hard", head); + return "warm"; + } + + /// <summary>The process's environment with what the adapter adds for every build and run of the probes.</summary> + public static Dictionary<string, string?> Environment(Context ctx, IProbeAdapter adapter, string home) + { + var environment = new Dictionary<string, string?>(ctx.Environment, StringComparer.Ordinal); + foreach (var (k, v) in adapter.Environment(home)) environment[k] = v; + return environment; + } + + /// <summary>The environment of one variant: the base, then the variant's own settings, then the extra ones (a seed's toggle).</summary> + public static Dictionary<string, string?> Variant(IReadOnlyDictionary<string, string?> environment, JsonObject variant, JsonObject? extra = null) + { + var env = new Dictionary<string, string?>(environment, StringComparer.Ordinal); + foreach (var (k, v) in variant) env[k] = Files.Str(v); + foreach (var (k, v) in extra ?? []) env[k] = Files.Str(v); + return env; + } + + /// <summary> + /// Runs the probe the filter selects once, with its rows written to <c>{results}/{name}-rows</c>, and reads how + /// each row ended. <paramref name="coverageFile"/> empty asks for no coverage. + /// </summary> + public static (Ran Ran, Dictionary<int, RowOutcome> Rows) Once(IProbeAdapter adapter, string worktree, string filter, string results, string name, + string coverageFile, Dictionary<string, string?> env) + { + var outDir = Path.Combine(results, name + "-rows"); + env["PROBE_OUT"] = outDir; + var ran = adapter.RunProbe(worktree, filter, results, coverageFile, env); + return (ran, Outcomes(outDir)); + } + + /// <summary>How each row ended, from every jsonl under <paramref name="outDir"/>; a line without a category is a violation.</summary> + public static Dictionary<int, RowOutcome> Outcomes(string outDir) + { + var rows = new Dictionary<int, RowOutcome>(); + if (!Directory.Exists(outDir)) return rows; + foreach (var file in Directory.EnumerateFiles(outDir, "*.jsonl")) + foreach (var line in Files.ReadLines(file)) + { + var category = Files.Str(line["category"], "violation"); + var reasons = Files.Strings(line["reasons"]).ToList(); + var reason = reasons.FirstOrDefault(r => r.StartsWith(category + ":", StringComparison.Ordinal)) ?? Files.Str(line["note"]); + static bool Broken(IEnumerable<string> reasons) => reasons.Any(r => r.StartsWith("harness-error:", StringComparison.Ordinal)); + var checks = new List<CheckOutcome>(); + if (line["checks"] is JsonArray { Count: > 0 } ran) + { + // The row's own harness error (outside any check) breaks every check of it; a check's breaks that check. + var rowBroken = Broken(reasons); + foreach (var c in ran.OfType<JsonObject>()) + { + var cc = Files.Str(c["category"], "violation"); + var cr = Files.Strings(c["reasons"]).ToList(); + checks.Add(new CheckOutcome(Files.Str(c["obligation"]), cc, cr.FirstOrDefault(r => r.StartsWith(cc + ":", StringComparison.Ordinal)) ?? Files.Str(c["notes"]), + rowBroken || cc == "harness-error" || Broken(cr))); + } + } + else checks.Add(new CheckOutcome(Claim.ImplicitObligation, category, reason, category == "harness-error" || Broken(reasons))); + var harnessError = Broken(reasons) || checks.Any(c => c.HarnessError); + rows[(int)Files.Num(line["idx"])] = new RowOutcome(category, reason, harnessError, checks); + } + return rows; + } + + /// <summary>The first line of a run's output (the verdict's), or its exit code where it printed nothing.</summary> + public static string FirstLine(Ran ran) => ran.Stdout.Split('\n')[0].Trim() is { Length: > 0 } line ? line : $"exit {ran.ExitCode}"; +} diff --git a/tools/Prober/Prober.csproj b/tools/Prober/Prober.csproj index bf90334c..ba12505a 100644 --- a/tools/Prober/Prober.csproj +++ b/tools/Prober/Prober.csproj @@ -24,6 +24,8 @@ <ItemGroup> <InternalsVisibleTo Include="CodoMetis.TypeKit.Prober.Tests" /> <None Include="Schemas/*.json" CopyToOutputDirectory="PreserveNewest" /> + <!-- The schemas SchemaCheck reads where a file is read (decision 102), by file name. --> + <EmbeddedResource Include="Schemas/claims.schema.json" LogicalName="%(Filename)%(Extension)" /> </ItemGroup> <!-- diff --git a/tools/Prober/Program.cs b/tools/Prober/Program.cs index a77e6695..7b62b3ae 100644 --- a/tools/Prober/Program.cs +++ b/tools/Prober/Program.cs @@ -10,27 +10,57 @@ internal static class Program universe [--base REF] each claim's axes, from the claims, the axes file and the generated axes rows --claim C [--full] a pairwise covering array over the claim's pairs not yet covered; inside a capped round it refuses past the deadline or the claim cap - status [--json] coverage from current evidence, open findings, done or not + rows --probe P [--full] writes P's rows file whole into the staging: a covering array over its + claim's pairs less those the claim's other probes have credited + status [--json] coverage from current evidence, open findings, done or not; per claim + its obligations and which have a seed + gaps [--base REF] what the corpus leaves out, each with its reason: unassigned settings, + refused shapes and values, what claims do not promise, out of reach, and + the wanted pairs no passing check of the last replay read both axes of gate [--since REF] [--json] the release gate (needs only the tracked corpus and a replay) stale [--json] [--include-retired] the probes a replay would run identity --probe P what a result of P is bound to record-run --probe P --result pass|fail [--evidence F] [--seconds S] footprint --probe P --cobertura F... --root WT [--include src] [--compile-time PATH...] - register --probe P --claim C --filter F [--variants JSON] + register --probe P --claim C --filter F [--variants JSON] [--seed PATCH] [--seed-env NAME=VALUE...] [--defect TEXT] + [--obligation O]: the seed (a patch, a toggle or both) into `seed`, or with + --obligation into `seeds`, in place of a seed naming O; refuses an O that + claim C does not list publish | dismiss --id F --reason TEXT | mark retired --probe P + fix F --commit SHA [--reason TEXT] records open finding F fixed at the commit (findings.jsonl); refuses + an id this ledger does not hold open and a commit the repository lacks lock --holder H --mode replay|explore|full [--pid N] | unlock --holder H | heartbeat --holder H round-start --holder H --minutes M [--claims N] | round-end --holder H | repair | abandon + merge --from DIR... joins the states of a replay split by family (one DIR per leg) into + this one; refuses while a registered probe was in no leg + + Writing a probe (into the branch's staging): + new --claim C [--probe P] [--family F] stages P's skeleton (the claim's source and obligations quoted, one + check per obligation that throws until written), the claim's rows and P's + registry entry with no seed yet; P defaults to the claim's prefix and words + (C20-testing-shouldhold: C20TestingShouldholdProbe); refuses a claim not in + the claims, a name taken, a family that is not the claim's + try --probe P [--rows i,j,...] [--no-build] builds once in the replay's worktree and runs P alone, every + variant or the rows given, printing how each row ended; records nothing, + holds the worktree from a replay, exits with the probe's exit code Running probes: - replay [--all] [--holder H] [--dry-run] [--where] replays the corpus with no model + replay [--all] [--family F] [--parallel N] [--no-footprint] [--holder H] [--dry-run] [--where] + replays the corpus with no model: the stale probes, or all; one family + of the replay configuration; N probe processes at a time (default: + the cores); without coverage or footprint, for a state nobody keeps round explore|full [--minutes M] [--claims N] [--usd X] a capped exploration round (local only) + seeds [--all] [--probe P] [--dry-run] applies each probe's seeded defect in a scratch worktree, replays the + probe there and requires a violation it causes (local only, a build per + seed): the probes whose identity or footprint changed since every seed of + theirs was caught, or all of them, as a release runs it State goes under PROBE_STATE_DIR when set (CI), else the main checkout's prober memory. - Exit codes: 0 done, 1 refused or failed, 2 usage, 3 this dll is stale. + Exit codes: 0 done, 1 refused or failed, 2 usage, 3 this dll is stale; `try` exits with the probe's own. """; private static readonly HashSet<string> Flags = new(StringComparer.Ordinal) - { "json", "full", "all", "include-retired", "dry-run", "where" }; + { "json", "full", "all", "include-retired", "dry-run", "where", "no-footprint", "no-build" }; private static int Main(string[] args) { @@ -54,6 +84,8 @@ public static int Run(Context ctx, string[] args) var a = new Args(args[1..], Flags); if (command == "replay") return Replay.Run(ctx, a); if (command == "round") return Round.Run(ctx, a); + if (command == "seeds") return Seeds.Run(ctx, a); + if (command == "try") return TryProbe.Run(ctx, a); var ledger = new Ledger(ctx, a.Get("branch")); // Commands run under a round keep its lock alive. if (command is not ("lock" or "unlock" or "abandon" or "heartbeat") && ctx.Env("PROBE_HOLDER") is { } holder) @@ -68,9 +100,11 @@ public static int Run(Context ctx, string[] args) "identity" => Commands.IdentityCommand(ledger, a), "record-run" => Commands.RecordRun(ledger, a), "footprint" => Commands.Footprint(ledger, a), + "new" => NewProbe.Run(ledger, a), "register" => Commands.Register(ledger, a), "publish" => Commands.Publish(ledger, a), "dismiss" => Commands.Dismiss(ledger, a), + "fix" => Commands.Fix(ledger, a), "mark" => Commands.Mark(ledger, a), "lock" => Commands.Lock(ledger, a), "unlock" => Commands.Unlock(ledger, a), @@ -80,6 +114,8 @@ public static int Run(Context ctx, string[] args) "round-end" => Commands.RoundEnd(ledger, a), "repair" => Commands.Repair(ledger, a), "abandon" => Commands.Abandon(ledger, a), + "merge" => Commands.Merge(ledger, a), + "gaps" => Commands.Gaps(ledger, a), _ => throw new Refusal($"unknown command {command}; see `prober help`", 2), }; } diff --git a/tools/Prober/README.md b/tools/Prober/README.md index e440aa6f..3abf10f2 100644 --- a/tools/Prober/README.md +++ b/tools/Prober/README.md @@ -12,10 +12,16 @@ dotnet tools/Prober/bin/Release/net10.0/prober.dll help | Command | Model | Where | |---|---|---| -| `replay [--all]` | none | anywhere, CI included: the probes whose identity or footprint changed, or all of them | +| `replay [--all] [--family F] [--parallel N] [--no-footprint]` | none | anywhere, CI included: the probes whose identity or footprint changed, or all of them; one family of the replay configuration; N probe processes at a time (the cores by default); without coverage or a footprint, for a state nobody keeps (decision 101) | +| `merge --from DIR...` | none | anywhere, CI included: joins the states of a replay split by family, and refuses while a registered probe was in no leg | | `gate [--since REF]` | none | anywhere, CI included: needs only the tracked corpus and a replay | -| `status`, `stale`, `universe`, `rows`, … | none | anywhere | +| `status`, `stale`, `universe`, `rows [--claim C \| --probe P]`, … | none | anywhere | +| `gaps [--base REF]` | none | anywhere: what the corpus leaves out, each with its recorded reason, and, from the last replay's evidence, each wanted pair no obligation wanting it has a passing check that read both its axes of (more rows cannot cover it); a report that decides nothing (decision 104) | +| `fix F --commit SHA [--reason TEXT]` | none | anywhere: records an open finding fixed at a commit, the fixed line of the state's `findings.jsonl` as `finding-line.schema.json` wants it; it counts once the commit is in HEAD's history | +| `new --claim C [--probe P] [--family F]` | none | anywhere: stages a probe's skeleton, its own rows and its registry entry with no seed yet, and prints what the writer still owes | +| `try --probe P [--rows i,j,...] [--no-build]` | none | locally: builds once in the replay's worktree and runs that one probe, every row or the rows given; records nothing, and holds the worktree from a replay while it runs | | `round explore\|full` | the `prober` agent | locally only, under caps it cannot overrun | +| `seeds [--all] [--probe P]` | none | locally only: one build per seed, in a scratch worktree (decision 100); the probes whose seeds identity or product footprint changed since every seed was caught, or all of them, as a release runs it (decision 107) | No command in the gate's path needs a login, an API key or a Claude executable. A gate obligation is met by a probe in the corpus, written by hand or by a round, or by a reviewed waiver in @@ -26,12 +32,141 @@ by a probe in the corpus, written by hand or by a round, or by a reviewed waiver The corpus is tracked under `tools/probe/corpus/`: claims, the probe registry, harness and probe sources, rows (gzipped), waivers and dismissed findings. State goes under `PROBE_STATE_DIR` when set (CI sets it), else the main checkout's `.claude/agent-memory-local/prober/`. Every file's shape is in `Schemas/`, versioned; -a file or line without `schemaVersion` is version 1. +a file or line without `schemaVersion` is version 1. `claims.json` is checked against its schema wherever it is +read, and a field the schema does not have is refused with the claim and the field named; each `exclude` entry +is a reason and its pairs, which `universe` prints beside the claim's narrowings. The JSON files the prober +writes keep `+`, backticks and `<` literal, so a `publish` that changes nothing in a tracked file leaves it as +it is (decision 102). The harness's generated declarations, `harness/*.g.cs`, are written by the file-based +apps of `tools/probe/generators/` from the rows and the axes file, and `GeneratedHarnessTests` in the +conventions tests holds each to its app's output (that directory's README). -A pair of a claim's axes counts as covered only by evidence a replay recorded: a row that was expected, -executed and ended as a pass, for the settings that actually applied, while the result's identity holds +Rows are per probe: `rows/<probe>.json.gz`, one file for each registered probe, its claim inside (`claim`), so +two probes of one claim (C07's `BaseProbe` and `C07BlanketMappingProbe`) each run rows of their own, and a +probe's identity reads its own file only: a sibling's rows changing leaves it as it was. The probe's +`Rows.Data("P")` names the file, and the replay, `try` and the identity read it. `rows --probe P` writes P's file +whole into the staging, never appended: a covering array over its claim's pairs less those the claim's other +probes have credited (P's own credit is not subtracted, since it came from the rows the file replaces), and +`publish` moves it into the corpus by probe; `rows --claim C` only prints such an array. The stored rows are +definitions, the evidence is in the state. `ProbeRowsTests` in the conventions tests refuses two rows of one +file with the same axis map, a file whose `claim` is not in `claims.json` or is not its probe's, a file that +names no registered probe, and a registered probe without a file. + +A registry entry may list `watched`: paths, files or directories, that the probe's result depends on and its +footprint cannot see, since the footprint is what ran in the probe's own process under `src/` beside the +`compileTimeFootprint` paths. A variant catalogue under `test/` compiled into the probes is one (C19 builds +over the analyzer tests' `TypeArgumentSites` and `TypeArgumentKinds`), a build input a child process reads is +another (C18's consumer build compiles against the base package and evaluates `src/Directory.Build.*`). The +replay records each with its object id in the footprint line, a change to one stales the footprint like a path +the probe ran, and the gate counts it as touching the probe's claim. A probe whose own process runs nothing under `src/` +(C22 builds its consumer in a child process) is recorded by what its entry watches alone; where nothing ran and nothing +is watched, `footprint` refuses to record an empty footprint, which would never go stale. + +The round lock, `round.lock` and its mutex, sits in the branch's directory (`branches/<branch>/`), beside +everything a round or a replay writes, so rounds and replays of two branches run at once and two of one branch +never do. The scratch worktrees, `wt` (the replay's), `seeds`, `explore` (the `prober` agent's) and `enumerate`, +are per checkout: under `PROBE_CACHE_DIR` when set, else `~/.cache/probe-replay/<checkout>-<hash of its path>`, +and `replay --where` prints them. So a linked worktree (`git worktree add`, as each session under +`.claude/worktrees/` is) builds a warm copy of its own, about a gigabyte of disk, and two sessions never reset +each other's copy; the main checkout keeps the directory it had before. Since the lock is per branch and the +worktree per checkout, a replay also holds `replay.lock` beside them (an OS lock, gone with its process), and a +replay of another branch in the same checkout, after a branch switch, is skipped while one runs. Removing a +linked worktree leaves its cache behind: before you remove it, run there the `remove:` line that `replay --where` +prints, which removes the cache's worktrees (`git worktree remove`) and then its directory, every path written out. + +A claim lists what it promises in `obligations`, each an id and a sentence quoted verbatim from the claim's +source, and what it does not promise in `not-promised`, each an id and the sentence of the document or record +that makes the difference intended, quoted from that source. `universe` checks every quote as it checks a +narrowing's and refuses a sentence its document does not hold, or an id listed twice. A claim without +`obligations` has one, the claim itself, under the id `claim`. `status` shows per claim its obligations and the +ones no seed names, and `gaps` prints every not-promised entry and narrowing beside the settings no surface +reaches, the refused shapes and values, and the axes file's `out-of-reach` surfaces (decision 104). + +An obligation may declare where it does not apply: `inapplicable`, each entry the claim's own axis values +(`axes`) and a citation (`cite`: `not-promised:id` of the claim's list, `docs:file#anchor`, `fact:KB-NNN` or +`record:NNNN`). A row holding every axis of a declaration at one of its values runs no check of that +obligation: `Row.Check` records the check inapplicable with the cite before its body runs, for that check only. +The replay stages the claims beside the rows (`claims.json` in the probe directory) for the harness's +`Rows.Claim`, and a probe's identity takes its claim's declarations. A pair a declaration matches is not wanted +by that obligation (`Claim.PairsWantedBy`); a pair no obligation wants is declared inapplicable: `universe` +prints each claim's count with the citations, `gaps` lists them under a heading of their own, `status --json` +gives `inapplicable_pairs` per claim, the covering generator never puts one in a row, and the gate leaves them +out as it leaves out an exclusion. A claim's pairs stay its pairs. `universe` refuses a declaration whose axis +or value the claim does not list, whose cite is malformed, or whose not-promised id the claim lacks. + +A pair of a claim's axes counts as covered only by evidence a replay recorded: a check that ended as a pass, in a +row that was expected and executed, for the settings that actually applied, while the result's identity holds (the probe, every compiled corpus source, the rows, the axes file, the build environment, this tool's -version and the schema version) and the product files the probe ran are unchanged. +version and the schema version) and the product files the probe ran are unchanged. A row's checks +(`Row.Check` in the harness) each name an obligation and a baseline and record the axes they read; a check is +credited with those and the row's own (read outside any check), and a pair is never put together from two +checks' axes. A row without checks is one check of the obligation `claim` with the row's category. Evidence is +version 2 (`Schemas/evidence.schema.json`): the replay copies each row's `checks` beside its `applied` and +`reasons`. `status --json` gives each obligation `current`, the pairs current checks naming it earned, and +`passed`; the text line counts rows and checks by outcome and ends with the obligations no passing check named +at the last replay (`K uncredited`). + +## Seeds + +Each probe's registry entry names a seeded product defect: a patch under `seeds/` against the product +files (the replay's `include`), an environment toggle, or both, and the sentence `defect`. Where its claim +lists obligations, the entry's `seeds` holds one such seed per obligation the probe checks, each naming it in +`obligation`, and every listed obligation of a claim with probes has a seed in one of them (decision 104). +`seeds` applies each seed on its own in the `seeds` worktree beside the replay's, builds, runs the probe +and counts the seed caught only for a check that ends as a violation, with no harness error in it or in its +row outside its checks (decision 103), in a row whose unseeded run had no violation of the same obligation; a +seed naming an obligation is caught only by a check naming it, so a probe whose rows run no checks (each the +one check `claim`) catches only seeds that name none. The unseeded rows come from the ledger where the probe's +evidence is current, else from an unseeded run first. Nothing it runs is evidence; it +writes `seeds-last.json` (`Schemas/seeds-run.schema.json`), one outcome per seed, reported as +`P/<obligation>` where the seed names one. `register --obligation O --seed PATCH --defect TEXT` (or +`--seed-env NAME=VALUE...` for a toggle) writes a seed into `seeds`, in place of any seed of the entry that +names O, `seed` included, and refuses an O that the claim's entry in `claims.json` does not list; without +`--obligation`, `--seed` and `--seed-env` set the one `seed`. No seed is written into the registry by hand. +Without `--all`, `seeds` runs only the probes that changed since the run that caught every one of their seeds +(design/adr/0107), as `replay` runs only the stale ones: a probe runs when its seeds identity (the probe's identity, +its seeds and the content of their patches) differs from the one recorded, when the product footprint its last +replay recorded or a file its seeds patch changed since the recorded commit, when no footprint is recorded, and when +no run caught all its seeds. It prints each probe it runs or skips with the reason. `seeds-last.json` keeps, per probe, +the identity and the commit of the run that caught them all, carried forward for the probes a run skips; a missed seed +drops the probe's record, so it is never skipped next time. `--all` runs every probe whatever the record says, and a +release runs it before the tag; `--probe P` runs P. +A seed is not part of a probe's identity, neither `seed` nor `seeds`. Without a build, +`Seeds.Problems`, which the prober tests run over the repository, refuses a probe without a seed, a seed that +does not apply, a seed naming an obligation its claim does not list, and a listed obligation no seed names. + +## Writing a probe + +`new --claim C` starts one in the branch's staging, whose path it prints. It writes `probes/P.cs` in the form +the corpus's probes have (`Rows.Data`, `[Theory, MemberData(nameof(Data))]`, `Rec.Run`), headed by the claim's +source and each obligation's id and sentence, with one check per obligation that throws until it is written: +the probe builds beside the others, and every row of it ends as a harness error, which fails the row, earns +nothing and is never a seed's catch. It stages the probe's own rows, a covering array over its claim's pairs the +claim's other probes have not covered, as `rows --probe P` writes them, and the registry entry with an empty +`seeds`, so `seeds` and `Seeds.Problems` name the seed each obligation owes. P defaults to the claim id's +prefix and words (`C20-testing-shouldhold` is `C20TestingShouldholdProbe`); it refuses a claim that is not in +the claims, a name a probe has, and a claim whose first surface is in no family, or not in `--family`'s. + +Then fill each check and run `try --probe P` until each row ends as it should. It moves the replay's worktree +to HEAD, overlays the corpus and the staging as the replay does, builds once through the adapter (not with +`--no-build`), and runs P alone in each of its registry variants, or only the rows `--rows` names, through the +harness's `PROBE_ROWS`, within the rows each variant's `PROBE_ONLY` selects. It prints each run's verdict line +and how each row ended, and exits with the probe's code (1 when the build fails). It records no evidence, +footprint or run and takes no round lock, but holds the replay's worktree (`replay.lock`) while it runs, so a try +refuses while a replay of the checkout runs, and a replay skips while a try does. Then write a seed per +obligation and register it with the command `new` printed for that obligation (`register --obligation O`, +under "Seeds"), show each caught with `seeds --probe P`, and `publish`. `register` lints the staged sources +first (below). A writer needs no build or sync script of their own. + +## Register and publish + +`register` and `publish` read the staged harness and probe sources first and refuse, by file and line, a +`Note` whose first argument is not a `NoteKeys` member the corpus declares, a harness `Skip` whose finding +is not a string literal naming a finding this ledger holds open (decision 103), and a check's `Inapplicable` or +`Refused` whose first argument is not a `Cite` factory with a string literal, or names what does not exist: a +not-promised entry the probe's claim does not list (any claim's, for a harness source no one probe owns), a docs +file, a knowledge entry under `design/knowledge/`, a record under `design/adr/`. `ProbeCiteTests` in the +conventions tests holds the same over the tracked corpus. The scan blanks out comments +and string literals and reads text, since this tool references the base class library only. ## The adapter diff --git a/tools/Prober/ReleaseGate.cs b/tools/Prober/ReleaseGate.cs index 7e91667e..42caea16 100644 --- a/tools/Prober/ReleaseGate.cs +++ b/tools/Prober/ReleaseGate.cs @@ -5,10 +5,10 @@ namespace Prober; /// <summary> /// The release gate. It needs nothing but the tracked corpus and a replay: no model, no login, no local -/// memory of a developer's machine. It refuses while the staging is unpublished, a finding is open, a probe -/// has not passed at its current identity and footprint, an expected row did not run, or a claim touched +/// memory of a developer's machine. It refuses while the staging is unpublished, a finding is open or a row +/// skips for one, a probe has not passed at its current identity and footprint, an expected row did not run, or a claim touched /// since the release has pairs no probe covers and no reviewed waiver. Touched is chosen conservatively: the -/// claim's source document, its surfaces' source areas or the build inputs changed; footprints only add. +/// claim's source document, its surfaces' source areas, the paths its probes watch or the build inputs changed; footprints only add. /// </summary> internal static class ReleaseGate { @@ -39,6 +39,9 @@ public static int Run(Ledger l, string? sinceArg, bool json) } expected.ExceptWith(executed); if (expected.Count > 0) reasons.Add($"{s.Probe}: {expected.Count} expected row(s) did not run"); + // A skip is an open finding the corpus itself records, so it binds where the ledger has none (CI). + foreach (var (finding, rows) in Ledger.FindingSkips(evidence)) + reasons.Add($"{s.Probe}: {rows} row(s) skip for finding {finding}; fix it and remove the skip, or dismiss it"); } var done = l.Covered(universe); @@ -52,17 +55,20 @@ public static int Run(Ledger l, string? sinceArg, bool json) .Select(w => Files.Str(w["claim"])).ToHashSet(StringComparer.Ordinal); var required = new List<string>(); var waived = new List<string>(); var missedByFootprint = new List<string>(); var footprintOnly = new List<string>(); - var missingTotal = 0; + var missingTotal = 0; var inapplicableTotal = 0; foreach (var claim in universe.Claims) { - var conservative = Under([claim.Source.Split('#')[0]]) || Under(claim.Watched) || Under(BuildInputs); var probes = byClaim.GetValueOrDefault(claim.Id) ?? []; + // What the claim's surfaces watch, and what its probes' registry entries watch beyond their footprints. + var conservative = Under([claim.Source.Split('#')[0]]) || Under(claim.Watched) || Under(probes.SelectMany(l.Watched)) || Under(BuildInputs); var footprint = probes.Count > 0 && probes.Any(p => l.FootprintChangedSince(p, since, footprints)); if (probes.Count > 0 && conservative && !footprint) missedByFootprint.Add(claim.Id); if (footprint && !conservative) footprintOnly.Add(claim.Id); if (!(conservative || footprint)) continue; if (waivers.Contains(claim.Id)) { waived.Add(claim.Id); continue; } - var pairs = claim.Pairs(); + // The pairs some obligation wants; one no obligation wants is declared inapplicable and left out, as an exclusion is. + var pairs = claim.PairsWanted(); + inapplicableTotal += claim.Pairs().Count - pairs.Count; var missing = pairs.Count(p => !done.Contains((claim.Id, p))); required.Add(claim.Id); missingTotal += missing; if (missing > 0) reasons.Add($"{claim.Id}: {missing} of {pairs.Count} pairs uncovered (touched since {since})"); @@ -72,7 +78,7 @@ public static int Run(Ledger l, string? sinceArg, bool json) { l.Ctx.Out.WriteLine(new JsonObject { - ["since"] = since, ["touched"] = new JsonArray([.. required.Select(x => (JsonNode)x)]), ["uncovered_pairs"] = missingTotal, + ["since"] = since, ["touched"] = new JsonArray([.. required.Select(x => (JsonNode)x)]), ["uncovered_pairs"] = missingTotal, ["inapplicable_pairs"] = inapplicableTotal, ["reasons"] = new JsonArray([.. reasons.Select(x => (JsonNode)x)]), ["selected_by_footprint_only"] = new JsonArray([.. footprintOnly.Select(x => (JsonNode)x)]), ["waived"] = new JsonArray([.. waived.Select(x => (JsonNode)x)]), diff --git a/tools/Prober/Replay.cs b/tools/Prober/Replay.cs index 14170f4b..eba97a0f 100644 --- a/tools/Prober/Replay.cs +++ b/tools/Prober/Replay.cs @@ -8,28 +8,51 @@ namespace Prober; /// <summary> /// Replays the written probes with no model in the loop. Per commit it runs only the probes whose identity or -/// footprint changed; <c>--all</c> runs every probe, retired ones included (the release run). Each runs alone, -/// once per process variant, with coverage; the replay records per-row evidence (rows expected, rows executed, -/// each row's outcome and applied settings), the result, and the fresh footprint. It works in one warm -/// worktree kept between runs and moved to HEAD without force, so only what changed is rebuilt. A dependency -/// input that changed stales every probe, and the axes are derived again. Only one round runs at a time. +/// footprint changed; <c>--all</c> runs every probe, retired ones included (the release run). Each probe runs +/// once per process variant, and the replay records each probe in registry order whatever order the runs +/// finished in: per-row evidence (rows expected, rows executed, each row's outcome and applied settings), the +/// result, and the fresh footprint. <c>--no-footprint</c> runs without coverage and records no footprint, for a +/// state nobody keeps (a pull request's); only then do the variants of every selected probe run +/// <c>--parallel N</c> at a time (the machine's cores by default), since the coverage collector rewrites the +/// assemblies on disk and two hosts from one output directory corrupt each other (KB-025). +/// <c>--family F</c> replays the probes whose claim's first surface the +/// replay configuration's <c>families</c> puts under F, so a matrix of jobs can split the corpus and +/// <c>merge</c> join their states; it refuses while any probe's surface is in no family, so a matrix cannot +/// quietly leave a probe out. It works in one warm worktree kept between runs and moved to HEAD without force, +/// so only what changed is rebuilt. A dependency input that changed stales every probe, and the axes are +/// derived again. Only one round runs at a time. /// </summary> internal static class Replay { - public static int Run(Context ctx, Args a) + /// <summary>How this replay was asked to run, recorded in replay-last.json for <c>merge</c> to read.</summary> + internal sealed record Options(bool All, string? Family, int Parallel, bool Footprint); + + public static int Run(Context ctx, Args a, IProbeAdapter? adapter = null) { var ledger = new Ledger(ctx, a.Get("branch")); var config = ledger.AxesBlock()["replay"] as JsonObject ?? throw new Refusal("the axes file's block has no \"replay\" configuration"); - IProbeAdapter adapter = new DotNetAdapter(config); - var key = Convert.ToHexStringLower(SHA1.HashData(Encoding.UTF8.GetBytes(ledger.Root)))[..8]; - var home = Path.Combine(ctx.Env("PROBE_CACHE_DIR") ?? Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), ".cache", "probe-replay"), - $"{Path.GetFileName(ledger.Root)}-{key}"); - if (a.Flag("where")) { ctx.Out.WriteLine($"replay: {Path.Combine(home, "wt")}\nexplore: {Path.Combine(home, "explore")}"); return 0; } - - var states = ledger.ProbeStates(a.Flag("all")); - var todo = a.Flag("all") ? states : [.. states.Where(s => s.Replay)]; + adapter ??= new DotNetAdapter(config); + var home = Home(ctx, ledger); + if (a.Flag("where")) + { + ctx.Out.WriteLine($"replay: {Path.Combine(home, "wt")}\nexplore: {Path.Combine(home, "explore")}\nseeds: {Path.Combine(home, "seeds")}"); + ctx.Out.WriteLine($"remove: {RemoveCommand(home)}"); + return 0; + } + + // With a footprint the coverage collector instruments the assemblies on disk, so two hosts from one + // output directory corrupt each other (KB-025): one at a time, whatever --parallel says. + var footprint = !a.Flag("no-footprint"); + var asked = Parallelism(a.Get("parallel")); + var options = new Options(a.Flag("all"), a.Get("family"), footprint ? 1 : asked, footprint); + var states = ledger.ProbeStates(options.All); + var scope = options.Family is null ? states : OfFamily(config, ledger, states, options.Family); + var todo = options.All ? scope : [.. scope.Where(s => s.Replay)]; + var skipped = scope.Where(s => !todo.Contains(s)).Select(s => s.Probe).ToList(); var head = ledger.Head; - ctx.Out.WriteLine($"prober replay: {ledger.Branch} at {head[..7]}: {todo.Count} of {states.Count} probes to replay"); + ctx.Out.WriteLine($"prober replay: {ledger.Branch} at {head[..7]}: {todo.Count} of {scope.Count} probes to replay" + + $"{(options.Family is null ? "" : $" (family {options.Family})")}, " + + (footprint ? "one at a time (the coverage collector rewrites the assemblies on disk)" : $"{options.Parallel} at a time, no footprint")); if (a.Flag("dry-run")) { foreach (var s in todo) ctx.Out.WriteLine($" {s.Probe}"); return 0; } Directory.CreateDirectory(home); @@ -45,7 +68,13 @@ public static int Run(Context ctx, Args a) try { using var beat = new RoundLock.Heartbeat(ledger, holder); - var axes = Rederive(ledger, adapter, config, Path.Combine(home, "enumerate"), force: a.Flag("all")); + using var held = HoldWorktree(home); + if (held is null) + { + ctx.Out.WriteLine($"prober replay: skipped, another branch's replay in this checkout is using {home}; run `prober replay` when it ends"); + return 0; + } + var axes = Rederive(ledger, adapter, config, Path.Combine(home, "enumerate"), force: options.All); if (axes is not null) { if (axes["error"] is { } error) ctx.Out.WriteLine($" axes: {Files.Str(error)}"); @@ -58,10 +87,10 @@ public static int Run(Context ctx, Args a) } if (todo.Count == 0) { - Files.WriteJson(ledger.P("replay-last.json"), new JsonObject { ["branch"] = ledger.Branch, ["commit"] = head, ["replayed"] = 0, ["failed"] = new JsonArray(), ["axes"] = axes?.DeepClone() }); + Summary(ledger, head, warmth: null, buildSeconds: 0, todo, skipped, failed: [], seconds: 0, axes, options); return 0; } - return Probes(ctx, ledger, adapter, config, home, Path.GetFullPath(a.Get("worktree") ?? Path.Combine(home, "wt")), head, todo, axes); + return Probes(ctx, ledger, adapter, config, home, Path.GetFullPath(a.Get("worktree") ?? Path.Combine(home, "wt")), head, todo, skipped, axes, options); } finally { @@ -69,6 +98,43 @@ public static int Run(Context ctx, Args a) } } + /// <summary>How many probe processes run at once: <c>--parallel N</c>, else one per core.</summary> + internal static int Parallelism(string? given) + { + if (given is null) return Math.Max(1, System.Environment.ProcessorCount); + if (!int.TryParse(given, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n) || n < 1) throw new Refusal("--parallel takes a whole number of 1 or more", 2); + return n; + } + + /// <summary> + /// The probes of one family: those whose claim's first surface the replay configuration's <c>families</c> + /// (name to surfaces) puts under it. Every probe of the registry must have a family, or a matrix of jobs by + /// family would never run the one that has none; so this refuses for the whole registry, not only the family + /// asked for. + /// </summary> + internal static List<Ledger.ProbeState> OfFamily(JsonObject config, Ledger ledger, List<Ledger.ProbeState> states, string family) + { + var families = config["families"] as JsonObject ?? throw new Refusal("the replay configuration names no families; add \"families\" (a name to the surfaces it holds) to the axes file's replay block", 2); + if (families[family] is not JsonArray) throw new Refusal($"no family {family}; the replay configuration has {string.Join(", ", families.Select(kv => kv.Key))}", 2); + var bySurface = new Dictionary<string, string>(StringComparer.Ordinal); + foreach (var (name, surfaces) in families) + foreach (var surface in Files.Strings(surfaces)) + { + if (bySurface.TryGetValue(surface, out var other) && other != name) throw new Refusal($"the surface {surface} is in two families, {other} and {name}", 2); + bySurface[surface] = name; + } + var firstSurface = ledger.Claims().ToDictionary(c => Files.Str(c["id"]), c => Files.Strings(c["surfaces"]).FirstOrDefault() ?? "", StringComparer.Ordinal); + var result = new List<Ledger.ProbeState>(); + foreach (var s in states) + { + var surface = firstSurface.GetValueOrDefault(s.Claim) ?? ""; + if (!bySurface.TryGetValue(surface, out var f)) + throw new Refusal($"{s.Probe}: the first surface of its claim {s.Claim} ({(surface.Length > 0 ? surface : "none")}) is in no family of the replay configuration, so a replay by family would never run it"); + if (f == family) result.Add(s); + } + return result; + } + /// <summary>Derives the generated axes again when a dependency input or the toolchain changed, or always when forced.</summary> private static JsonObject? Rederive(Ledger ledger, IProbeAdapter adapter, JsonObject config, string work, bool force) { @@ -95,53 +161,49 @@ static SortedSet<string> Settings(JsonObject o) => }; } - private static int Probes(Context ctx, Ledger ledger, IProbeAdapter adapter, JsonObject config, string home, string worktree, string head, - List<Ledger.ProbeState> todo, JsonObject? axes) - { - var clock = Stopwatch.StartNew(); - string warmth; - if (!Directory.Exists(worktree)) - { - var added = Git.Run(ledger.Root, "worktree", "add", "--detach", worktree, head); - if (added.ExitCode != 0) { ctx.Error.WriteLine(added.Stderr); return 2; } - warmth = "cold"; - } - else - { - // No force: files the commits did not change keep their mtimes, so the build stays incremental. - if (Git.Run(worktree, "checkout", "--detach", head).ExitCode != 0) Git.Run(worktree, "reset", "--hard", head); - warmth = "warm"; - } + /// <summary>One process variant of a probe, run: what it was asked, what it answered, and where its rows, coverage and logs went.</summary> + private sealed record VariantRun(JsonObject Variant, int Index, Ran Ran, double Seconds, string OutDir, string Coverage, string LogDir); - // The corpus: tracked, overlaid by the branch's unpublished staging; rows unpacked. - var probeDir = Path.Combine(worktree, Files.Str(config["probeDir"])); - Directory.CreateDirectory(Path.Combine(probeDir, "rows")); - var wanted = new SortedDictionary<string, string>(StringComparer.Ordinal); - var rows = new SortedDictionary<string, string>(StringComparer.Ordinal); - foreach (var source in new[] { ledger.CorpusDir, ledger.StagingDir }) - { - foreach (var sub in new[] { "harness", "probes" }) - if (Directory.Exists(Path.Combine(source, sub))) - foreach (var f in Directory.EnumerateFiles(Path.Combine(source, sub), "*.cs")) wanted[Path.GetFileName(f)] = f; - if (Directory.Exists(Path.Combine(source, "rows"))) - foreach (var f in Directory.EnumerateFiles(Path.Combine(source, "rows")).Where(f => f.EndsWith(".json", StringComparison.Ordinal) || f.EndsWith(".json.gz", StringComparison.Ordinal))) - rows[Path.GetFileName(f).Replace(".gz", "", StringComparison.Ordinal)] = f; - } - foreach (var existing in Directory.EnumerateFiles(probeDir)) if (!wanted.ContainsKey(Path.GetFileName(existing))) File.Delete(existing); - foreach (var (name, src) in wanted) - { - var dst = Path.Combine(probeDir, name); - if (!File.Exists(dst) || !File.ReadAllBytes(dst).AsSpan().SequenceEqual(File.ReadAllBytes(src))) File.Copy(src, dst, overwrite: true); - } - foreach (var (name, src) in rows) + /// <summary> + /// Starts every variant of a probe, each on a thread of its own that waits its turn at the gate, so the + /// variants of all selected probes share the <c>--parallel</c> slots in the order they were started. + /// </summary> + private static List<Task<VariantRun>> Start(IProbeAdapter adapter, string worktree, string results, IReadOnlyDictionary<string, string?> environment, + Ledger.ProbeState s, SemaphoreSlim gate, bool coverage) + { + var pending = new List<Task<VariantRun>>(); + var index = 0; + foreach (var variant in s.Variants.OfType<JsonObject>()) { - var data = src.EndsWith(".gz", StringComparison.Ordinal) ? Files.Gunzip(src) : File.ReadAllBytes(src); - var dst = Path.Combine(probeDir, "rows", name); - if (!File.Exists(dst) || !File.ReadAllBytes(dst).AsSpan().SequenceEqual(data)) File.WriteAllBytes(dst, data); + var i = index++; + var coverageFile = coverage ? $"{s.Probe}-{i}.cobertura.xml" : ""; + var outDir = Path.Combine(results, $"{s.Probe}-{i}-rows"); + // PROBE_LOG_DIR: where the run may leave its full logs; a failed run's are kept in the state. + var logDir = Path.Combine(results, $"{s.Probe}-{i}-log"); + var env = new Dictionary<string, string?>(environment, StringComparer.Ordinal) { ["PROBE_OUT"] = outDir, ["PROBE_LOG_DIR"] = logDir }; + foreach (var (k, v) in variant) env[k] = Files.Str(v); + pending.Add(Task.Factory.StartNew(() => + { + gate.Wait(); + try + { + var clock = Stopwatch.StartNew(); + var ran = adapter.RunProbe(worktree, s.Filter, results, coverageFile, env); + return new VariantRun(variant, i, ran, clock.Elapsed.TotalSeconds, outDir, coverageFile, logDir); + } + finally { gate.Release(); } + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)); } + return pending; + } - var environment = new Dictionary<string, string?>(ctx.Environment, StringComparer.Ordinal); - foreach (var (k, v) in adapter.Environment(home)) environment[k] = v; + private static int Probes(Context ctx, Ledger ledger, IProbeAdapter adapter, JsonObject config, string home, string worktree, string head, + List<Ledger.ProbeState> todo, List<string> skipped, JsonObject? axes, Options options) + { + var clock = Stopwatch.StartNew(); + var warmth = ProbeRun.Ready(ledger, worktree, head); + var probeDir = Overlay(ledger, config, worktree); + var environment = ProbeRun.Environment(ctx, adapter, home); var results = Directory.CreateTempSubdirectory("prober-replay-").FullName; var buildClock = Stopwatch.StartNew(); @@ -150,67 +212,76 @@ private static int Probes(Context ctx, Ledger ledger, IProbeAdapter adapter, Jso ctx.Out.WriteLine($" build ({warmth}): {buildSeconds.ToString("F1", CultureInfo.InvariantCulture)} s, {FirstLine(build)}"); if (build.ExitCode != 0) { ctx.Out.WriteLine(build.Stdout); return 2; } + // Every variant of every probe is started now and runs when a slot frees; the probes are then read, + // reported and recorded one by one in registry order, each once all its variants have ended. + using var gate = new SemaphoreSlim(options.Parallel); + var started = todo.Select(s => Start(adapter, worktree, results, environment, s, gate, options.Footprint)).ToList(); + var failed = new List<string>(); - foreach (var s in todo) + foreach (var (s, pending) in todo.Zip(started)) { - var claimRows = Files.ReadRows(Path.Combine(probeDir, "rows", s.Claim + ".json")); + var variants = pending.Select(t => t.GetAwaiter().GetResult()).OrderBy(v => v.Index).ToList(); + var probeRows = Files.ReadRows(Path.Combine(probeDir, "rows", s.Probe + ".json")); var evidence = new JsonObject { ["variants"] = new JsonArray(), ["rows"] = new JsonObject() }; var result = "pass"; var seconds = 0.0; var reports = new List<string>(); - var index = 0; - foreach (var variant in s.Variants.OfType<JsonObject>()) + foreach (var v in variants) { - var coverage = $"{s.Probe}-{index}.cobertura.xml"; - var outDir = Path.Combine(results, $"{s.Probe}-{index}-rows"); - var env = new Dictionary<string, string?>(environment, StringComparer.Ordinal) { ["PROBE_OUT"] = outDir }; - foreach (var (k, v) in variant) env[k] = Files.Str(v); - var probeClock = Stopwatch.StartNew(); - var ran = adapter.RunProbe(worktree, s.Filter, results, coverage, env); - seconds += probeClock.Elapsed.TotalSeconds; - var label = variant.Count > 0 ? " [" + string.Join(", ", variant.Select(kv => $"{kv.Key}={Files.Str(kv.Value)}")) + "]" : ""; - ctx.Out.WriteLine($" {s.Probe}{label}: {FirstLine(ran)} ({probeClock.Elapsed.TotalSeconds.ToString("F1", CultureInfo.InvariantCulture)} s)"); - if (ran.ExitCode != 0) { result = "fail"; ctx.Out.WriteLine(string.Join('\n', ran.Stdout.Split('\n').Skip(1).Take(11))); } - reports.AddRange(Directory.EnumerateFiles(results, coverage, SearchOption.AllDirectories)); + seconds += v.Seconds; + var label = v.Variant.Count > 0 ? " [" + string.Join(", ", v.Variant.Select(kv => $"{kv.Key}={Files.Str(kv.Value)}")) + "]" : ""; + ctx.Out.WriteLine($" {s.Probe}{label}: {FirstLine(v.Ran)} ({v.Seconds.ToString("F1", CultureInfo.InvariantCulture)} s)"); + if (v.Ran.ExitCode != 0) { result = "fail"; ctx.Out.WriteLine(string.Join('\n', v.Ran.Stdout.Split('\n').Skip(1).Take(11))); } + // A failed run's full logs, kept under the state's logs/ for triage: in CI the state is the artifact. + if (v.Ran.ExitCode != 0 && Directory.Exists(v.LogDir)) + foreach (var file in Directory.EnumerateFiles(v.LogDir)) + { + var kept = ledger.P(Path.Combine("logs", $"{s.Probe}-{v.Index}-{Path.GetFileName(file)}")); + Directory.CreateDirectory(Path.GetDirectoryName(kept)!); + File.Copy(file, kept, overwrite: true); + ctx.Out.WriteLine($" log: {kept}"); + } + if (v.Coverage.Length > 0) reports.AddRange(Directory.EnumerateFiles(results, v.Coverage, SearchOption.AllDirectories)); // Execution evidence: the rows this variant should run, the rows it did, how each ended and the // settings that applied. A row missing here, or recorded without its settings, earns nothing. - var only = Files.Str(variant["PROBE_ONLY"]); + var only = Files.Str(v.Variant["PROBE_ONLY"]); var expected = new JsonArray(); var executed = new SortedSet<int>(); - for (var i = 0; i < claimRows.Count; i++) - if (Selected(Files.StringMap(claimRows[i]?["row"] ?? claimRows[i]), only)) expected.Add(i); - if (Directory.Exists(outDir)) - foreach (var file in Directory.EnumerateFiles(outDir, "*.jsonl")) + for (var i = 0; i < probeRows.Count; i++) + if (Selected(Files.StringMap(probeRows[i]?["row"] ?? probeRows[i]), only)) expected.Add(i); + if (Directory.Exists(v.OutDir)) + foreach (var file in Directory.EnumerateFiles(v.OutDir, "*.jsonl")) foreach (var line in Files.ReadLines(file)) { var idx = (int)Files.Num(line["idx"]); executed.Add(idx); - (evidence["rows"] as JsonObject)![idx.ToString(CultureInfo.InvariantCulture)] = new JsonObject - { - ["category"] = Files.Str(line["category"], "violation"), ["applied"] = line["applied"]?.DeepClone() ?? new JsonObject(), - ["reasons"] = new JsonArray([.. Files.Strings(line["reasons"]).Take(6).Select(x => (JsonNode)x)]), - }; + (evidence["rows"] as JsonObject)![idx.ToString(CultureInfo.InvariantCulture)] = EvidenceRow(line); } (evidence["variants"] as JsonArray)!.Add(new JsonObject { - ["env"] = variant.DeepClone(), ["expected"] = expected, ["executed"] = new JsonArray([.. executed.Select(x => (JsonNode)x)]), + ["env"] = v.Variant.DeepClone(), ["expected"] = expected, ["executed"] = new JsonArray([.. executed.Select(x => (JsonNode)x)]), }); var missing = expected.Select(x => (int)Files.Num(x)).Where(x => !executed.Contains(x)).ToList(); if (missing.Count > 0) ctx.Out.WriteLine($" {missing.Count} expected row(s) did not run: [{string.Join(", ", missing.Take(10))}]"); - index++; } + foreach (var stale in ledger.StaleSkips(evidence)) { result = "fail"; ctx.Out.WriteLine($" {stale}"); } if (result == "fail") failed.Add(s.Probe); var evidencePath = Path.Combine(results, $"{s.Probe}-evidence.json"); Files.WriteJson(evidencePath, evidence); var categories = (evidence["rows"] as JsonObject)!.GroupBy(kv => Files.Str(kv.Value!["category"]), StringComparer.Ordinal) .OrderBy(g => g.Key, StringComparer.Ordinal).Select(g => $"{g.Key} {g.Count()}"); - ctx.Out.WriteLine(" outcomes: " + string.Join(", ", categories)); + // Checks beside rows, once a row of the probe ran any: until then every row is its one check and the line says nothing more. + var checks = (evidence["rows"] as JsonObject)!.Any(kv => kv.Value?["checks"] is JsonArray { Count: > 0 }) + ? "; checks: " + string.Join(", ", Ledger.CheckOutcomes(evidence).Select(kv => $"{kv.Key} {kv.Value}")) : ""; + ctx.Out.WriteLine(" outcomes: " + string.Join(", ", categories) + checks); var record = new StringWriter(); Program.Run(new Context(ledger.Top, ctx.Environment, record, ctx.Error), ["record-run", "--branch", ledger.Branch, "--probe", s.Probe, "--result", result, "--seconds", seconds.ToString("F1", CultureInfo.InvariantCulture), "--evidence", evidencePath]); if (record.ToString().Trim() is { Length: > 0 } said) ctx.Out.WriteLine(" " + said); // The footprint is what ran, pass or fail: a probe failing for an open finding is skipped like any - // other until its footprint changes (a fix touches it). - if (reports.Count > 0) + // other until its footprint changes (a fix touches it). Without one the probe stays stale, which is + // right for a state nobody keeps. + if (!options.Footprint) ctx.Out.WriteLine(" footprint: not recorded (--no-footprint)"); + else if (reports.Count > 0) { var fp = new StringWriter(); var fpError = new StringWriter(); Program.Run(new Context(ledger.Top, ctx.Environment, fp, fpError), @@ -222,15 +293,130 @@ private static int Probes(Context ctx, Ledger ledger, IProbeAdapter adapter, Jso else ctx.Error.WriteLine(" footprint: no coverage report found; the probe stays stale"); } + Summary(ledger, head, warmth, buildSeconds, todo, skipped, failed, clock.Elapsed.TotalSeconds, axes, options); + ctx.Out.WriteLine($"prober replay: {todo.Count - failed.Count} passed, {failed.Count} failed, {clock.Elapsed.TotalSeconds.ToString("F1", CultureInfo.InvariantCulture)} s in all"); + try { Directory.Delete(results, recursive: true); } catch (IOException) { } + return failed.Count > 0 ? 1 : 0; + } + + /// <summary> + /// replay-last.json: what this replay did, and how it was asked to run. <c>selected</c> and <c>skipped</c> + /// together are the probes in its scope (its family, or all), so <c>merge</c> can tell a probe no leg saw. + /// </summary> + private static void Summary(Ledger ledger, string head, string? warmth, double buildSeconds, List<Ledger.ProbeState> todo, List<string> skipped, + List<string> failed, double seconds, JsonObject? axes, Options options) + { + static JsonArray Names(IEnumerable<string> names) => new([.. names.Select(x => (JsonNode)x)]); Files.WriteJson(ledger.P("replay-last.json"), new JsonObject { ["branch"] = ledger.Branch, ["commit"] = head, ["build"] = warmth, ["build_seconds"] = Math.Round(buildSeconds, 1), - ["replayed"] = todo.Count, ["failed"] = new JsonArray([.. failed.Select(x => (JsonNode)x)]), - ["seconds"] = Math.Round(clock.Elapsed.TotalSeconds, 1), ["axes"] = axes?.DeepClone(), + ["replayed"] = todo.Count, ["failed"] = Names(failed), ["seconds"] = Math.Round(seconds, 1), ["axes"] = axes?.DeepClone(), + ["all"] = options.All, ["family"] = options.Family, ["parallel"] = options.Parallel, ["footprint"] = options.Footprint, + ["selected"] = Names(todo.Select(s => s.Probe)), ["skipped"] = Names(skipped), }); - ctx.Out.WriteLine($"prober replay: {todo.Count - failed.Count} passed, {failed.Count} failed, {clock.Elapsed.TotalSeconds.ToString("F1", CultureInfo.InvariantCulture)} s in all"); - try { Directory.Delete(results, recursive: true); } catch (IOException) { } - return failed.Count > 0 ? 1 : 0; + } + + /// <summary> + /// The cache directory of this checkout's scratch worktrees (<c>wt</c>, <c>seeds</c>, <c>explore</c>, <c>enumerate</c>) under + /// PROBE_CACHE_DIR, else ~/.cache/probe-replay: the checkout's directory name and a hash of its path. A linked worktree + /// has one of its own, so sessions in two checkouts never reset each other's copy; the main checkout is keyed on + /// <see cref="Ledger.Root"/>, as it was before, so its warm copy stays where it is. + /// </summary> + internal static string Home(Context ctx, Ledger ledger) + { + var checkout = ledger.Linked ? ledger.Top : ledger.Root; + var key = Convert.ToHexStringLower(SHA1.HashData(Encoding.UTF8.GetBytes(checkout)))[..8]; + return Path.Combine(ctx.Env("PROBE_CACHE_DIR") ?? Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), ".cache", "probe-replay"), + $"{Path.GetFileName(checkout)}-{key}"); + } + + /// <summary> + /// The command that removes this checkout's cache, its worktrees first (for <c>git worktree list</c>) and then the + /// directory, every path literal: removing a linked worktree leaves its cache behind. + /// </summary> + internal static string RemoveCommand(string home) + { + // Forward slashes on Windows, which git and the sh of Git for Windows both read. + static string Quoted(string path) => "'" + (Path.DirectorySeparatorChar == '\\' ? path.Replace('\\', '/') : path).Replace("'", "'\\''") + "'"; + var worktrees = new[] { "wt", "seeds", "explore" }.Select(w => Path.Combine(home, w)).Where(w => File.Exists(Path.Combine(w, ".git"))); + return string.Join("; ", worktrees.Select(w => $"git worktree remove --force {Quoted(w)}").Append($"rm -rf {Quoted(home)}")); + } + + /// <summary> + /// Holds this checkout's replay worktree for one replay: the lock is per branch, the worktree per checkout, so two + /// branches' replays in one checkout (a branch switched while the post-commit replay runs) would otherwise share it. + /// An OS lock on <c>replay.lock</c> beside it, released by the OS when its process ends; null when another holds it. + /// </summary> + internal static FileStream? HoldWorktree(string home) + { + Directory.CreateDirectory(home); + try { return new FileStream(Path.Combine(home, "replay.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } + catch (IOException) { return null; } + } + + /// <summary> + /// Copies the corpus into the worktree's probe directory: tracked, overlaid by the branch's unpublished staging; rows + /// unpacked, one file per probe, and a rows file the corpus no longer has removed; and the claims as claims.json, + /// whose declarations of where an obligation does not apply the harness reads. + /// </summary> + internal static string Overlay(Ledger ledger, JsonObject config, string worktree) + { + var probeDir = Path.Combine(worktree, Files.Str(config["probeDir"])); + Directory.CreateDirectory(Path.Combine(probeDir, "rows")); + var wanted = new SortedDictionary<string, string>(StringComparer.Ordinal); + var rows = new SortedDictionary<string, string>(StringComparer.Ordinal); + foreach (var source in new[] { ledger.CorpusDir, ledger.StagingDir }) + { + foreach (var sub in new[] { "harness", "probes" }) + if (Directory.Exists(Path.Combine(source, sub))) + foreach (var f in Directory.EnumerateFiles(Path.Combine(source, sub), "*.cs")) wanted[Path.GetFileName(f)] = f; + if (Directory.Exists(Path.Combine(source, "rows"))) + foreach (var f in Directory.EnumerateFiles(Path.Combine(source, "rows")).Where(f => f.EndsWith(".json", StringComparison.Ordinal) || f.EndsWith(".json.gz", StringComparison.Ordinal))) + rows[Path.GetFileName(f).Replace(".gz", "", StringComparison.Ordinal)] = f; + } + foreach (var existing in Directory.EnumerateFiles(probeDir)) if (!wanted.ContainsKey(Path.GetFileName(existing)) && Path.GetFileName(existing) != "claims.json") File.Delete(existing); + foreach (var existing in Directory.EnumerateFiles(Path.Combine(probeDir, "rows"))) if (!rows.ContainsKey(Path.GetFileName(existing))) File.Delete(existing); + foreach (var (name, src) in wanted) + { + var dst = Path.Combine(probeDir, name); + if (!File.Exists(dst) || !File.ReadAllBytes(dst).AsSpan().SequenceEqual(File.ReadAllBytes(src))) File.Copy(src, dst, overwrite: true); + } + foreach (var (name, src) in rows) + { + var data = src.EndsWith(".gz", StringComparison.Ordinal) ? Files.Gunzip(src) : File.ReadAllBytes(src); + var dst = Path.Combine(probeDir, "rows", name); + if (!File.Exists(dst) || !File.ReadAllBytes(dst).AsSpan().SequenceEqual(data)) File.WriteAllBytes(dst, data); + } + // The claims beside the rows, the corpus's with the staging's over them: the harness's Rows.Claim reads each + // obligation's declarations from here, so Row.Check skips a check where its claim declares it inapplicable. + var claims = Path.Combine(probeDir, "claims.json"); + var text = new JsonArray([.. ledger.Claims().Select(c => (JsonNode)c)]).ToJsonString(); + if (!File.Exists(claims) || File.ReadAllText(claims) != text) File.WriteAllText(claims, text); + return probeDir; + } + + /// <summary> + /// One row of the harness's jsonl as evidence keeps it: its category, the settings that applied, six reasons, the + /// findings it skipped for, and each check it ran with the same fields (obligation, baseline, category, its own + /// settings, six reasons, notes). A row that ran no check keeps no <c>checks</c>. + /// </summary> + internal static JsonObject EvidenceRow(JsonObject line) + { + static JsonArray Six(JsonNode? reasons) => new([.. Files.Strings(reasons).Take(6).Select(x => (JsonNode)x)]); + var row = new JsonObject + { + ["category"] = Files.Str(line["category"], "violation"), ["applied"] = line["applied"]?.DeepClone() ?? new JsonObject(), + ["reasons"] = Six(line["reasons"]), + ["findings"] = new JsonArray([.. Files.Strings(line["findings"]).Select(x => (JsonNode)x)]), + }; + if (line["checks"] is JsonArray checks && checks.Count > 0) + row["checks"] = new JsonArray([.. checks.OfType<JsonObject>().Select(c => (JsonNode)new JsonObject + { + ["obligation"] = Files.Str(c["obligation"]), ["baseline"] = c["baseline"] is JsonValue b ? Files.Str(b) : null, + ["category"] = Files.Str(c["category"], "violation"), ["applied"] = c["applied"]?.DeepClone() ?? new JsonObject(), + ["reasons"] = Six(c["reasons"]), ["notes"] = Files.Str(c["notes"]), + })]); + return row; } /// <summary>The harness's PROBE_ONLY filter, replicated: the rows a variant is expected to run.</summary> @@ -250,5 +436,5 @@ internal static bool Selected(IReadOnlyDictionary<string, string> row, string on return true; } - private static string FirstLine(Ran ran) => ran.Stdout.Split('\n')[0].Trim() is { Length: > 0 } line ? line : $"exit {ran.ExitCode}"; + private static string FirstLine(Ran ran) => ProbeRun.FirstLine(ran); } diff --git a/tools/Prober/RoundLock.cs b/tools/Prober/RoundLock.cs index 1e4555d4..e3e16831 100644 --- a/tools/Prober/RoundLock.cs +++ b/tools/Prober/RoundLock.cs @@ -3,11 +3,12 @@ namespace Prober; /// <summary> -/// One round at a time. The lock file is created exclusively, with its holder's owner and a heartbeat -/// inside; a holder is alive while its heartbeat is younger than <see cref="HeartbeatMinutes"/>. Liveness is -/// the heartbeat alone (no process is probed, no signal sent), which is the same on every OS. Every -/// read-judge-write of the lock runs under an exclusive file create (the mutex), so two takers can never both see -/// it free, or both take over a holder whose heartbeat stopped. +/// One round at a time per branch. The lock file sits in the branch's directory (<c>branches/<branch>/round.lock</c>), +/// beside everything a round or a replay writes, so two branches' rounds may run at once and two of one branch never do. +/// It is created exclusively, with its holder's owner and a heartbeat inside; a holder is alive while its heartbeat is +/// younger than <see cref="HeartbeatMinutes"/>. Liveness is the heartbeat alone (no process is probed, no signal sent), +/// which is the same on every OS. Every read-judge-write of the lock runs under an exclusive file create (the mutex), so +/// two takers can never both see it free, or both take over a holder whose heartbeat stopped. /// </summary> internal static class RoundLock { @@ -15,7 +16,7 @@ internal static class RoundLock public static readonly TimeSpan BeatEvery = TimeSpan.FromSeconds(30); private const int MutexStaleSeconds = 60; - public static string LockPath(Ledger l) => Path.Combine(l.Memory, "round.lock"); + public static string LockPath(Ledger l) => Path.Combine(l.Dir, "round.lock"); public static double Now() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000.0; @@ -30,7 +31,7 @@ public static bool Alive(JsonObject held) => /// </summary> public static T WithMutex<T>(Ledger l, Func<T> body) { - Directory.CreateDirectory(l.Memory); + Directory.CreateDirectory(l.Dir); var path = LockPath(l) + ".mutex"; var deadline = DateTime.UtcNow.AddSeconds(10); while (true) diff --git a/tools/Prober/SchemaCheck.cs b/tools/Prober/SchemaCheck.cs new file mode 100644 index 00000000..56725a7d --- /dev/null +++ b/tools/Prober/SchemaCheck.cs @@ -0,0 +1,88 @@ +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace Prober; + +/// <summary> +/// The part of JSON Schema the tracked files' schemas use, checked where a file is read, so a field nobody +/// reads is refused instead of kept (decision 102: three invented fields for an exclusion's reason sat in +/// claims.json unread). A keyword outside this part throws: a schema that says more than is checked would be +/// a check that silently does less than it reads. +/// </summary> +internal static class SchemaCheck +{ + private static readonly HashSet<string> Annotations = ["$schema", "$id", "title", "description"]; + + /// <summary>The schema of <c>Schemas/{name}</c>, embedded in the dll, so it is the one the dll's fingerprint covers.</summary> + public static JsonObject Load(string name) + { + using var stream = typeof(SchemaCheck).Assembly.GetManifestResourceStream(name) + ?? throw new InvalidOperationException($"no embedded schema {name}"); + return JsonNode.Parse(stream) as JsonObject ?? throw new InvalidOperationException($"the schema {name} is not a JSON object"); + } + + /// <summary>What in <paramref name="node"/> the schema refuses, each at its path (<c>exclude[0].reason</c>).</summary> + public static List<string> Problems(JsonNode? node, JsonObject schema, string path = "") + { + var problems = new List<string>(); + Check(node, schema, path, problems); + return problems; + } + + private static void Check(JsonNode? node, JsonObject schema, string path, List<string> problems) + { + var at = path.Length == 0 ? "the value" : path; + foreach (var (keyword, _) in schema) + if (!Annotations.Contains(keyword) && keyword is not ("type" or "const" or "required" or "properties" or "additionalProperties" + or "items" or "minItems" or "maxItems" or "minLength" or "pattern")) + throw new InvalidOperationException($"a schema uses {keyword}, which SchemaCheck does not check"); + + if (schema["type"] is JsonValue type && !IsType(node, Files.Str(type))) + { + problems.Add($"{at} is not {Article(Files.Str(type))}"); + return; + } + if (schema["const"] is { } constant && !JsonNode.DeepEquals(node, constant)) problems.Add($"{at} is not {constant.ToJsonString()}"); + + if (node is JsonObject o) + { + foreach (var name in Files.Strings(schema["required"])) + if (!o.ContainsKey(name)) problems.Add($"{Join(path, name)} is required"); + var properties = schema["properties"] as JsonObject ?? []; + foreach (var (name, value) in o) + { + if (properties[name] is JsonObject property) Check(value, property, Join(path, name), problems); + else if (schema["additionalProperties"] is JsonObject rest) Check(value, rest, Join(path, name), problems); + else if (schema["additionalProperties"] is JsonValue allowed && allowed.TryGetValue<bool>(out var b) && !b) + problems.Add($"{Join(path, name)} is not a field the schema has"); + } + } + if (node is JsonArray a) + { + if (schema["minItems"] is { } min && a.Count < Files.Num(min)) problems.Add($"{at} has {a.Count} item(s), fewer than {Files.Num(min)}"); + if (schema["maxItems"] is { } max && a.Count > Files.Num(max)) problems.Add($"{at} has {a.Count} item(s), more than {Files.Num(max)}"); + if (schema["items"] is JsonObject items) + for (var i = 0; i < a.Count; i++) Check(a[i], items, $"{path}[{i}]", problems); + } + if (node is JsonValue v && v.TryGetValue<string>(out var s)) + { + if (schema["minLength"] is { } length && s.Length < Files.Num(length)) problems.Add($"{at} is shorter than {Files.Num(length)}"); + if (schema["pattern"] is JsonValue pattern && !Regex.IsMatch(s, Files.Str(pattern), RegexOptions.CultureInvariant)) + problems.Add($"{at} does not match {Files.Str(pattern)}"); + } + } + + private static string Join(string path, string name) => path.Length == 0 ? name : $"{path}.{name}"; + + private static string Article(string type) => type is "object" or "array" or "integer" ? $"an {type}" : $"a {type}"; + + private static bool IsType(JsonNode? node, string type) => type switch + { + "object" => node is JsonObject, + "array" => node is JsonArray, + "string" => node is JsonValue v && v.TryGetValue<string>(out _), + "integer" => node is JsonValue v && v.TryGetValue<long>(out _), + "boolean" => node is JsonValue v && v.TryGetValue<bool>(out _), + _ => throw new InvalidOperationException($"a schema names type {type}, which SchemaCheck does not check"), + }; +} diff --git a/tools/Prober/Schemas/claims.schema.json b/tools/Prober/Schemas/claims.schema.json index 8a158b39..3c944f19 100644 --- a/tools/Prober/Schemas/claims.schema.json +++ b/tools/Prober/Schemas/claims.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codometis.typekit/probe/schemas/claims.schema.json", "title": "Claims", - "description": "Tracked (corpus/claims.json): the claims the documents make, each naming the surfaces whose axes it takes.", + "description": "Tracked (corpus/claims.json): the claims the documents make, each naming the surfaces whose axes it takes. The prober refuses a file that does not match this schema when it reads one (decision 102).", "type": "array", "items": { "type": "object", @@ -18,7 +18,8 @@ "description": "The schema version. A file or line without it is version 1 (the Python baseline)." }, "id": { - "type": "string" + "type": "string", + "minLength": 1 }, "source": { "type": "string", @@ -48,19 +49,141 @@ }, "description": "axis dropped, with the source sentence that limits it (checked verbatim)" }, + "obligations": { + "type": "array", + "minItems": 1, + "description": "what the claim promises, each checkable on its own: a sentence quoted verbatim from the claim's source document (checked as a narrowing is), under an id a probe's seed names (decision 104); a claim without the field has one obligation, the claim itself, under the id `claim`", + "items": { + "type": "object", + "required": [ + "id", + "sentence" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "sentence": { + "type": "string", + "pattern": "\\S" + }, + "axes": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "the axis values the obligation concerns, where it is not the whole claim" + }, + "inapplicable": { + "type": "array", + "minItems": 1, + "description": "where the obligation does not apply: a row holding every axis of a declaration at one of its values runs no check of the obligation (Row.Check records it inapplicable with the cite), a pair matched by a declaration is not wanted by the obligation, and a pair no obligation wants is declared inapplicable, never put in a generated row and left out of the gate's count as an exclusion is. `Axes.Build` refuses an axis or value the claim does not list, a malformed cite, and a not-promised id the claim lacks", + "items": { + "type": "object", + "required": [ + "axes", + "cite" + ], + "properties": { + "axes": { + "type": "object", + "additionalProperties": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "description": "axis to the values where the obligation does not apply; every axis must hold at one of its values" + }, + "cite": { + "type": "string", + "pattern": "^(not-promised|docs|fact|record):", + "description": "why: not-promised:<id of the claim's not-promised list>, docs:<file#anchor>, fact:KB-NNN or record:NNNN, in the forms the harness's Cite accepts" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "not-promised": { + "type": "array", + "minItems": 1, + "description": "what the claim does not promise, one entry per intended difference: the sentence of the document or record that makes it intended, quoted verbatim from `source` (checked as a narrowing is), and the axis values it concerns; a note key of outcome expected-refusal, a narrowing or an applicability condition cites an entry by its id (decision 104)", + "items": { + "type": "object", + "required": [ + "id", + "source", + "sentence" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "source": { + "type": "string", + "description": "file#heading of the document or record that states it" + }, + "sentence": { + "type": "string", + "pattern": "\\S" + }, + "axes": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "the axis values or shapes the difference concerns" + } + }, + "additionalProperties": false + } + }, "exclude": { "type": "array", + "description": "pairs no row can hold, each group with the reason why; `prober universe` prints the reasons, since nothing checks them mechanically", "items": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[^=]+=.*$" + "type": "object", + "required": [ + "reason", + "pairs" + ], + "properties": { + "reason": { + "type": "string", + "pattern": "\\S", + "description": "one sentence saying why the pairs cannot exist or are not promised, citing a docs sentence, a record or a KB fact where one applies" + }, + "pairs": { + "type": "array", + "minItems": 1, + "items": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[^=]+=.*$" + }, + "minItems": 2, + "maxItems": 2 + } + } }, - "minItems": 2, - "maxItems": 2 + "additionalProperties": false } } }, - "additionalProperties": true + "additionalProperties": false } } diff --git a/tools/Prober/Schemas/evidence.schema.json b/tools/Prober/Schemas/evidence.schema.json index 662db069..7d86b9db 100644 --- a/tools/Prober/Schemas/evidence.schema.json +++ b/tools/Prober/Schemas/evidence.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codometis.typekit/probe/schemas/evidence.schema.json", "title": "Evidence", - "description": "State (branches/<branch>/evidence/<probe>.json): what one replay of a probe did. Only rows expected, executed and ending as a pass earn credit, for the settings that applied, while the identity holds.", + "description": "State (branches/<branch>/evidence/<probe>.json): what one replay of a probe did. Only checks that ended as a pass, in rows expected and executed, earn credit, for the settings that applied (the row's, the check's laid over them), while the identity holds. A row without checks is one check of the obligation `claim` with the row's category.", "type": "object", "required": [ "identity", @@ -12,8 +12,11 @@ "properties": { "schemaVersion": { "type": "integer", - "const": 1, - "description": "The schema version. A file or line without it is version 1 (the Python baseline)." + "enum": [ + 1, + 2 + ], + "description": "The schema version: 2 records each row's checks. A file without it is version 1, whose rows have no checks." }, "probe": { "type": "string" @@ -80,21 +83,85 @@ "expected-refusal", "inapplicable", "harness-error", - "inconclusive" - ] + "inconclusive", + "known-finding" + ], + "description": "The highest of the row's own outcome and its checks'." }, "applied": { "type": "object", "additionalProperties": { "type": "string" }, - "description": "Axis name to value, both compared ordinally." + "description": "Axis name to value, both compared ordinally: the axes the row read outside any check, which every check of it ran under." }, "reasons": { "type": "array", "items": { "type": "string" } + }, + "findings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The open findings the row stepped around (Row.Skip in the harness); the row ends as known-finding." + }, + "checks": { + "type": "array", + "description": "Version 2: one entry per check the row ran (Row.Check in the harness), in the order they ended. Absent for a row that ran none.", + "items": { + "type": "object", + "required": [ + "obligation", + "category", + "applied" + ], + "properties": { + "obligation": { + "type": "string", + "description": "An id of the claim's obligations, or `claim` for a claim without them." + }, + "baseline": { + "type": [ + "string", + "null" + ], + "description": "What the check compared against: twin, framework, round-trip, model, refusal, cost, fact:KB-NNN or docs:file#anchor. A pass always has one." + }, + "category": { + "enum": [ + "pass", + "violation", + "expected-refusal", + "inapplicable", + "harness-error", + "inconclusive", + "known-finding" + ] + }, + "applied": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The axes this check read, at the value that applied, and none another check read." + }, + "reasons": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Six at most, as the row's." + }, + "notes": { + "type": "string", + "description": "How the check was compared, and the citations of what did not apply or was refused, separated by ;." + } + }, + "additionalProperties": false + } } } } diff --git a/tools/Prober/Schemas/footprint-line.schema.json b/tools/Prober/Schemas/footprint-line.schema.json index 8b233629..f5e41f2e 100644 --- a/tools/Prober/Schemas/footprint-line.schema.json +++ b/tools/Prober/Schemas/footprint-line.schema.json @@ -36,6 +36,16 @@ ] } }, + "watched": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "The paths the probe's registry entry watches, each with its object id at the commit the run built (null where it does not exist). A footprint line without one of the entry's watched paths is not current." + }, "toolchain": { "type": "object", "additionalProperties": { diff --git a/tools/Prober/Schemas/lock.schema.json b/tools/Prober/Schemas/lock.schema.json index 29f97911..5e9e290d 100644 --- a/tools/Prober/Schemas/lock.schema.json +++ b/tools/Prober/Schemas/lock.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codometis.typekit/probe/schemas/lock.schema.json", "title": "Round lock", - "description": "State (round.lock): one round at a time. Created exclusively; alive while its heartbeat is recent.", + "description": "State (branches/<branch>/round.lock): one round at a time per branch. Created exclusively; alive while its heartbeat is recent.", "type": "object", "required": [ "holder", diff --git a/tools/Prober/Schemas/registry.schema.json b/tools/Prober/Schemas/registry.schema.json index b78be1e9..3be5eb0a 100644 --- a/tools/Prober/Schemas/registry.schema.json +++ b/tools/Prober/Schemas/registry.schema.json @@ -41,6 +41,99 @@ }, "retired": { "type": "boolean" + }, + "watched": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "description": "Paths, files or directories relative to the repository root, that the probe's result depends on and its footprint cannot see: a variant catalogue under test/ compiled into the probes, a build input a child process reads. The replay records each with its object id in the probe's footprint line; a change to one stales the footprint (Ledger.FootprintCurrent, FootprintChangedSince), and the release gate counts a change to one as touching the probe's claim. Part of the probe's identity, as the whole entry but its seeds is." + }, + "seeds": { + "type": "array", + "description": "One seeded defect per obligation of the probe's claim that this probe checks (decision 104): each as `seed` below, naming the obligation it breaks. A probe may carry `seed` and `seeds` both. Empty in the entry `prober new` stages, until the writer registers each with `prober register --obligation O`, which replaces a seed naming O; `seeds` and Seeds.Problems refuse a probe with none. Not part of the probe's identity.", + "items": { + "type": "object", + "required": [ + "obligation", + "defect" + ], + "anyOf": [ + { + "required": [ + "patch" + ] + }, + { + "required": [ + "env" + ] + } + ], + "properties": { + "obligation": { + "type": "string", + "description": "The id of the claim's obligation this seed breaks (claims.json `obligations[].id`, or `claim` for a claim without the field)." + }, + "patch": { + "type": "string" + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "defect": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "seed": { + "type": "object", + "required": [ + "defect" + ], + "anyOf": [ + { + "required": [ + "patch" + ] + }, + { + "required": [ + "env" + ] + } + ], + "properties": { + "patch": { + "type": "string", + "description": "A unified diff against the product files (the replay's \"include\"), relative to the corpus directory: seeds/<claim>.patch." + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Environment variables set for the seeded build and every variant's run." + }, + "defect": { + "type": "string", + "minLength": 1, + "description": "What the seed breaks, in one sentence." + }, + "obligation": { + "type": "string", + "description": "The id of the claim's obligation this seed breaks (decision 104); absent, the seed proves the probe and counts for the claim's one implicit obligation only where the claim lists none." + } + }, + "additionalProperties": false, + "description": "The product defect `prober seeds` applies in a scratch worktree to prove the probe can fail (decision 100). Not part of the probe's identity." } }, "additionalProperties": false diff --git a/tools/Prober/Schemas/rows.schema.json b/tools/Prober/Schemas/rows.schema.json index d447dac3..290f9a7b 100644 --- a/tools/Prober/Schemas/rows.schema.json +++ b/tools/Prober/Schemas/rows.schema.json @@ -2,9 +2,10 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codometis.typekit/probe/schemas/rows.schema.json", "title": "Rows", - "description": "Tracked (corpus/rows/<claim>.json, gzipped): the rows a claim's probe runs, a pairwise covering array over its axes.", + "description": "Tracked (corpus/rows/<probe>.json.gz, gzipped; rows/<probe>.json in the staging): the rows one probe runs, a pairwise covering array over its claim's axes. One file per registered probe, written whole by `prober rows --probe P`, never appended; no two rows of a file have the same axis map (ProbeRowsTests).", "type": "object", "required": [ + "claim", "rows" ], "properties": { @@ -14,7 +15,8 @@ "description": "The schema version. A file or line without it is version 1 (the Python baseline)." }, "claim": { - "type": "string" + "type": "string", + "description": "The id of the probe's claim in claims.json; the file is named for the probe, so the claim is kept inside." }, "rows": { "type": "array", diff --git a/tools/Prober/Schemas/seeds-run.schema.json b/tools/Prober/Schemas/seeds-run.schema.json new file mode 100644 index 00000000..824b9919 --- /dev/null +++ b/tools/Prober/Schemas/seeds-run.schema.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codometis.typekit/probe/schemas/seeds-run.schema.json", + "title": "Seeds run", + "description": "State (seeds-last.json): the last `prober seeds` run on the branch, one outcome per seed it ran (a probe without one has a single outcome, `no seed`), the probes it skipped and why, and per probe the run that last caught every one of its seeds, which decides what the next run without --all skips (design/adr/0107). Never evidence: a seeded run proves the probe, not the product.", + "type": "object", + "required": [ + "branch", + "commit", + "seeds" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "The schema version. A file or line without it is version 1 (the Python baseline)." + }, + "branch": { + "type": "string" + }, + "commit": { + "type": "string" + }, + "seconds": { + "type": "number" + }, + "all": { + "type": "boolean", + "description": "Whether the run was `--all`, every probe whatever its record says, as a release runs it. Absent in a file written before footprint selection." + }, + "probes": { + "type": "object", + "description": "Per probe, the run that last caught every seed it owes; a probe with a seed missed, or that never ran, has no entry, so it runs next time. Carried forward for the probes a run skipped.", + "additionalProperties": { + "type": "object", + "required": [ + "identity", + "commit" + ], + "properties": { + "identity": { + "type": "string", + "description": "The seeds identity it was caught under: the probe's identity, its seeds and the content of their patches." + }, + "commit": { + "type": "string", + "description": "HEAD of the run that caught them; the product footprint and the patched files are compared with it." + }, + "seeds": { + "type": "integer", + "description": "How many seeds were caught." + } + }, + "additionalProperties": false + } + }, + "skipped": { + "type": "array", + "description": "The probes this run skipped, unchanged since every seed of theirs was caught, each with the reason printed.", + "items": { + "type": "object", + "required": [ + "probe", + "reason" + ], + "properties": { + "probe": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "seeds": { + "type": "array", + "items": { + "type": "object", + "required": [ + "probe", + "claim", + "result" + ], + "properties": { + "probe": { + "type": "string" + }, + "claim": { + "type": "string" + }, + "obligation": { + "type": [ + "string", + "null" + ], + "description": "The claim's obligation the seed breaks (decision 104), or null for a seed that names none." + }, + "seed": { + "type": "string", + "description": "The patch and the environment toggle applied, or (none)." + }, + "result": { + "enum": [ + "caught", + "missed", + "no seed", + "does not apply", + "build failed" + ] + }, + "violations": { + "type": "integer", + "description": "Rows the seed made violations that the unseeded probe does not have." + }, + "rows": { + "type": "integer", + "description": "Rows that ran under the seed." + }, + "detail": { + "type": "string" + } + }, + "additionalProperties": false + }, + "description": "One outcome per seed this run ran; the probes it skipped have none." + } + }, + "additionalProperties": false +} diff --git a/tools/Prober/Schemas/universe.schema.json b/tools/Prober/Schemas/universe.schema.json index b12bfc25..776635f3 100644 --- a/tools/Prober/Schemas/universe.schema.json +++ b/tools/Prober/Schemas/universe.schema.json @@ -73,6 +73,45 @@ "minItems": 2, "maxItems": 2 } + }, + "obligations": { + "type": "array", + "description": "written only where an obligation of the claim declares where it does not apply: every obligation's id with its declarations, as claims.json holds them, so the covering generator, status and the gate know the pairs each wants", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "inapplicable": { + "type": "array", + "items": { + "type": "object", + "required": [ + "axes", + "cite" + ], + "properties": { + "axes": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "cite": { + "type": "string" + } + } + } + } + } + } } } } diff --git a/tools/Prober/Seeds.cs b/tools/Prober/Seeds.cs new file mode 100644 index 00000000..92e5fe86 --- /dev/null +++ b/tools/Prober/Seeds.cs @@ -0,0 +1,396 @@ +using System.Diagnostics; +using System.Globalization; +using System.Text.Json.Nodes; + +namespace Prober; + +/// <summary> +/// Proves that each probe can fail (decision 100). A probe's registry entry names a seeded product defect: +/// a patch against the product files (the replay's <c>include</c>, <c>src</c> here), an environment toggle, +/// or both, and a sentence saying what it breaks. This applies each seed in a scratch worktree of its own, +/// builds, replays that probe there, and counts the seed caught only where a check ends as a violation that the +/// unseeded probe does not have in the same row for the same obligation and that no harness error raised, and, for a +/// seed naming an obligation, only a check naming that obligation (a row without checks is one check, <c>claim</c>). +/// Nothing it runs is recorded as evidence: a seeded run proves the probe, +/// not the product. Never in the checkout and never in the replay's worktree; one build per seed, so it runs +/// locally, not in CI. A missed seed, a probe without one, a seed that no longer applies and a seeded build +/// that fails all fail the command. Without <c>--all</c> it runs only the probes whose seeds identity or product +/// footprint changed since the run that last caught every seed of the probe (design/adr/0107), and says why it skips +/// each of the others; the release runs <c>--all</c>. +/// </summary> +internal static class Seeds +{ + /// <summary>How one seed ended; <paramref name="Obligation"/> is the claim's obligation it breaks, or null for a seed that proves the probe alone.</summary> + internal sealed record Outcome(string Probe, string Claim, string? Obligation, string Seed, string Result, int Violations, int Rows, string Detail); + + /// <summary> + /// Every seed of a registry entry, each with the obligation it names: the one `seed` (its `obligation`, if + /// any) and each of `seeds` (decision 104). An entry without either has none. + /// </summary> + internal static List<(string? Obligation, JsonObject Seed)> SeedsOf(JsonObject entry) + { + var result = new List<(string?, JsonObject)>(); + if (entry["seed"] is JsonObject one) result.Add((Files.Str(one["obligation"]) is { Length: > 0 } o ? o : null, one)); + foreach (var seed in (entry["seeds"] as JsonArray ?? []).OfType<JsonObject>()) + result.Add((Files.Str(seed["obligation"]) is { Length: > 0 } o ? o : null, seed)); + return result; + } + + private static string Named(string probe, string? obligation) => obligation is null ? probe : $"{probe}/{obligation}"; + + public static int Run(Context ctx, Args a, IProbeAdapter? adapter = null) + { + var ledger = new Ledger(ctx, a.Get("branch")); + var config = ledger.AxesBlock()["replay"] as JsonObject ?? throw new Refusal("the axes file's block has no \"replay\" configuration"); + adapter ??= new DotNetAdapter(config); + var registry = ledger.Registry(); + var states = ledger.ProbeStates(includeRetired: false).Where(s => !s.Retired).ToList(); + if (a.Get("probe") is { } only) + { + states = [.. states.Where(s => s.Probe == only)]; + if (states.Count == 0) throw new Refusal($"no registered probe {only} that is not retired", 2); + } + var head = ledger.Head; + var home = Replay.Home(ctx, ledger); + var worktree = Path.Combine(home, "seeds"); + var include = Files.Strings(config["include"]).ToArray() is { Length: > 0 } inc ? inc : ["src"]; + // The probes whose every seed the last runs caught, each at the commit and the identity it was caught at; a probe + // named by --probe, and every probe under --all, runs whatever its record says. + var last = Files.ReadJson(ledger.P(LastFile)) as JsonObject; + var record = (last?["probes"] as JsonObject ?? []).Where(kv => kv.Value is JsonObject).ToDictionary(kv => kv.Key, kv => (JsonObject)kv.Value!.DeepClone(), StringComparer.Ordinal); + var identities = states.ToDictionary(s => s.Probe, s => SeedsIdentity(ledger, s.Probe), StringComparer.Ordinal); + var skipped = new List<(string Probe, string Why)>(); + if (!a.Flag("all") && a.Get("probe") is null) + { + var footprints = ledger.Footprints(); + var runs = new List<Ledger.ProbeState>(); + foreach (var s in states) + { + var why = Stale(ledger, s.Probe, registry[s.Probe], record.GetValueOrDefault(s.Probe), identities[s.Probe], footprints); + if (why.Run) runs.Add(s); + else skipped.Add((s.Probe, why.Reason)); + ctx.Out.WriteLine($" {(why.Run ? "runs" : "skips")} {s.Probe}: {why.Reason}"); + } + states = runs; + } + var seedsOf = states.ToDictionary(s => s.Probe, s => SeedsOf(registry[s.Probe]), StringComparer.Ordinal); + // A probe without a seed owes one per obligation its claim lists (decision 104), each an outcome of its own; + // under a claim without obligations, one. + var listed = ledger.Claims().ToDictionary(c => Files.Str(c["id"]), Claim.ObligationIds, StringComparer.Ordinal); + List<(string? Obligation, JsonNode? Seed)> Owed(Ledger.ProbeState s) => + seedsOf[s.Probe].Count > 0 ? [.. seedsOf[s.Probe].Select(x => (x.Obligation, (JsonNode?)x.Seed))] + : listed.GetValueOrDefault(s.Claim) is { } ids && ids is not [Claim.ImplicitObligation] ? [.. ids.Select(id => ((string?)id, (JsonNode?)null))] + : [(null, null)]; + ctx.Out.WriteLine($"prober seeds: {ledger.Branch} at {head[..7]}: {seedsOf.Values.Sum(x => x.Count)} seeds of {states.Count} probes, in {worktree}" + + (skipped.Count > 0 ? $"; {skipped.Count} probes skipped, unchanged since every seed of theirs was caught (--all runs them)" : "")); + if (a.Flag("dry-run")) + { + foreach (var s in states) + { + foreach (var (obligation, seed) in Owed(s)) ctx.Out.WriteLine($" {Named(s.Probe, obligation)}: {Describe(seed)}"); + } + return 0; + } + + var clock = Stopwatch.StartNew(); + var outcomes = new List<Outcome>(); + // Nothing to run: no worktree and no build, only the record carried forward. + if (states.Count > 0) + { + Directory.CreateDirectory(home); + if (!Directory.Exists(worktree)) + { + var added = Git.Run(ledger.Root, "worktree", "add", "--detach", worktree, head); + if (added.ExitCode != 0) throw new Refusal("could not add the scratch worktree: " + added.Stderr.Trim()); + } + var environment = ProbeRun.Environment(ctx, adapter, home); + var results = Directory.CreateTempSubdirectory("prober-seeds-").FullName; + try + { + // The baseline: the checks each probe already fails without a seed, by row and obligation. From the ledger + // where its evidence is current, else from an unseeded run here, so a seed is credited only with violations + // of its own. + var baseline = new Dictionary<string, HashSet<(int Row, string Obligation)>>(StringComparer.Ordinal); + var unseeded = new List<Ledger.ProbeState>(); + foreach (var s in states) + { + if (!s.Stale && Files.ReadJson(ledger.EvidencePath(s.Probe)) is JsonObject evidence) + baseline[s.Probe] = [.. (evidence["rows"] as JsonObject ?? []).Where(kv => kv.Value is JsonObject) + .SelectMany(kv => Ledger.ChecksOf((JsonObject)kv.Value!).Where(c => c.Category == "violation") + .Select(c => (int.Parse(kv.Key, CultureInfo.InvariantCulture), c.Obligation)))]; + else unseeded.Add(s); + } + ctx.Out.WriteLine($" baseline: {states.Count - unseeded.Count} from the ledger, {unseeded.Count} to run unseeded"); + if (unseeded.Count > 0) + { + Reset(worktree, head); + Replay.Overlay(ledger, config, worktree); + var build = Timed(() => adapter.Build(worktree, environment), out var seconds); + ctx.Out.WriteLine($" unseeded build: {Seconds(seconds)}, {FirstLine(build)}"); + if (build.ExitCode != 0) { ctx.Out.WriteLine(build.Stdout); throw new Refusal("the unseeded build failed; no seed can be judged"); } + foreach (var s in unseeded) + { + var rows = RunProbe(adapter, worktree, s, environment, null, results, "base"); + baseline[s.Probe] = Violating(rows); + ctx.Out.WriteLine($" {s.Probe}: {rows.Count} rows, {baseline[s.Probe].Select(x => x.Row).Distinct().Count()} violations unseeded"); + } + } + + foreach (var s in states) + { + // A seed missing is an outcome "no seed"; a probe with several runs each under its obligation. + foreach (var (obligation, seed) in Owed(s)) + { + var outcome = One(ledger, config, adapter, worktree, head, include, environment, results, s, obligation, seed, baseline[s.Probe]); + outcomes.Add(outcome); + ctx.Out.WriteLine($" {Named(s.Probe, obligation)} ({s.Claim}): {outcome.Result}{(outcome.Detail.Length > 0 ? ", " + outcome.Detail : "")}"); + } + } + } + finally + { + // The worktree is left at HEAD, every seed undone, for the next run to build incrementally. + Reset(worktree, head); + try { Directory.Delete(results, recursive: true); } catch (IOException) { } + } + } + + // A probe is recorded only where every seed it owes was caught, so a missed one runs again next time, whatever changed. + foreach (var s in states) + { + var own = outcomes.Where(o => o.Probe == s.Probe).ToList(); + if (own.Count > 0 && own.All(o => o.Result == "caught")) + record[s.Probe] = new JsonObject { ["identity"] = identities[s.Probe], ["commit"] = head, ["seeds"] = own.Count }; + else record.Remove(s.Probe); + } + foreach (var gone in record.Keys.Where(probe => !registry.ContainsKey(probe)).ToList()) record.Remove(gone); + + Files.WriteJson(ledger.P(LastFile), new JsonObject + { + ["schemaVersion"] = Files.SchemaVersion, ["branch"] = ledger.Branch, ["commit"] = head, + ["seconds"] = Math.Round(clock.Elapsed.TotalSeconds, 1), + ["all"] = a.Flag("all"), + ["probes"] = new JsonObject(record.OrderBy(kv => kv.Key, StringComparer.Ordinal).Select(kv => KeyValuePair.Create(kv.Key, (JsonNode?)kv.Value))), + ["skipped"] = new JsonArray([.. skipped.Select(x => (JsonNode)new JsonObject { ["probe"] = x.Probe, ["reason"] = x.Why })]), + ["seeds"] = new JsonArray([.. outcomes.Select(o => (JsonNode)new JsonObject + { + ["probe"] = o.Probe, ["claim"] = o.Claim, ["obligation"] = o.Obligation, ["seed"] = o.Seed, ["result"] = o.Result, + ["violations"] = o.Violations, ["rows"] = o.Rows, ["detail"] = o.Detail, + })]), + }); + var caught = outcomes.Count(o => o.Result == "caught"); + var counts = outcomes.GroupBy(o => o.Result, StringComparer.Ordinal).OrderBy(g => g.Key, StringComparer.Ordinal).Select(g => $"{g.Count()} {g.Key}"); + ctx.Out.WriteLine($"prober seeds: {string.Join(", ", counts)}, {Seconds(clock.Elapsed.TotalSeconds)} in all"); + return caught == outcomes.Count ? 0 : 1; + } + + public const string LastFile = "seeds-last.json"; + + /// <summary> + /// What a probe's seeded runs depend on besides the product: the probe's identity (<see cref="Ledger.Identity"/>: + /// its registry entry, the harness and probe sources, its rows, the axes file, the build environment, the tool), + /// its seeds, which that identity leaves out, and the content of each patch they apply. + /// </summary> + internal static string SeedsIdentity(Ledger ledger, string probe) + { + using var sha = System.Security.Cryptography.IncrementalHash.CreateHash(System.Security.Cryptography.HashAlgorithmName.SHA256); + void Add(string text) => sha.AppendData(System.Text.Encoding.UTF8.GetBytes(text + "\0")); + Add(ledger.Identity(probe)); + foreach (var (obligation, seed) in SeedsOf(ledger.Registry().GetValueOrDefault(probe) ?? [])) + { + Add(obligation ?? "-"); + Add(Files.Canonical(seed)); + var patch = Files.Str(seed["patch"]); + if (patch.Length > 0 && File.Exists(ledger.CorpusPath(patch))) sha.AppendData(File.ReadAllBytes(ledger.CorpusPath(patch))); + } + return Convert.ToHexStringLower(sha.GetHashAndReset())[..16]; + } + + /// <summary> + /// Whether a probe's seeds run without <c>--all</c>, and why: they run unless every seed was caught at a recorded + /// commit, under the seeds identity the probe has now, and neither the product footprint the last replay recorded for + /// it nor a file its seeds patch changed since that commit. A probe without a recorded footprint runs. + /// </summary> + internal static (bool Run, string Reason) Stale(Ledger ledger, string probe, JsonObject entry, JsonObject? caught, string identity, Dictionary<string, JsonObject> footprints) + { + if (caught is null) return (true, "no run caught every seed of it yet"); + var commit = Files.Str(caught["commit"]); + var at = commit.Length >= 7 ? commit[..7] : commit; + if (Files.Str(caught["identity"]) != identity) return (true, $"its identity or its seeds changed since they were caught at {at}"); + if (!footprints.ContainsKey(probe)) return (true, "no product footprint recorded for it (a replay records one)"); + if (ledger.FootprintChangedSince(probe, commit, footprints)) return (true, $"its product footprint changed since {at}"); + var patched = SeedsOf(entry).Select(x => Files.Str(x.Seed["patch"])).Where(p => p.Length > 0 && File.Exists(ledger.CorpusPath(p))) + .SelectMany(p => Patched(File.ReadAllText(ledger.CorpusPath(p)))).Distinct(StringComparer.Ordinal).ToArray(); + if (patched.Length > 0 && Git.Run(ledger.Top, ["diff", "--quiet", commit, "HEAD", "--", .. patched]).ExitCode != 0) + return (true, $"a file its seeds patch changed since {at}"); + return (false, $"every seed caught at {at}, and neither its identity nor its product footprint changed since"); + } + + /// <summary>The files a patch changes, as its <c>+++ b/</c> lines name them.</summary> + private static IEnumerable<string> Patched(string patch) => + patch.Split('\n').Where(line => line.StartsWith("+++ b/", StringComparison.Ordinal)).Select(line => line["+++ b/".Length..].TrimEnd('\r')); + + /// <summary>Applies one probe's seed, builds, runs the probe, and undoes the seed.</summary> + private static Outcome One(Ledger ledger, JsonObject config, IProbeAdapter adapter, string worktree, string head, string[] include, + Dictionary<string, string?> environment, string results, Ledger.ProbeState s, string? obligation, JsonNode? seed, + HashSet<(int Row, string Obligation)> baseline) + { + Outcome End(string result, string detail, int violations = 0, int rows = 0) => new(s.Probe, s.Claim, obligation, Describe(seed), result, violations, rows, detail); + + Reset(worktree, head); + Replay.Overlay(ledger, config, worktree); + var (refused, detail, patch, env) = Validate(ledger, include, seed, worktree); + if (refused is not null) return End(refused, detail); + if (patch is not null) + { + var ran = Git.Run(worktree, "apply", "--whitespace=nowarn", patch); + if (ran.ExitCode != 0) return End("does not apply", FirstLine(ran.Stderr)); + } + try + { + var seeded = new Dictionary<string, string?>(environment, StringComparer.Ordinal); + foreach (var (k, v) in env ?? []) seeded[k] = Files.Str(v); + var build = Timed(() => adapter.Build(worktree, seeded), out var buildSeconds); + if (build.ExitCode != 0) return End("build failed", $"the seeded build: {FirstLine(build)}"); + var rows = RunProbe(adapter, worktree, s, environment, env, results, "seed"); + // A check that broke the harness says nothing about the claim, even where it also returned a failure; a + // seed naming an obligation is caught only by a check naming it (decision 104), a seed naming none by any. + var fresh = rows.SelectMany(kv => kv.Value.Checks.Select(c => (Row: kv.Key, Check: c))) + .Where(x => x.Check.CleanViolation && !baseline.Contains((x.Row, x.Check.Obligation))).OrderBy(x => x.Row).ToList(); + var own = fresh.Where(x => obligation is null || x.Check.Obligation == obligation).ToList(); + var caughtRows = own.Select(x => x.Row).Distinct().Count(); + var tally = string.Join(", ", rows.Values.GroupBy(r => r.Category, StringComparer.Ordinal).OrderBy(g => g.Key, StringComparer.Ordinal).Select(g => $"{g.Key} {g.Count()}")); + var broken = rows.Values.Count(r => r.HarnessError); + var harness = broken > 0 ? $"; {broken} ended in a harness error, which is no catch" : ""; + // Violations by checks of other obligations are no catch of this one; say whose they were, a row without checks being `claim`'s. + var others = fresh.Count > own.Count + ? $"; {fresh.Select(x => x.Row).Distinct().Count()} violate by a check naming {string.Join(", ", fresh.Select(x => x.Check.Obligation).Distinct().Order(StringComparer.Ordinal))}, not {obligation}" + : ""; + if (own.Count == 0) return End("missed", $"no row violates that did not already ({rows.Count} rows: {(tally.Length > 0 ? tally : "none ran")}{harness}{others}; build {Seconds(buildSeconds)})", 0, rows.Count); + var example = own[0]; + var named = example.Check.Obligation == Claim.ImplicitObligation && rows[example.Row].Checks.Count == 1 ? "" : " " + example.Check.Obligation; + return End("caught", $"{caughtRows} of {rows.Count} rows violate; e.g. #{example.Row}{named}: {Cut(example.Check.Reason)}", caughtRows, rows.Count); + } + finally + { + if (patch is not null) Git.Run(worktree, "apply", "-R", "--whitespace=nowarn", patch); + } + } + + /// <summary> + /// Why a seed cannot be applied in <paramref name="worktree"/> (a result and a detail), or a null result with + /// the patch's full path and the environment toggle to apply. A seed names its defect and a patch, an + /// environment toggle or both; a patch exists in the corpus, touches only the product files and applies. + /// </summary> + private static (string? Refused, string Detail, string? Patch, JsonObject? Env) Validate(Ledger ledger, string[] include, JsonNode? seed, string worktree) + { + if (seed is not JsonObject entry) return ("no seed", "its registry entry names none", null, null); + var patch = Files.Str(entry["patch"]); + var env = entry["env"] as JsonObject; + if (Files.Str(entry["defect"]).Length == 0 || (patch.Length == 0 && env is not { Count: > 0 })) + return ("no seed", "a seed names its defect and a patch or an environment toggle", null, null); + if (patch.Length == 0) return (null, "", null, env); + + var file = Path.GetFullPath(ledger.CorpusPath(patch)); + if (!File.Exists(file)) return ("does not apply", $"{patch} is not in the corpus", null, null); + var touched = Git.Run(worktree, "apply", "--numstat", file); + if (touched.ExitCode != 0) return ("does not apply", FirstLine(touched.Stderr), null, null); + var outside = touched.Stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(line => line.Split('\t')[^1].Trim()) + .Where(path => !include.Any(p => path.StartsWith(p.TrimEnd('/') + "/", StringComparison.Ordinal))).ToList(); + if (outside.Count > 0) return ("does not apply", $"it patches {string.Join(", ", outside)}, outside the product files ({string.Join(", ", include)})", null, null); + var check = Git.Run(worktree, "apply", "--check", file); + return check.ExitCode != 0 ? ("does not apply", FirstLine(check.Stderr), null, null) : (null, "", file, env); + } + + /// <summary> + /// What `seeds` would refuse before building anything, against this checkout: a probe without a seed, or a + /// seed that does not apply. No build, so the tests run it on every build and a probe registered without a + /// seed, or a product change that leaves a seed behind, fails there and not only at the next local run. + /// </summary> + internal static List<string> Problems(Ledger ledger) + { + var config = ledger.AxesBlock()["replay"] as JsonObject ?? []; + var include = Files.Strings(config["include"]).ToArray() is { Length: > 0 } inc ? inc : ["src"]; + var problems = new List<string>(); + // The obligations each claim lists (or its one implicit obligation), and the seeds that name each. + var obligations = ledger.Claims().ToDictionary(c => Files.Str(c["id"]), Claim.ObligationIds, StringComparer.Ordinal); + var seeded = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); + foreach (var (probe, entry) in ledger.Registry()) + { + if (entry["retired"] is JsonValue retired && retired.TryGetValue<bool>(out var r) && r) continue; + var claim = Files.Str(entry["claim"]); + var seeds = SeedsOf(entry); + if (seeds.Count == 0) { problems.Add($"{probe}: no seed, its registry entry names none"); continue; } + foreach (var (obligation, seed) in seeds) + { + if (Validate(ledger, include, seed, ledger.Top) is { Refused: { } refused, Detail: var detail }) + problems.Add($"{Named(probe, obligation)}: {refused}, {detail}"); + var ids = obligations.GetValueOrDefault(claim) ?? [Claim.ImplicitObligation]; + // A seed naming no obligation proves the probe; it counts for the claim's one implicit obligation only. + var counts = obligation ?? (ids is [Claim.ImplicitObligation] ? Claim.ImplicitObligation : null); + if (counts is null) continue; + if (!ids.Contains(counts)) { problems.Add($"{Named(probe, obligation)}: names obligation {counts}, which claim {claim} does not list"); continue; } + (seeded.TryGetValue(claim, out var set) ? set : seeded[claim] = new(StringComparer.Ordinal)).Add(counts); + } + } + // Every listed obligation of a claim that has a probe is seeded by some probe of that claim (decision 104); + // the implicit obligation is covered by the rule above that every probe has a seed. + var claimed = ledger.Registry().Values.Select(e => Files.Str(e["claim"])).ToHashSet(StringComparer.Ordinal); + foreach (var (claim, ids) in obligations.OrderBy(kv => kv.Key, StringComparer.Ordinal)) + { + if (ids is [Claim.ImplicitObligation] || !claimed.Contains(claim)) continue; + foreach (var id in ids.Where(id => !(seeded.GetValueOrDefault(claim)?.Contains(id) ?? false))) + problems.Add($"{claim}: obligation {id} has no seed in any of its probes"); + } + return problems; + } + + /// <summary>The (row, obligation) of every check that ended as a violation, clean or not: what a seed's violations are measured against.</summary> + private static HashSet<(int Row, string Obligation)> Violating(Dictionary<int, ProbeRun.RowOutcome> rows) => + [.. rows.SelectMany(kv => kv.Value.Checks.Where(c => c.Category == "violation").Select(c => (kv.Key, c.Obligation)))]; + + /// <summary>Runs one probe in every one of its variants and reads how each row ended.</summary> + private static Dictionary<int, ProbeRun.RowOutcome> RunProbe(IProbeAdapter adapter, string worktree, Ledger.ProbeState s, Dictionary<string, string?> environment, + JsonObject? seedEnv, string results, string tag) + { + var rows = new Dictionary<int, ProbeRun.RowOutcome>(); + var index = 0; + foreach (var variant in s.Variants.OfType<JsonObject>()) + { + var name = $"{s.Probe}-{tag}-{index}"; + var (_, outcomes) = ProbeRun.Once(adapter, worktree, s.Filter, results, name, name + ".cobertura.xml", ProbeRun.Variant(environment, variant, seedEnv)); + foreach (var (idx, outcome) in outcomes) rows[idx] = outcome; + index++; + } + return rows; + } + + /// <summary>Puts the scratch worktree's tracked files back at HEAD; the copied corpus, untracked, stays.</summary> + private static void Reset(string worktree, string head) + { + if (Git.Run(worktree, "checkout", "-q", "--detach", head).ExitCode != 0 && Git.Run(worktree, "reset", "-q", "--hard", head).ExitCode != 0) + throw new Refusal($"could not move the scratch worktree {worktree} to {head[..7]}"); + Git.Run(worktree, "reset", "-q", "--hard", head); + } + + private static string Describe(JsonNode? seed) => + seed is JsonObject o + ? string.Join(" + ", new[] { Files.Str(o["patch"]), o["env"] is JsonObject env ? string.Join(",", env.Select(kv => $"{kv.Key}={Files.Str(kv.Value)}")) : "" }.Where(x => x.Length > 0)) + : "(none)"; + + private static Ran Timed(Func<Ran> run, out double seconds) + { + var clock = Stopwatch.StartNew(); + var ran = run(); + seconds = clock.Elapsed.TotalSeconds; + return ran; + } + + private static string Seconds(double seconds) => seconds.ToString("F1", CultureInfo.InvariantCulture) + " s"; + + private static string Cut(string s) => string.Join(" ", s.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)) is var line && line.Length > 160 ? line[..160] + "..." : line; + + private static string FirstLine(Ran ran) => ProbeRun.FirstLine(ran); + + private static string FirstLine(string text) => text.Trim().Split('\n')[0].Trim(); +} diff --git a/tools/Prober/SourceLint.cs b/tools/Prober/SourceLint.cs new file mode 100644 index 00000000..6b34502b --- /dev/null +++ b/tools/Prober/SourceLint.cs @@ -0,0 +1,277 @@ +using System.Text; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace Prober; + +/// <summary> +/// What `register` and `publish` refuse in the staged probe sources before anything is built (decision 103): +/// a <c>Note</c> whose first argument is not a key of the catalogue (<c>NoteKeys.Name</c>, a member the +/// corpus or the staging declares), and a two-argument <c>Skip</c> (the harness's, not LINQ's) whose first +/// argument is not a string literal naming a finding this ledger holds open. The compiler refuses a key +/// outside the catalogue too; this says so by file and line before a build, and holds the finding ids, +/// which stay strings. A check's <c>Inapplicable</c> and <c>Refused</c> cite their reason by a <c>Cite</c> +/// factory with a string literal, and what it names exists: a not-promised entry of the probe's claim (of any +/// claim, for a harness source no one probe owns), a file of the docs, a knowledge entry under design/knowledge/, +/// a record under design/adr/. It reads code with comments and string literals blanked out, so a call written in +/// either does not count. +/// </summary> +internal static partial class SourceLint +{ + /// <summary>The catalogue's members, as <c>harness/Row.cs</c> declares them.</summary> + [GeneratedRegex(@"public\s+static\s+readonly\s+NoteKeys\s+(\w+)\s*=")] + private static partial Regex CatalogueMember(); + + [GeneratedRegex(@"\.\s*(Note|Skip|Inapplicable|Refused)\s*\(")] + private static partial Regex Call(); + + [GeneratedRegex("^Cite\\s*\\.\\s*(NotPromised|Docs|Fact|Record)\\s*\\(\\s*\"([^\"\\\\]*)\"\\s*\\)$")] + private static partial Regex CiteFactory(); + + [GeneratedRegex(@"^NoteKeys\s*\.\s*(\w+)$")] + private static partial Regex CatalogueKey(); + + [GeneratedRegex("^\"([^\"\\\\]*)\"$")] + private static partial Regex PlainLiteral(); + + /// <summary>Refuses, naming each file and line, while a staged source breaks a rule; does nothing without a staging.</summary> + public static void Refuse(Ledger l, string command) + { + var problems = Problems(l); + if (problems.Count > 0) + throw new Refusal($"{command}: the staged sources make a note, a skip or a citation that is not written out (decision 103):\n " + string.Join("\n ", problems)); + } + + /// <summary>Every problem in the staged harness and probe sources, with the finding ids checked against this ledger.</summary> + public static List<string> Problems(Ledger l) + { + var staged = Sources(l.StagingDir).ToList(); + if (staged.Count == 0) return []; + var catalogue = new HashSet<string>(StringComparer.Ordinal); + foreach (var file in Sources(l.CorpusDir).Concat(staged)) + foreach (Match m in CatalogueMember().Matches(Mask(File.ReadAllText(file)))) catalogue.Add(m.Groups[1].Value); + var findings = l.Findings(); + // The not-promised ids of each claim, and the claim of each registered probe: a probe's source cites its own + // claim's entries, a harness source that no one probe owns any claim's. + var notPromised = l.Claims().ToDictionary(c => Files.Str(c["id"]), + c => (IReadOnlySet<string>)(c["not-promised"] as JsonArray ?? []).OfType<JsonObject>().Select(n => Files.Str(n["id"])).ToHashSet(StringComparer.Ordinal), StringComparer.Ordinal); + // A probe's source is named for it, or for the class its filter selects (*C1Probe). + var claimOf = new Dictionary<string, string>(StringComparer.Ordinal); + foreach (var (name, entry) in l.Registry()) + { + claimOf[name] = Files.Str(entry["claim"]); + if (Files.Str(entry["filter"]).TrimStart('*') is { Length: > 0 } cls) claimOf.TryAdd(cls, Files.Str(entry["claim"])); + } + var problems = new List<string>(); + foreach (var file in staged) + { + // Relative to the checkout where the state lives in it, else in full (PROBE_STATE_DIR elsewhere). + var path = Path.GetRelativePath(l.Top, file) is var rel && !rel.StartsWith("..", StringComparison.Ordinal) ? rel.Replace('\\', '/') : file; + var cites = new Citations(l.Top, claimOf.TryGetValue(Path.GetFileNameWithoutExtension(file), out var claim) ? claim : null, notPromised); + foreach (var (line, problem, finding) in Scan(File.ReadAllText(file), catalogue, cites.Problem)) + { + if (problem is not null) problems.Add($"{path}:{line}: {problem}"); + else if (finding is not null && Status(findings, finding) is { } status) + problems.Add($"{path}:{line}: Skip(\"{finding}\", ...) names {status}; a skip steps around an open finding only"); + } + } + return problems; + } + + private static string? Status(Dictionary<string, System.Text.Json.Nodes.JsonObject> findings, string id) => + !findings.TryGetValue(id, out var f) ? "no finding this ledger has" + : Files.Str(f["status"]) is var status && status != "open" ? $"finding {id}, which is {status}" + : null; + + private static IEnumerable<string> Sources(string root) => + new[] { "harness", "probes" }.Select(sub => Path.Combine(root, sub)).Where(Directory.Exists) + .SelectMany(dir => Directory.EnumerateFiles(dir, "*.cs")).Order(StringComparer.Ordinal); + + /// <summary> + /// What a citation of one source may name, and why one does not: <see cref="Problem"/> takes the factory and its + /// literal and says what is missing, or null. <paramref name="Claim"/> is the claim of the probe the source is, or + /// null for a source that no one probe owns, which may cite any claim's not-promised entry. + /// </summary> + internal sealed record Citations(string Top, string? Claim, IReadOnlyDictionary<string, IReadOnlySet<string>> NotPromised) + { + public string? Problem(string factory, string value) + { + switch (factory) + { + case "NotPromised" when Claim is not null: + return NotPromised.GetValueOrDefault(Claim)?.Contains(value) ?? false ? null : $"names no not-promised entry of its claim {Claim}"; + case "NotPromised": + return NotPromised.Values.Any(ids => ids.Contains(value)) ? null : "names no not-promised entry of any claim"; + case "Docs": + var file = value.Split('#')[0]; + return file.Length > 0 && !file.Contains("..", StringComparison.Ordinal) && File.Exists(Path.Combine(Top, file)) ? null : $"names no file {file} in the repository"; + case "Fact": + return Named(Path.Combine(Top, "design", "knowledge"), value) ? null : "names no entry under design/knowledge/"; + default: + return Named(Path.Combine(Top, "design", "adr"), value) ? null : "names no record under design/adr/"; + } + } + + /// <summary>A file of the directory named for the id: <c>KB-031-...md</c>, <c>0094-...md</c>.</summary> + private static bool Named(string directory, string id) => + id.Length > 0 && Directory.Exists(directory) + && Directory.EnumerateFiles(directory, "*.md").Any(f => Path.GetFileName(f).StartsWith(id + "-", StringComparison.Ordinal) || Path.GetFileName(f) == id + ".md"); + } + + /// <summary> + /// Each <c>Note</c>, two-argument <c>Skip</c>, and <c>Inapplicable</c> or <c>Refused</c> with an argument, of one + /// source: its line, the rule it breaks (or null), and for a skip written out, the finding it names. + /// <paramref name="cite"/> says what a citation's factory and literal fail to name; without it only the form is held. + /// </summary> + internal static IEnumerable<(int Line, string? Problem, string? Finding)> Scan(string source, IReadOnlySet<string> catalogue, Func<string, string, string?>? cite = null) + { + var masked = Mask(source); + foreach (Match m in Call().Matches(masked)) + { + var open = m.Index + m.Length - 1; + var arguments = Arguments(masked, source, open); + if (arguments is null) continue; + var line = 1 + masked.AsSpan(0, m.Index).Count('\n'); + var first = arguments.Count > 0 ? arguments[0] : ""; + if (m.Groups[1].Value is "Inapplicable" or "Refused") + { + if (arguments.Count == 0) continue; // an entry's own Refused() of the harness, not a check's + var call = m.Groups[1].Value; + var factory = CiteFactory().Match(first); + if (!factory.Success) yield return (line, $"{call}({Shown(first)}, ...) does not cite by a Cite factory with a string literal", null); + else if (cite?.Invoke(factory.Groups[1].Value, factory.Groups[2].Value) is { } missing) + yield return (line, $"{call}({Shown(first)}, ...) {missing}", null); + } + else if (m.Groups[1].Value == "Note") + { + var key = CatalogueKey().Match(first); + if (!key.Success) yield return (line, $"Note({Shown(first)}, ...) does not name a key of NoteKeys", null); + else if (!catalogue.Contains(key.Groups[1].Value)) yield return (line, $"Note({Shown(first)}, ...) names no key of NoteKeys", null); + } + else if (arguments.Count == 2) + { + var id = PlainLiteral().Match(first); + yield return id.Success ? (line, null, id.Groups[1].Value) : (line, $"Skip({Shown(first)}, ...) does not name its finding as a string literal", null); + } + } + } + + private static string Shown(string argument) => Regex.Replace(argument, @"\s+", " ") is var s && s.Length > 60 ? s[..60] + "..." : s; + + /// <summary>The top-level arguments of the call whose parenthesis opens at <paramref name="open"/>, from the original text; null if it never closes.</summary> + private static List<string>? Arguments(string masked, string source, int open) + { + var arguments = new List<string>(); + var depth = 0; var start = open + 1; + for (var i = open + 1; i < masked.Length; i++) + { + switch (masked[i]) + { + case '(' or '[' or '{': depth++; break; + case ')' or ']' or '}' when depth > 0: depth--; break; + case ',' when depth == 0: arguments.Add(source[start..i].Trim()); start = i + 1; break; + case ')': + var last = source[start..i].Trim(); + if (last.Length > 0 || arguments.Count > 0) arguments.Add(last); + return arguments; + } + } + return null; + } + + /// <summary> + /// The source with every comment and the content of every string and character literal blanked out (line + /// breaks kept, so lines still count), and the code of an interpolation hole kept as code. + /// </summary> + internal static string Mask(string s) + { + var m = new StringBuilder(s); + Code(s, m, 0, hole: false); + return m.ToString(); + } + + private static void Blank(StringBuilder m, int from, int to) + { + for (var i = from; i < to && i < m.Length; i++) if (m[i] != '\n') m[i] = ' '; + } + + /// <summary>Scans code from <paramref name="i"/>; in a hole, returns the index after its closing brace.</summary> + private static int Code(string s, StringBuilder m, int i, bool hole) + { + var depth = 0; + while (i < s.Length) + { + var c = s[i]; + if (c == '/' && i + 1 < s.Length && s[i + 1] == '/') + { + var end = s.IndexOf('\n', i); end = end < 0 ? s.Length : end; + Blank(m, i, end); i = end; + } + else if (c == '/' && i + 1 < s.Length && s[i + 1] == '*') + { + var end = s.IndexOf("*/", i + 2, StringComparison.Ordinal); end = end < 0 ? s.Length : end + 2; + Blank(m, i, end); i = end; + } + else if (c == '\'') + { + var j = i + 1; + while (j < s.Length && s[j] != '\'' && s[j] != '\n') j += s[j] == '\\' ? 2 : 1; + Blank(m, i + 1, j); i = j + 1; + } + else if (c is '"' or '$' or '@' && StringStart(s, i) is { } literal) i = String(s, m, i, literal); + else if (hole && c == '{') { depth++; i++; } + else if (hole && c == '}') { if (depth-- == 0) return i + 1; i++; } + else i++; + } + return i; + } + + private sealed record Literal(int Dollars, bool Verbatim, int Quotes, int QuoteAt); + + /// <summary>The literal that starts at <paramref name="i"/>: its <c>$</c> count, <c>@</c>, and run of quotes.</summary> + private static Literal? StringStart(string s, int i) + { + if (i > 0 && (char.IsLetterOrDigit(s[i - 1]) || s[i - 1] == '_') && s[i] != '"') return null; + var j = i; var dollars = 0; var verbatim = false; + while (j < s.Length && (s[j] == '$' || s[j] == '@')) { if (s[j] == '$') dollars++; else verbatim = true; j++; } + if (j >= s.Length || s[j] != '"') return null; + var quotes = 0; + while (j + quotes < s.Length && s[j + quotes] == '"') quotes++; + return new Literal(dollars, verbatim, quotes >= 3 ? quotes : 1, j); + } + + /// <summary>Blanks one literal's content, keeping its holes as code; returns the index after it.</summary> + private static int String(string s, StringBuilder m, int i, Literal literal) + { + var j = literal.QuoteAt + literal.Quotes; + if (literal.Quotes >= 3) + { + // A raw literal ends at a run of as many quotes as it opened with. Holes in it hold no note or skip + // (both return nothing), so it is blanked whole. + var close = new string('"', literal.Quotes); + var end = s.IndexOf(close, j, StringComparison.Ordinal); end = end < 0 ? s.Length : end; + Blank(m, j, end); + return end + literal.Quotes; + } + var from = j; + while (j < s.Length) + { + var c = s[j]; + if (literal.Verbatim && c == '"' && j + 1 < s.Length && s[j + 1] == '"') { j += 2; continue; } + if (!literal.Verbatim && c == '\\') { j += 2; continue; } + if (c == '"') { Blank(m, from, j); return j + 1; } + if (literal.Dollars > 0 && c == '{') + { + if (j + 1 < s.Length && s[j + 1] == '{') { j += 2; continue; } + Blank(m, from, j + 1); + j = Code(s, m, j + 1, hole: true); + from = j - 1; + continue; + } + if (!literal.Verbatim && c == '\n') break; + j++; + } + Blank(m, from, j); + return j; + } +} diff --git a/tools/Prober/TryProbe.cs b/tools/Prober/TryProbe.cs new file mode 100644 index 00000000..ba6e36cc --- /dev/null +++ b/tools/Prober/TryProbe.cs @@ -0,0 +1,151 @@ +using System.Diagnostics; +using System.Globalization; +using System.Text.Json.Nodes; + +namespace Prober; + +/// <summary> +/// <c>try</c>: the writer's loop for one probe. It moves the replay's worktree to HEAD, overlays the corpus and +/// the branch's staging there as the replay does, builds once through the adapter (not with <c>--no-build</c>), +/// and runs the one probe in each of its registry variants; <c>--rows</c> keeps the rows given, run through the +/// harness's <c>PROBE_ROWS</c> one stretch of consecutive rows at a time, within the rows each variant's +/// <c>PROBE_ONLY</c> selects. It prints each run's first line (the verdict's) and how each row ended, from the +/// jsonl the harness writes. It records nothing, no evidence, footprint or run, so nothing under the state +/// directory changes, and takes no round lock, but it holds the replay's worktree (<see cref="Replay.HoldWorktree"/>) +/// while it runs: a try and a replay of the same checkout never build in it at once, and a try refuses while a +/// replay holds it. Its exit code is the probe's: the first run's that failed, else 0; 1 when the build fails. +/// </summary> +internal static class TryProbe +{ + public static int Run(Context ctx, Args a, IProbeAdapter? adapter = null) + { + var ledger = new Ledger(ctx, a.Get("branch")); + var config = ledger.AxesBlock()["replay"] as JsonObject ?? throw new Refusal("the axes file's block has no \"replay\" configuration"); + adapter ??= new DotNetAdapter(config); + var probe = a.Need("probe"); + if (!ledger.Registry().TryGetValue(probe, out var entry)) + throw new Refusal($"no probe {probe} in the registry, tracked or staged; `prober new` stages one", 2); + var claim = Files.Str(entry["claim"]); + var filter = Files.Str(entry["filter"]); + + // What each variant runs: the rows its PROBE_ONLY selects, those of --rows among them, in stretches. + var probeRows = ledger.RowsOf(probe); + var asked = Indices(a.Get("rows"), probeRows.Count); + var plan = new List<(JsonObject Variant, List<int> Rows, List<string?> Stretches)>(); + foreach (var variant in Ledger.VariantsOf(entry).OfType<JsonObject>()) + { + var only = Files.Str(variant["PROBE_ONLY"]); + var selected = Enumerable.Range(0, probeRows.Count).Where(i => Replay.Selected(Files.StringMap(probeRows[i]?["row"] ?? probeRows[i]), only)).ToList(); + if (asked is null) { plan.Add((variant, selected, [null])); continue; } + var rows = selected.Where(asked.Contains).ToList(); + if (rows.Count > 0) plan.Add((variant, rows, Stretches(rows))); + } + if (asked is not null && asked.Except(plan.SelectMany(p => p.Rows)).ToList() is { Count: > 0 } unrun) + throw new Refusal($"no variant of {probe} runs row(s) {string.Join(", ", unrun)}: each variant's PROBE_ONLY leaves them out", 2); + + var home = Replay.Home(ctx, ledger); + var worktree = Path.Combine(home, "wt"); + var noBuild = a.Flag("no-build"); + if (noBuild && !Directory.Exists(worktree)) throw new Refusal($"--no-build: the replay worktree {worktree} does not exist yet, so nothing is built; run without it", 2); + // The worktree is the replay's: held for the whole try, so neither moves it or builds in it under the other. + using var held = Replay.HoldWorktree(home) + ?? throw new Refusal($"a replay in this checkout is using {worktree}; run `prober try` when it ends"); + var head = ledger.Head; + var warmth = ProbeRun.Ready(ledger, worktree, head); + Replay.Overlay(ledger, config, worktree); + var environment = ProbeRun.Environment(ctx, adapter, home); + ctx.Out.WriteLine($"prober try: {probe} ({claim}) at {head[..7]} in {worktree}; nothing is recorded"); + if (!noBuild) + { + var clock = Stopwatch.StartNew(); + var build = adapter.Build(worktree, environment); + ctx.Out.WriteLine($" build ({warmth}): {Seconds(clock.Elapsed.TotalSeconds)}, {ProbeRun.FirstLine(build)}"); + if (build.ExitCode != 0) { ctx.Out.WriteLine(build.Stdout); return 1; } + } + + // The full logs of each run, kept until the next try; the results and the rows' jsonl are thrown away. + var logs = Path.Combine(home, "try-logs"); + try { if (Directory.Exists(logs)) Directory.Delete(logs, recursive: true); } catch (IOException) { } + var results = Directory.CreateTempSubdirectory("prober-try-").FullName; + var exit = 0; + var tally = new SortedDictionary<string, int>(StringComparer.Ordinal); + try + { + var index = 0; + foreach (var (variant, rows, stretches) in plan) + { + var label = variant.Count > 0 ? " [" + string.Join(", ", variant.Select(kv => $"{kv.Key}={Files.Str(kv.Value)}")) + "]" : ""; + foreach (var stretch in stretches) + { + var name = $"{probe}-try-{index++}"; + var env = ProbeRun.Variant(environment, variant); + if (stretch is not null) env["PROBE_ROWS"] = stretch; + env["PROBE_LOG_DIR"] = Path.Combine(logs, name); + var clock = Stopwatch.StartNew(); + var (ran, outcomes) = ProbeRun.Once(adapter, worktree, filter, results, name, "", env); + ctx.Out.WriteLine($" {probe}{label}{(stretch is null ? "" : $" rows {stretch}")}: {ProbeRun.FirstLine(ran)} (exit {ran.ExitCode}, {Seconds(clock.Elapsed.TotalSeconds)})"); + foreach (var i in rows.Where(i => stretch is null || Within(i, stretch))) + { + var outcome = outcomes.GetValueOrDefault(i); + var category = outcome?.Category ?? "did not run"; + tally[category] = tally.GetValueOrDefault(category) + 1; + ctx.Out.WriteLine($" #{i} {category}{(outcome is { Category: not "pass", Reason.Length: > 0 } ? ": " + Cut(Unprefixed(outcome.Reason, category)) : "")}"); + } + if (ran.ExitCode != 0 && Directory.Exists(env["PROBE_LOG_DIR"])) ctx.Out.WriteLine($" log: {env["PROBE_LOG_DIR"]}"); + if (ran.ExitCode != 0 && exit == 0) exit = ran.ExitCode; + } + } + } + finally + { + try { Directory.Delete(results, recursive: true); } catch (IOException) { } + } + ctx.Out.WriteLine($"prober try: {probe}: {(tally.Count == 0 ? "no rows" : string.Join(", ", tally.Select(kv => $"{kv.Key} {kv.Value}")))}; exit {exit}"); + return exit; + } + + /// <summary>The row indices of <c>--rows</c> (<c>i,j,...</c>, <c>a-b</c> for a stretch), or null for every row; refuses one the claim does not have.</summary> + internal static SortedSet<int>? Indices(string? given, int count) + { + if (given is null) return null; + var result = new SortedSet<int>(); + foreach (var part in given.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var bounds = part.Split('-'); + if (bounds.Length > 2 || !bounds.All(x => int.TryParse(x, NumberStyles.None, CultureInfo.InvariantCulture, out _))) + throw new Refusal($"--rows takes row indices, i,j,... or a-b: not {part}", 2); + var lo = int.Parse(bounds[0], CultureInfo.InvariantCulture); var hi = int.Parse(bounds[^1], CultureInfo.InvariantCulture); + for (var i = lo; i <= hi; i++) result.Add(i); + } + if (result.Count == 0) throw new Refusal("--rows takes row indices, i,j,... or a-b", 2); + if (result.Max >= count) throw new Refusal($"--rows {result.Max}: the claim has {count} row(s), 0 to {count - 1}", 2); + return result; + } + + /// <summary>The consecutive stretches of sorted rows, as the harness's PROBE_ROWS spells one: <c>lo-hi</c>.</summary> + internal static List<string?> Stretches(List<int> rows) + { + var result = new List<string?>(); + for (var i = 0; i < rows.Count;) + { + var j = i; + while (j + 1 < rows.Count && rows[j + 1] == rows[j] + 1) j++; + result.Add(string.Create(CultureInfo.InvariantCulture, $"{rows[i]}-{rows[j]}")); + i = j + 1; + } + return result; + } + + private static bool Within(int row, string stretch) + { + var bounds = stretch.Split('-'); + return row >= int.Parse(bounds[0], CultureInfo.InvariantCulture) && row <= int.Parse(bounds[1], CultureInfo.InvariantCulture); + } + + /// <summary>A reason without the category the harness puts in front of it (<c>violation:...</c>), which the line already names.</summary> + private static string Unprefixed(string reason, string category) => reason.StartsWith(category + ":", StringComparison.Ordinal) ? reason[(category.Length + 1)..] : reason; + + private static string Seconds(double seconds) => seconds.ToString("F1", CultureInfo.InvariantCulture) + " s"; + + private static string Cut(string s) => string.Join(" ", s.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)) is var line && line.Length > 160 ? line[..160] + "..." : line; +} diff --git a/tools/Shared/Dotnet.cs b/tools/Shared/Dotnet.cs new file mode 100644 index 00000000..c6fb05c5 --- /dev/null +++ b/tools/Shared/Dotnet.cs @@ -0,0 +1,75 @@ +using System.Collections.Concurrent; +using System.Diagnostics; + +/// <summary> +/// The dotnet a tool starts. A shell may find a dotnet first on PATH that cannot satisfy global.json's SDK +/// pin: on macOS, Homebrew's formula (10.0.401 against the 10.0.402 pin) shadows the SDK installer's, and +/// every command it runs fails with exit 155. Then the one the installer puts in its default location is +/// tried, as the verdict tool and the SessionStart hook do; when neither satisfies the pin, the one on PATH +/// stays, so its own error reaches whoever reads the output (design/adr/0105). +/// </summary> +internal static class Dotnet +{ + /// <summary> + /// Where the SDK installer puts dotnet: /usr/local/share/dotnet, or %ProgramFiles%\dotnet on Windows, unless + /// TOOLS_DOTNET_FALLBACK_DIR names another directory (an install elsewhere, or a test's stub). + /// </summary> + public static string InstallerDirectory => + Environment.GetEnvironmentVariable("TOOLS_DOTNET_FALLBACK_DIR") is { Length: > 0 } given ? given + : OperatingSystem.IsWindows() ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet") + : "/usr/local/share/dotnet"; + + static string ExecutableIn(string directory) => Path.Combine(directory, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + + /// <summary> + /// The choice, given whether each candidate satisfies the pin. A bare "dotnet" is what a child started by that + /// name gets, and .NET looks for it beside the running host before it reads PATH, so a caller that falls back + /// starts the installer's by its full path, never by putting its directory on PATH alone. The choice: the one on PATH when it does, the installer's + /// when only it does (with the directory to put first on a child's PATH and DOTNET_ROOT), and otherwise the + /// one on PATH. Starts nothing. + /// </summary> + public static (string File, string? Directory) Choose(string installerDirectory, Func<string, bool> satisfies) + { + if (satisfies("dotnet")) return ("dotnet", null); + var fallback = ExecutableIn(installerDirectory); + return File.Exists(fallback) && satisfies(fallback) ? (fallback, installerDirectory) : ("dotnet", null); + } + + /// <summary>Whether <c>dotnet --version</c> succeeds in the directory, whose global.json then pins an SDK this dotnet has.</summary> + public static bool Satisfies(string dotnet, string directory) + { + var start = new ProcessStartInfo(dotnet) + { + WorkingDirectory = directory, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, + }; + start.ArgumentList.Add("--version"); + try + { + using var process = Process.Start(start); + if (process is null) return false; + var drained = Task.WhenAll(process.StandardOutput.ReadToEndAsync(), process.StandardError.ReadToEndAsync()); + process.WaitForExit(); + drained.Wait(); + return process.ExitCode == 0; + } + catch (System.ComponentModel.Win32Exception) + { + return false; + } + } + + static readonly ConcurrentDictionary<string, (string File, string? Directory)> chosen = new(StringComparer.Ordinal); + + /// <summary>The dotnet to start in a directory, decided once per directory and process.</summary> + public static (string File, string? Directory) For(string directory) => + chosen.GetOrAdd(Path.GetFullPath(directory), d => Choose(InstallerDirectory, dotnet => Satisfies(dotnet, d))); + + /// <summary>Points a child at the chosen dotnet's directory, so what it starts in turn (MSBuild, a test host) uses the same SDK.</summary> + public static void Apply(string? directory, IDictionary<string, string?> environment) + { + if (directory is null) return; + environment.TryGetValue("PATH", out var path); + environment["PATH"] = string.IsNullOrEmpty(path) ? directory : directory + Path.PathSeparator + path; + environment["DOTNET_ROOT"] = directory; + } +} diff --git a/tools/probe/corpus/claims.json b/tools/probe/corpus/claims.json index f9c3198f..301b96a8 100644 --- a/tools/probe/corpus/claims.json +++ b/tools/probe/corpus/claims.json @@ -1,453 +1,16178 @@ [ - { - "id": "C01-decimal-spelling", - "source": "docs/json.md#decimals-in-one-spelling", - "surfaces": [ - "json" - ], - "axes": { - "converter": [ - "number", - "string" - ], - "input": [ - "7.80", - "7.8000", - "0.00", - "100", - "1E+2", - "max28digits", - "1e-28" - ] - } - }, - { - "id": "C02-optional-text", - "source": "docs/text.md#required-and-optional", - "surfaces": [ - "text" - ], - "axes": { - "entry": [ - "AsOptionalText", - "AsOptionalIdentifier", - "AsText", - "AsIdentifier" - ], - "input": [ - "null", - "empty", - "spaces", - "tab-newline", - "word", - "word+ZWSP", - "nbsp-only", - "ZWSP-only" - ] - }, - "narrow": { - "shape.kind": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`", - "shape.wrapped": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`", - "text.entry": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`" - } - }, - { - "id": "C03-composed-faults", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types", - "surfaces": [ - "http" - ], - "axes": { - "arity": [ - "2", - "3" - ], - "error": [ - "first-set", - "last-set", - "with-detail", - "with-rule" - ] - } - }, - { - "id": "C04-without-faults", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#leaving-out-what-a-route-cannot-answer", - "surfaces": [ - "http" - ], - "axes": { - "scope": [ - "endpoint", - "group", - "controller" - ], - "combined": [ - "alone", - "with-WithFaultStatus-on-group" - ] - } - }, - { - "id": "C05-with-rule", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#the-rule-that-refused", - "surfaces": [ - "http" - ], - "axes": { - "ruletype": [ - "enum", - "closed-set", - "nested-enum", - "flags-combination", - "undefined-cast" - ], - "qualified": [ - "bare", - "QualifiedFaultNames" - ] - } - }, - { - "id": "C06-fault-kinds", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#a-closed-set-as-the-fault-type", - "surfaces": [ - "http" - ], - "axes": { - "kind": [ - "PreconditionFailed", - "Malformed", - "Invalid", - "Unavailable" - ] - } - }, - { - "id": "C07-fault-responses-blanket", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#every-fault-set-at-once", - "surfaces": [ - "http" - ], - "axes": { - "mapping": [ - "by-kind", - "own-IFault-mapping" - ], - "set": [ - "fault-set", - "own-mapped-set", - "enum-unmapped", - "member-without-kind", - "mapping-throws", - "status-not-error" - ] - } - }, - { - "id": "C08-same-name-warning", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types", - "surfaces": [ - "http" - ], - "axes": { - "naming": [ - "bare", - "qualified" - ], - "arity": [ - "2", - "3" - ], - "leave-out": [ - "none", - "WithoutFaults-one" - ] - } - }, - { - "id": "C09-base-record-equality", - "source": "CHANGELOG.md#2.1.0-fixed", - "surfaces": [ - "generation" - ], - "axes": { - "base-equality": [ - "none", - "override-Equals", - "override-GetHashCode", - "explicit-IEquatable" - ], - "collection": [ - "HashSet", - "Dictionary-key", - "Distinct" - ] - } - }, - { - "id": "C10-preserve-guard", - "source": "src/CodoMetis.TypeKit.Generators/README.md#json", - "surfaces": [ - "json" - ], - "axes": { - "handler": [ - "Preserve", - "IgnoreCycles", - "none" - ] - } - }, - { - "id": "C11-parse-shapes-build", - "source": "CHANGELOG.md#2.1.0-fixed", - "surfaces": [ - "text", - "generation" - ], - "axes": { - "parse-shape": [ - "implicit-result", - "ref-text", - "abstract-string-ctor", - "ordinary" - ] - } - }, - { - "id": "C12-described-converter", - "source": "docs/json.md#describing-what-a-converter-writes", - "surfaces": [ - "http" - ], - "axes": { - "source": [ - "IDescribedJsonConverter", - "JsonSchemaDescriptions", - "decimal-converter" - ], - "http.host": [ - "minimal", - "mvc", - "both", - "route-group", - "controller-without-ApiController-visible" - ] - } - }, - { - "id": "C13-patch-required", - "source": "docs/merge-patches.md#required-constructor-parameters", - "surfaces": [ - "http" - ], - "axes": { - "declared": [ - "init", - "ctor-parameter" - ], - "http.host": [ - "minimal", - "mvc", - "both", - "route-group", - "controller-without-ApiController-visible" - ] - } - }, - { - "id": "C14-declared-problem", - "source": "CHANGELOG.md#2.1.0-fixed", - "surfaces": [ - "http" - ], - "axes": { - "declared": [ - "ProducesProblem", - "ProducesResponseType", - "none" - ], - "http.host": [ - "minimal", - "mvc", - "both", - "route-group", - "controller-without-ApiController-visible" - ] - } - }, - { - "id": "C15-mvc-fault-status-doc", - "source": "CHANGELOG.md#2.1.0-fixed", - "surfaces": [ - "http" - ], - "axes": { - "setter": [ - "MapControllers().WithFaultStatus", - "action-attribute", - "controller-attribute" - ], - "kind": [ - "status-only", - "WithoutFaults" - ], - "http.host": [ - "minimal", - "mvc", - "both", - "route-group", - "controller-without-ApiController-visible" - ] - } - }, - { - "id": "C16-options-agreement", - "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", - "surfaces": [ - "http" - ], - "axes": { - "difference": [ - "converter-only-http", - "naming-policy", - "number-handling" - ], - "http.host": [ - "minimal", - "mvc", - "both", - "route-group", - "controller-without-ApiController-visible" - ] - } - }, - { - "id": "C17-cmtk0014", - "source": "src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0014", - "surfaces": [ - "analyzers" - ], - "axes": { - "value": [ - "(FaultKind)9", - "default", - "valid" - ], - "site": [ - "attribute-argument", - "cast", - "default-literal" - ] - } - }, - { - "id": "C18-cmtk1023", - "source": "src/CodoMetis.TypeKit.Generators/README.md#build-errors", - "surfaces": [ - "generation" - ], - "axes": { - "base-member": [ - "Equals", - "GetHashCode", - "IEquatable" - ] - } - }, - { - "id": "C19-cmtk0010-kinds", - "source": "docs/type-constraints.md#the-kind-of-a-type-argument", - "surfaces": [ - "analyzers" - ], - "axes": { - "attribute": [ - "Interface", - "Unsealed", - "ValueObject", - "ClosedSet", - "FlagsEnum", - "ReadOnlyStruct", - "Distinct", - "Unrelated" - ], - "site": [ - "call", - "foreach", - "await", - "Deconstruct", - "collection-expression" - ] - } - }, - { - "id": "C20-testing-shouldhold", - "source": "src/CodoMetis.TypeKit.Testing/README.md", - "surfaces": [ - "option-result" - ], - "axes": { - "arity": [ - "2", - "3" - ], - "set": [ - "first", - "last" - ], - "wrong-reason": [ - "no", - "yes" - ] + { + "id": "C01-decimal-spelling", + "source": "docs/json.md#decimals-in-one-spelling", + "surfaces": [ + "json" + ], + "axes": { + "converter": [ + "number", + "string" + ], + "input": [ + "7.80", + "7.8000", + "0.00", + "100", + "1E+2", + "max28digits", + "1e-28" + ] + }, + "narrow": { + "shape.wrapped": "Register either one once, and it covers every decimal: nullable ones, dictionary keys, and every value object over a decimal, since its JSON is the underlying type's.", + "input.json": "Both read a number, or a string holding one, and keep the scale they read." + }, + "obligations": [ + { + "id": "smallest-scale", + "sentence": "Two converters write the smallest scale instead:" + }, + { + "id": "number-is-a-number", + "sentence": "**`DecimalNumberJsonConverter`** writes it as a number.", + "axes": { + "converter": [ + "number" + ] + } + }, + { + "id": "string-is-a-string", + "sentence": "**`DecimalStringJsonConverter`** writes it as a string.", + "axes": { + "converter": [ + "string" + ] + } + }, + { + "id": "reads-a-number-or-a-string", + "sentence": "Both read a number, or a string holding one, and keep the scale they read." + }, + { + "id": "keeps-the-scale-it-reads", + "sentence": "Both read a number, or a string holding one, and keep the scale they read." + }, + { + "id": "covers-every-decimal", + "sentence": "Register either one once, and it covers every decimal: nullable ones, dictionary keys, and every value object over a decimal, since its JSON is the underlying type's." + } + ], + "exclude": [ + { + "reason": "A closed set wraps nothing, so it holds no decimal for a decimal converter to write (docs/json.md#decimals-in-one-spelling covers decimals, nullable ones, dictionary keys and value objects over a decimal).", + "pairs": [ + [ + "converter=number", + "shape.kind=closed-set" + ], + [ + "converter=string", + "shape.kind=closed-set" + ], + [ + "input=0.00", + "shape.kind=closed-set" + ], + [ + "input=100", + "shape.kind=closed-set" + ], + [ + "input=1E+2", + "shape.kind=closed-set" + ], + [ + "input=1e-28", + "shape.kind=closed-set" + ], + [ + "input=7.80", + "shape.kind=closed-set" + ], + [ + "input=7.8000", + "shape.kind=closed-set" + ], + [ + "input=max28digits", + "shape.kind=closed-set" + ], + [ + "json.AllowDuplicateProperties=default", + "shape.kind=closed-set" + ], + [ + "json.AllowDuplicateProperties=false", + "shape.kind=closed-set" + ], + [ + "json.AllowOutOfOrderMetadataProperties=default", + "shape.kind=closed-set" + ], + [ + "json.AllowOutOfOrderMetadataProperties=true", + "shape.kind=closed-set" + ], + [ + "json.AllowTrailingCommas=default", + "shape.kind=closed-set" + ], + [ + "json.AllowTrailingCommas=true", + "shape.kind=closed-set" + ], + [ + "json.Converters=JsonStringEnumConverter", + "shape.kind=closed-set" + ], + [ + "json.Converters=StoredJsonConverterFactory", + "shape.kind=closed-set" + ], + [ + "json.Converters=default", + "shape.kind=closed-set" + ], + [ + "json.Converters=own-for-value-object", + "shape.kind=closed-set" + ], + [ + "json.Converters=own-for-wrapped", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenReading", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWriting", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWritingDefault", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWritingNull", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=default", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=CamelCase", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=KebabCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=SnakeCaseLower", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=default", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=own-upper-case", + "shape.kind=closed-set" + ], + [ + "json.Encoder=UnsafeRelaxedJsonEscaping", + "shape.kind=closed-set" + ], + [ + "json.Encoder=default", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyFields=default", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyFields=true", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyProperties=default", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyProperties=true", + "shape.kind=closed-set" + ], + [ + "json.IncludeFields=default", + "shape.kind=closed-set" + ], + [ + "json.IncludeFields=true", + "shape.kind=closed-set" + ], + [ + "json.MaxDepth=3", + "shape.kind=closed-set" + ], + [ + "json.MaxDepth=default", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=AllowNamedFloatingPointLiterals", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=AllowReadingFromString", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=WriteAsString", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=default", + "shape.kind=closed-set" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "shape.kind=closed-set" + ], + [ + "json.PreferredObjectCreationHandling=default", + "shape.kind=closed-set" + ], + [ + "json.PropertyNameCaseInsensitive=default", + "shape.kind=closed-set" + ], + [ + "json.PropertyNameCaseInsensitive=true", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=CamelCase", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=KebabCaseLower", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=KebabCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=SnakeCaseLower", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=SnakeCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=default", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=own-upper-case", + "shape.kind=closed-set" + ], + [ + "json.ReadCommentHandling=Skip", + "shape.kind=closed-set" + ], + [ + "json.ReadCommentHandling=default", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=IgnoreCycles", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=Preserve", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=default", + "shape.kind=closed-set" + ], + [ + "json.RespectNullableAnnotations=default", + "shape.kind=closed-set" + ], + [ + "json.RespectNullableAnnotations=true", + "shape.kind=closed-set" + ], + [ + "json.RespectRequiredConstructorParameters=default", + "shape.kind=closed-set" + ], + [ + "json.RespectRequiredConstructorParameters=true", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=default", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=default-with-renaming-modifier", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=source-generated-context", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolverChain=context-then-default", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolverChain=default", + "shape.kind=closed-set" + ], + [ + "json.UnknownTypeHandling=JsonNode", + "shape.kind=closed-set" + ], + [ + "json.UnknownTypeHandling=default", + "shape.kind=closed-set" + ], + [ + "json.UnmappedMemberHandling=Disallow", + "shape.kind=closed-set" + ], + [ + "json.UnmappedMemberHandling=default", + "shape.kind=closed-set" + ], + [ + "shape.containing=class", + "shape.kind=closed-set" + ], + [ + "shape.containing=generic-type", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-class", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-struct", + "shape.kind=closed-set" + ], + [ + "shape.containing=struct", + "shape.kind=closed-set" + ], + [ + "shape.kind=closed-set", + "shape.position=array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-key" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-value" + ], + [ + "shape.kind=closed-set", + "shape.position=field" + ], + [ + "shape.kind=closed-set", + "shape.position=immutable-array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=in-patch" + ], + [ + "shape.kind=closed-set", + "shape.position=list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=nullable-property" + ], + [ + "shape.kind=closed-set", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.kind=closed-set", + "shape.position=property" + ], + [ + "shape.kind=closed-set", + "shape.position=read-only-list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=record-ctor-parameter" + ], + [ + "shape.kind=closed-set", + "shape.position=required-init" + ], + [ + "shape.kind=closed-set", + "shape.position=root" + ] + ] + }, + { + "reason": "A value at the root is not held by a containing type, so shape.containing has nothing to apply to.", + "pairs": [ + [ + "shape.containing=class", + "shape.position=root" + ], + [ + "shape.containing=generic-type", + "shape.position=root" + ], + [ + "shape.containing=record-class", + "shape.position=root" + ], + [ + "shape.containing=record-struct", + "shape.position=root" + ], + [ + "shape.containing=struct", + "shape.position=root" + ] + ] + }, + { + "reason": "System.Text.Json serializes a field only under IncludeFields (or [JsonInclude]), so without it the member is in no document and no converter runs in either direction, for the plain type alike.", + "pairs": [ + [ + "json.IncludeFields=default", + "shape.position=field" + ] + ] + }, + { + "reason": "System.Text.Json refuses JsonObjectCreationHandling.Populate together with a reference handler (InvalidOperationException when the options are used; harness/JsonRow.cs runs such a row without Populate), so no row can set both.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ] + ] + } + ] + }, + { + "id": "C02-optional-text", + "source": "docs/text.md#required-and-optional", + "surfaces": [ + "text" + ], + "axes": { + "entry": [ + "AsOptionalText", + "AsOptionalIdentifier", + "AsText", + "AsIdentifier" + ], + "input": [ + "null", + "empty", + "spaces", + "tab-newline", + "word", + "word+ZWSP", + "nbsp-only", + "ZWSP-only" + ] + }, + "narrow": { + "shape.kind": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`", + "shape.wrapped": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`", + "text.entry": "`AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`" + }, + "obligations": [ + { + "id": "required-blank-is-an-error", + "sentence": "For a required field, `Blank` is an error.", + "axes": { + "entry": [ + "AsText", + "AsIdentifier" + ], + "input": [ + "null", + "empty", + "spaces", + "tab-newline", + "nbsp-only" + ] + } + }, + { + "id": "optional-blank-is-no-value", + "sentence": "For an optional one, a blank means \"no value\", and `AsOptionalText()` and `AsOptionalIdentifier()` return a `Result<Option<string>, TextFault>`.", + "axes": { + "entry": [ + "AsOptionalText", + "AsOptionalIdentifier" + ], + "input": [ + "null", + "empty", + "spaces", + "tab-newline", + "nbsp-only" + ] + } + }, + { + "id": "hidden-is-an-error-in-both", + "sentence": "`Hidden` is an error in both cases.", + "axes": { + "input": [ + "word+ZWSP", + "ZWSP-only" + ] + } + }, + { + "id": "null-is-blank", + "sentence": "A null string is `Blank`.", + "axes": { + "input": [ + "null" + ] + } + } + ], + "not-promised": [ + { + "id": "as-text-refused-in-globalization-invariant-mode", + "source": "docs/text.md#globalization-invariant-mode", + "sentence": "So `AsText()` throws `PlatformNotSupportedException` there, on its first call, whatever the string.", + "axes": { + "text.culture": [ + "globalization-invariant" + ], + "entry": [ + "AsText", + "AsOptionalText" + ] + } + } + ], + "exclude": [ + { + "reason": "Not promised (as-text-refused-in-globalization-invariant-mode): `AsText()` throws `PlatformNotSupportedException` in globalization-invariant mode (docs/text.md#globalization-invariant-mode), and `AsOptionalText()`, which calls it, throws with it, so C02OptionalTextProbe checks that refusal without credit and runs the row on the identifier counterpart; no row holds the pair.", + "pairs": [ + [ + "entry=AsText", + "text.culture=globalization-invariant" + ], + [ + "entry=AsOptionalText", + "text.culture=globalization-invariant" + ] + ] + } + ] + }, + { + "id": "C03-composed-faults", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types", + "surfaces": [ + "http" + ], + "axes": { + "arity": [ + "2", + "3" + ], + "error": [ + "first-set", + "last-set", + "with-detail", + "with-rule" + ] + }, + "obligations": [ + { + "id": "each-error-own-set", + "sentence": "Each error is answered as its own set maps it." + }, + { + "id": "detail-and-rule-intact", + "sentence": "`Fault<AccountFault, EditFault>` (from CodoMetis.TypeKit) holds one error of either set, with its detail and rule intact.", + "axes": { + "error": [ + "with-detail", + "with-rule" + ] + } + }, + { + "id": "statuses-ascending", + "sentence": "So its operation lists the statuses of both sets, in ascending order, with the problem types of both where they share a status." + }, + { + "id": "shared-status-types", + "sentence": "So its operation lists the statuses of both sets, in ascending order, with the problem types of both where they share a status." + }, + { + "id": "unmapped-set-stops-start", + "sentence": "The host doesn't start while either set is unmapped." + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C04-without-faults", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#leaving-out-what-a-route-cannot-answer", + "surfaces": [ + "http" + ], + "axes": { + "scope": [ + "endpoint", + "group", + "controller" + ], + "combined": [ + "alone", + "with-WithFaultStatus-on-group" + ] + }, + "obligations": [ + { + "id": "doc-omits-left-out", + "sentence": "The operation then lists no status and no problem type for a member left out." + }, + { + "id": "reporting-left-out-throws", + "sentence": "Reporting a member left out anyway throws an `InvalidOperationException` naming the endpoint and the member, rather than answering with a status the document doesn't list." + }, + { + "id": "narrows-wider-status", + "sentence": "If a group gives `NotFound` a status and one endpoint inside leaves `NotFound` out, that endpoint drops it, and the group's other endpoints keep the status.", + "axes": { + "scope": [ + "endpoint", + "controller" + ], + "combined": [ + "with-WithFaultStatus-on-group" + ] + } + }, + { + "id": "left-out-everywhere-stops-start", + "sentence": "**These stop the host from starting**, as for a status: a value or a name no member has, a status that every endpoint it's set on leaves out (on the endpoint itself, or on a group whose endpoints all leave the member out), and a group or a controller none of whose endpoints answers with the type.", + "axes": { + "scope": [ + "group" + ], + "combined": [ + "with-WithFaultStatus-on-group" + ] + } + } + ], + "not-promised": [ + { + "id": "controller-scope-is-mvc", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#leaving-out-what-a-route-cannot-answer", + "sentence": "**In MVC**, an action or a controller says it with `[WithoutFaults<AccountFault>(nameof(AccountFault.NotFound))]`.", + "axes": { + "scope": [ + "controller" + ], + "http.host": [ + "minimal", + "route-group" + ] + } + } + ], + "exclude": [ + { + "reason": "A minimal-API host has no controller to leave a member out on: the controller scope is MVC's (not-promised controller-scope-is-mvc).", + "pairs": [ + [ + "scope=controller", + "http.host=minimal" + ], + [ + "scope=controller", + "http.host=route-group" + ] + ] + }, + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C05-with-rule", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#the-rule-that-refused", + "surfaces": [ + "http" + ], + "axes": { + "ruletype": [ + "enum", + "closed-set", + "nested-enum", + "flags-combination", + "undefined-cast" + ], + "qualified": [ + "bare", + "QualifiedFaultNames" + ] + }, + "obligations": [ + { + "id": "rule-qualified", + "sentence": "The problem carries it as `rule`, qualified with its type's name, so two types' `Blank` can be told apart:", + "axes": { + "ruletype": [ + "enum", + "closed-set" + ] + } + }, + { + "id": "nested-chain", + "sentence": "A rule of a nested type is named after its nesting chain, whatever `QualifiedFaultNames` says.", + "axes": { + "ruletype": [ + "nested-enum" + ] + } + }, + { + "id": "stays-the-errors", + "sentence": "The status, the mapping and the document stay the error's.", + "axes": { + "ruletype": [ + "enum", + "closed-set", + "nested-enum" + ] + } + }, + { + "id": "unnamed-value-throws", + "sentence": "A value no member names, such as a combination of flags or a cast number, throws where it's given.", + "axes": { + "ruletype": [ + "flags-combination", + "undefined-cast" + ] + } + }, + { + "id": "no-namespace", + "sentence": "**The name carries no namespace**, which would publish your application's structure to every client.", + "axes": { + "ruletype": [ + "enum", + "closed-set", + "nested-enum" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C06-fault-kinds", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#a-closed-set-as-the-fault-type", + "surfaces": [ + "http" + ], + "axes": { + "kind": [ + "PreconditionFailed", + "Malformed", + "Invalid", + "Unavailable" + ] + }, + "obligations": [ + { + "id": "kind-gives-status", + "sentence": "The kind gives the status:" + }, + { + "id": "description-as-detail", + "sentence": "The description becomes the problem's `detail`, so a client can show it." + }, + { + "id": "no-description-no-detail", + "sentence": "A member without one has no `detail`." + }, + { + "id": "member-must-have-kind", + "sentence": "**A member must have a kind.** A member whose kind is `default`, which is no kind, fails this call." + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C07-fault-responses-blanket", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#every-fault-set-at-once", + "surfaces": [ + "http" + ], + "axes": { + "mapping": [ + "by-kind", + "own-IFault-mapping" + ], + "set": [ + "fault-set", + "own-mapped-set", + "enum-unmapped", + "member-without-kind", + "mapping-throws", + "status-not-error" + ] + }, + "obligations": [ + { + "id": "by-kind", + "sentence": "every fault set an endpoint answers with, by its kind", + "axes": { + "mapping": [ + "by-kind" + ] + } + }, + { + "id": "own-mapping", + "sentence": "The mapping can be your own. It reads each member's kind and description through `IFault`:", + "axes": { + "mapping": [ + "own-IFault-mapping" + ] + } + }, + { + "id": "own-set-keeps-mapping", + "sentence": "A set mapped on its own keeps its mapping.", + "axes": { + "set": [ + "own-mapped-set" + ] + } + }, + { + "id": "enum-needs-own-mapping", + "sentence": "Neither has a kind, and the host doesn't start while an endpoint answers with one that's unmapped.", + "axes": { + "set": [ + "enum-unmapped" + ] + } + }, + { + "id": "member-without-kind-stops-start", + "sentence": "A member with no kind stops the start, and so does one the mapping throws for or gives a status that isn't an error.", + "axes": { + "set": [ + "member-without-kind" + ] + } + }, + { + "id": "mapping-refusal-stops-start", + "sentence": "A member with no kind stops the start, and so does one the mapping throws for or gives a status that isn't an error.", + "axes": { + "set": [ + "mapping-throws", + "status-not-error" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C08-same-name-warning", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types", + "surfaces": [ + "http" + ], + "axes": { + "naming": [ + "bare", + "qualified" + ], + "arity": [ + "2", + "3" + ], + "leave-out": [ + "none", + "WithoutFaults-one" + ] + }, + "obligations": [ + { + "id": "bare-names-warn", + "sentence": "With bare names, the default, that's `AccountFault.NotFound` beside an `EditFault.NotFound`.", + "axes": { + "naming": [ + "bare" + ] + } + }, + { + "id": "qualified-chain-warns", + "sentence": "With qualified names, it's two sets with one nesting chain in two namespaces, since the name carries no namespace.", + "axes": { + "naming": [ + "qualified" + ] + } + }, + { + "id": "warning-names-both", + "sentence": "A client reads the two errors as one, so the host logs a warning at startup naming both.", + "axes": { + "leave-out": [ + "none" + ] + } + }, + { + "id": "qualified-names-fix", + "sentence": "Fix it with `QualifiedFaultNames`, a rename, or by leaving one out of the route", + "axes": { + "naming": [ + "qualified" + ] + } + }, + { + "id": "leave-one-out-fixes", + "sentence": "Fix it with `QualifiedFaultNames`, a rename, or by leaving one out of the route", + "axes": { + "leave-out": [ + "WithoutFaults-one" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C09-base-record-equality", + "source": "CHANGELOG.md#2.1.0-fixed", + "surfaces": [ + "generation" + ], + "axes": { + "base-equality": [ + "none", + "override-Equals", + "override-GetHashCode", + "explicit-IEquatable" + ], + "collection": [ + "HashSet", + "Dictionary-key", + "Distinct" + ] + }, + "narrow": { + "shape.position": "Over a base record, a value object's equality is its wrapped value's, in a collection of the base too, whatever equality the base declares.", + "shape.containing": "Over a base record, a value object's equality is its wrapped value's, in a collection of the base too, whatever equality the base declares." + }, + "obligations": [ + { + "id": "equal-by-the-wrapped-value", + "sentence": "Over a base record, a value object's equality is its wrapped value's" + }, + { + "id": "in-a-collection-of-the-base", + "sentence": "in a collection of the base too, whatever equality the base declares." + } + ], + "exclude": [ + { + "reason": "A record struct or a closed set has no base record: C# gives a struct no base type, and a closed set is a sealed class that is never a record (CMTK1020).", + "pairs": [ + [ + "base-equality=explicit-IEquatable", + "shape.kind=closed-set" + ], + [ + "base-equality=explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "base-equality=explicit-IEquatable", + "shape.kind=validated-struct" + ], + [ + "base-equality=none", + "shape.kind=closed-set" + ], + [ + "base-equality=none", + "shape.kind=record-struct" + ], + [ + "base-equality=none", + "shape.kind=validated-struct" + ], + [ + "base-equality=override-Equals", + "shape.kind=closed-set" + ], + [ + "base-equality=override-Equals", + "shape.kind=record-struct" + ], + [ + "base-equality=override-Equals", + "shape.kind=validated-struct" + ], + [ + "base-equality=override-GetHashCode", + "shape.kind=closed-set" + ], + [ + "base-equality=override-GetHashCode", + "shape.kind=record-struct" + ], + [ + "base-equality=override-GetHashCode", + "shape.kind=validated-struct" + ], + [ + "collection=Dictionary-key", + "shape.kind=closed-set" + ], + [ + "collection=Dictionary-key", + "shape.kind=record-struct" + ], + [ + "collection=Dictionary-key", + "shape.kind=validated-struct" + ], + [ + "collection=Distinct", + "shape.kind=closed-set" + ], + [ + "collection=Distinct", + "shape.kind=record-struct" + ], + [ + "collection=Distinct", + "shape.kind=validated-struct" + ], + [ + "collection=HashSet", + "shape.kind=closed-set" + ], + [ + "collection=HashSet", + "shape.kind=record-struct" + ], + [ + "collection=HashSet", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=none", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=none", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=none", + "shape.kind=validated-struct" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=closed-set", + "shape.site=in-generic-type" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-class" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=closed-set", + "shape.site=top-level" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-in-class" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=record-struct", + "shape.site=top-level" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=Guid" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=Uri" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=bool" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=byte" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=char" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=decimal" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=double" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=enum" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=int" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=long" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=string" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=ulong" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-in-class" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=validated-struct", + "shape.site=top-level" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=Guid" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=Uri" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=bool" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=byte" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=char" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=decimal" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=double" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=enum" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=int" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=long" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=string" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=ulong" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ] + ] + }, + { + "reason": "A value object nested in a generic type fails the build with CMTK1005 (Generators README, Build errors), so it has no generated equality or parsing to check.", + "pairs": [ + [ + "base-equality=explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "base-equality=none", + "shape.site=in-generic-type" + ], + [ + "base-equality=override-Equals", + "shape.site=in-generic-type" + ], + [ + "base-equality=override-GetHashCode", + "shape.site=in-generic-type" + ], + [ + "collection=Dictionary-key", + "shape.site=in-generic-type" + ], + [ + "collection=Distinct", + "shape.site=in-generic-type" + ], + [ + "collection=HashSet", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-1", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-2", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-3", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=none", + "shape.site=in-generic-type" + ], + [ + "shape.kind=sealed-record-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-class", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=in-generic-type" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTime" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Guid" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeSpan" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Uri" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=bool" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=byte" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=char" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=decimal" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=double" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=flags-enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=int" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=long" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=own-record-class" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=string" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=in-generic-type" + ] + ] + }, + { + "reason": "The claim is about a value object over a base record; with no base record it states nothing.", + "pairs": [ + [ + "base-equality=explicit-IEquatable", + "shape.inheritance=none" + ], + [ + "base-equality=none", + "shape.inheritance=none" + ], + [ + "base-equality=override-Equals", + "shape.inheritance=none" + ], + [ + "base-equality=override-GetHashCode", + "shape.inheritance=none" + ], + [ + "collection=Dictionary-key", + "shape.inheritance=none" + ], + [ + "collection=Distinct", + "shape.inheritance=none" + ], + [ + "collection=HashSet", + "shape.inheritance=none" + ], + [ + "shape.inheritance=none", + "shape.kind=sealed-record-class" + ], + [ + "shape.inheritance=none", + "shape.kind=validated-class" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=none", + "shape.site=nested-in-class" + ], + [ + "shape.inheritance=none", + "shape.site=nested-in-struct" + ], + [ + "shape.inheritance=none", + "shape.site=nested-two-levels" + ], + [ + "shape.inheritance=none", + "shape.site=top-level" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateOnly" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateTime" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.inheritance=none", + "shape.wrapped=Guid" + ], + [ + "shape.inheritance=none", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.inheritance=none", + "shape.wrapped=TimeOnly" + ], + [ + "shape.inheritance=none", + "shape.wrapped=TimeSpan" + ], + [ + "shape.inheritance=none", + "shape.wrapped=Uri" + ], + [ + "shape.inheritance=none", + "shape.wrapped=bool" + ], + [ + "shape.inheritance=none", + "shape.wrapped=byte" + ], + [ + "shape.inheritance=none", + "shape.wrapped=char" + ], + [ + "shape.inheritance=none", + "shape.wrapped=decimal" + ], + [ + "shape.inheritance=none", + "shape.wrapped=double" + ], + [ + "shape.inheritance=none", + "shape.wrapped=enum" + ], + [ + "shape.inheritance=none", + "shape.wrapped=flags-enum" + ], + [ + "shape.inheritance=none", + "shape.wrapped=int" + ], + [ + "shape.inheritance=none", + "shape.wrapped=long" + ], + [ + "shape.inheritance=none", + "shape.wrapped=own-record-class" + ], + [ + "shape.inheritance=none", + "shape.wrapped=string" + ], + [ + "shape.inheritance=none", + "shape.wrapped=ulong" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ] + ] + }, + { + "reason": "A declaration of a refused kind fails the build (CMTK1005, CMTK1006), so it is generated into nothing; .claude/probe-axes.md: shape.refused-kind is taken only where the refusal is the claim.", + "pairs": [ + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "base-equality=none", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "collection=Distinct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "collection=HashSet", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=ulong" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-equality=explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "base-equality=none", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-equality=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-equality=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-equality=override-Equals", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-equality=override-GetHashCode", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "collection=Dictionary-key", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "collection=Distinct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "collection=Distinct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "collection=Distinct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "collection=HashSet", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "collection=HashSet", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "collection=HashSet", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=ulong" + ] + ] + } + ] + }, + { + "id": "C10-preserve-guard", + "source": "src/CodoMetis.TypeKit.Generators/README.md#json", + "surfaces": [ + "json" + ], + "axes": { + "handler": [ + "Preserve", + "IgnoreCycles", + "none" + ] + }, + "obligations": [ + { + "id": "refused-where-references-could-be-written", + "sentence": "**Preserved references** (`ReferenceHandler.Preserve`) throw a `NotSupportedException` where the serializer could write `$id` or `$ref` inside the underlying value.", + "axes": { + "handler": [ + "Preserve" + ], + "shape.wrapped": [ + "own-record-class" + ] + } + }, + { + "id": "value-converters-unaffected", + "sentence": "These are unaffected: a value the serializer writes with a value converter (a number, a string, a `Guid`, a `Uri`)", + "axes": { + "handler": [ + "Preserve" + ] + } + }, + { + "id": "own-converters-unaffected", + "sentence": "anything a converter of your own writes, on the options or on one property", + "axes": { + "json.Converters": [ + "own-for-wrapped", + "own-for-value-object" + ] + } + }, + { + "id": "ignore-cycles-unaffected", + "sentence": "a struct of such values, and `ReferenceHandler.IgnoreCycles`.", + "axes": { + "handler": [ + "IgnoreCycles" + ] + } + } + ], + "exclude": [ + { + "reason": "handler is this claim's name for JsonSerializerOptions.ReferenceHandler (Preserve, IgnoreCycles, none = default), the same setting as json.ReferenceHandler; one setting cannot hold two values in one row.", + "pairs": [ + [ + "handler=IgnoreCycles", + "json.ReferenceHandler=Preserve" + ], + [ + "handler=IgnoreCycles", + "json.ReferenceHandler=default" + ], + [ + "handler=Preserve", + "json.ReferenceHandler=IgnoreCycles" + ], + [ + "handler=Preserve", + "json.ReferenceHandler=default" + ], + [ + "handler=none", + "json.ReferenceHandler=IgnoreCycles" + ], + [ + "handler=none", + "json.ReferenceHandler=Preserve" + ] + ] + }, + { + "reason": "A closed set wraps nothing (Generators README#closed-sets: a sealed class whose instances are its members), so it has no shape.wrapped.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A value at the root is not held by a containing type, so shape.containing has nothing to apply to.", + "pairs": [ + [ + "shape.containing=class", + "shape.position=root" + ], + [ + "shape.containing=generic-type", + "shape.position=root" + ], + [ + "shape.containing=record-class", + "shape.position=root" + ], + [ + "shape.containing=record-struct", + "shape.position=root" + ], + [ + "shape.containing=struct", + "shape.position=root" + ] + ] + }, + { + "reason": "System.Text.Json refuses JsonObjectCreationHandling.Populate together with a reference handler (InvalidOperationException when the options are used; harness/JsonRow.cs runs such a row without Populate), so no row can set both.", + "pairs": [ + [ + "handler=IgnoreCycles", + "json.PreferredObjectCreationHandling=Populate" + ], + [ + "handler=Preserve", + "json.PreferredObjectCreationHandling=Populate" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ] + ] + } + ] + }, + { + "id": "C11-parse-shapes-build", + "source": "CHANGELOG.md#2.1.0-fixed", + "surfaces": [ + "text", + "generation" + ], + "axes": { + "parse-shape": [ + "implicit-result", + "ref-text", + "abstract-string-ctor", + "ordinary" + ] + }, + "narrow": { + "shape.position": "A wrapped type's `Parse` whose result can't be assigned to it, or that takes its text by `ref`, or an abstract class's string constructor, no longer breaks the build; the value object has no parsing.", + "shape.containing": "A wrapped type's `Parse` whose result can't be assigned to it, or that takes its text by `ref`, or an abstract class's string constructor, no longer breaks the build; the value object has no parsing." + }, + "obligations": [ + { + "id": "implicit-result", + "sentence": "A wrapped type's `Parse` whose result can't be assigned to it", + "axes": { + "parse-shape": [ + "implicit-result" + ] + } + }, + { + "id": "ref-text", + "sentence": "or that takes its text by `ref`", + "axes": { + "parse-shape": [ + "ref-text" + ] + } + }, + { + "id": "abstract-string-constructor", + "sentence": "or an abstract class's string constructor, no longer breaks the build; the value object has no parsing.", + "axes": { + "parse-shape": [ + "abstract-string-ctor" + ] + } + }, + { + "id": "parsing-where-it-can-be-parsed", + "sentence": "`IParsable`/`ISpanParsable`/`IUtf8SpanParsable`, formatting in the invariant culture", + "axes": { + "parse-shape": [ + "ordinary" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "not-promised:no-span-parsing-without-the-underlying-types" + } + ] + } + ], + "not-promised": [ + { + "id": "no-span-parsing-without-the-underlying-types", + "source": "src/CodoMetis.TypeKit.Generators/README.md#what-is-generated", + "sentence": "A `Uri`, an enum and a NodaTime type have neither.", + "axes": { + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + } + } + ], + "exclude": [ + { + "reason": "A closed set wraps no type, so it has no wrapped type's Parse to take for its parser.", + "pairs": [ + [ + "parse-shape=abstract-string-ctor", + "shape.kind=closed-set" + ], + [ + "parse-shape=implicit-result", + "shape.kind=closed-set" + ], + [ + "parse-shape=ordinary", + "shape.kind=closed-set" + ], + [ + "parse-shape=ref-text", + "shape.kind=closed-set" + ], + [ + "process.converter-lookup=none", + "shape.kind=closed-set" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=none", + "shape.kind=closed-set" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=closed-set", + "shape.site=in-generic-type" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-class" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=closed-set", + "shape.site=top-level" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ], + [ + "shape.kind=closed-set", + "text.culture=ar-SA" + ], + [ + "shape.kind=closed-set", + "text.culture=de-DE" + ], + [ + "shape.kind=closed-set", + "text.culture=globalization-invariant" + ], + [ + "shape.kind=closed-set", + "text.culture=invariant" + ], + [ + "shape.kind=closed-set", + "text.culture=tr-TR" + ], + [ + "shape.kind=closed-set", + "text.entry=IParsable" + ], + [ + "shape.kind=closed-set", + "text.entry=ISpanParsable" + ], + [ + "shape.kind=closed-set", + "text.entry=Parse" + ], + [ + "shape.kind=closed-set", + "text.entry=ToString-format" + ], + [ + "shape.kind=closed-set", + "text.entry=TryFormat-short-span" + ], + [ + "shape.kind=closed-set", + "text.entry=TryFrom" + ], + [ + "shape.kind=closed-set", + "text.entry=TryParse" + ], + [ + "shape.kind=closed-set", + "text.entry=type-converter" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ] + ] + }, + { + "reason": "A value object nested in a generic type fails the build with CMTK1005 (Generators README, Build errors), so it has no generated equality or parsing to check.", + "pairs": [ + [ + "parse-shape=abstract-string-ctor", + "shape.site=in-generic-type" + ], + [ + "parse-shape=implicit-result", + "shape.site=in-generic-type" + ], + [ + "parse-shape=ordinary", + "shape.site=in-generic-type" + ], + [ + "parse-shape=ref-text", + "shape.site=in-generic-type" + ], + [ + "process.converter-lookup=none", + "shape.site=in-generic-type" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-1", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-2", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-3", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=none", + "shape.site=in-generic-type" + ], + [ + "shape.kind=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=sealed-record-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-struct", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=in-generic-type" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTime" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Guid" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeSpan" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Uri" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=bool" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=byte" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=char" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=decimal" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=double" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=flags-enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=int" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=long" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=own-record-class" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=string" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=ulong" + ], + [ + "shape.site=in-generic-type", + "text.culture=ar-SA" + ], + [ + "shape.site=in-generic-type", + "text.culture=de-DE" + ], + [ + "shape.site=in-generic-type", + "text.culture=globalization-invariant" + ], + [ + "shape.site=in-generic-type", + "text.culture=invariant" + ], + [ + "shape.site=in-generic-type", + "text.culture=tr-TR" + ], + [ + "shape.site=in-generic-type", + "text.entry=IParsable" + ], + [ + "shape.site=in-generic-type", + "text.entry=ISpanParsable" + ], + [ + "shape.site=in-generic-type", + "text.entry=Parse" + ], + [ + "shape.site=in-generic-type", + "text.entry=ToString-format" + ], + [ + "shape.site=in-generic-type", + "text.entry=TryFormat-short-span" + ], + [ + "shape.site=in-generic-type", + "text.entry=TryFrom" + ], + [ + "shape.site=in-generic-type", + "text.entry=TryParse" + ], + [ + "shape.site=in-generic-type", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=in-generic-type" + ] + ] + }, + { + "reason": "A record struct has no base type, so it cannot derive from a base record or inherit a base's explicit IEquatable.", + "pairs": [ + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ] + ] + }, + { + "reason": "The parse shape is the wrapped type itself, a class of its own; a value object over one of the catalogue's wrapped types is the ordinary shape.", + "pairs": [ + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=DateOnly" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=DateTime" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=DateTimeOffset" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=Guid" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=NodaTime.Instant" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=TimeOnly" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=TimeSpan" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=Uri" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=bool" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=byte" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=char" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=decimal" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=double" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=enum" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=flags-enum" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=int" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=long" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=own-record-class" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=string" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.wrapped=ulong" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=DateOnly" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=DateTime" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=DateTimeOffset" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=Guid" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=NodaTime.Instant" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=TimeOnly" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=TimeSpan" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=Uri" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=bool" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=byte" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=char" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=decimal" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=double" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=enum" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=flags-enum" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=int" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=long" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=own-record-class" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=string" + ], + [ + "parse-shape=implicit-result", + "shape.wrapped=ulong" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=DateOnly" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=DateTime" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=DateTimeOffset" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=Guid" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=NodaTime.Instant" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=TimeOnly" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=TimeSpan" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=Uri" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=bool" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=byte" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=char" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=decimal" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=double" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=enum" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=flags-enum" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=int" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=long" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=own-record-class" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=string" + ], + [ + "parse-shape=ref-text", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A declaration of a refused kind fails the build (CMTK1005, CMTK1006), so it is generated into nothing; .claude/probe-axes.md: shape.refused-kind is taken only where the refusal is the claim.", + "pairs": [ + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "text.entry=type-converter" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "parse-shape=abstract-string-ctor", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "parse-shape=implicit-result", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "parse-shape=ordinary", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "parse-shape=ref-text", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "process.converter-lookup=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.culture=ar-SA" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.culture=de-DE" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.culture=globalization-invariant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.culture=invariant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.culture=tr-TR" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=IParsable" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=ISpanParsable" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=Parse" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=ToString-format" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=TryFormat-short-span" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=TryFrom" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=TryParse" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "text.entry=type-converter" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "text.entry=type-converter" + ] + ] + }, + { + "reason": "A `Uri`, an enum and a NodaTime type have neither (Generators README, What is generated: no ISpanParsable or IUtf8SpanParsable where the underlying type has none, not-promised no-span-parsing-without-the-underlying-types), so parsing-where-it-can-be-parsed is inapplicable there and no check that reads shape.wrapped can credit them.", + "pairs": [ + [ + "shape.wrapped=NodaTime.Instant", + "text.entry=ISpanParsable" + ], + [ + "shape.wrapped=Uri", + "text.entry=ISpanParsable" + ], + [ + "shape.wrapped=enum", + "text.entry=ISpanParsable" + ], + [ + "shape.wrapped=flags-enum", + "text.entry=ISpanParsable" + ] + ] + } + ] + }, + { + "id": "C12-described-converter", + "source": "docs/json.md#describing-what-a-converter-writes", + "surfaces": [ + "http" + ], + "axes": { + "source": [ + "IDescribedJsonConverter", + "JsonSchemaDescriptions", + "decimal-converter" + ], + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "own-converter-describes-itself", + "sentence": "**Your own converter** implements `IDescribedJsonConverter` and describes itself, as both decimal converters do.", + "axes": { + "source": [ + "IDescribedJsonConverter", + "decimal-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "source": [ + "IDescribedJsonConverter", + "JsonSchemaDescriptions" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "options-describe", + "sentence": "**A converter someone else wrote**, or the serializer's own enum converter, is described in the options by `JsonSchemaDescriptions`.", + "axes": { + "source": [ + "JsonSchemaDescriptions" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "source": [ + "IDescribedJsonConverter", + "JsonSchemaDescriptions" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "every-position", + "sentence": "With CodoMetis.TypeKit.AspNetCore, `AddTypeKit()` writes the same descriptions into the OpenAPI document, in every position, a list's elements and a dictionary's values included.", + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "source": [ + "IDescribedJsonConverter", + "JsonSchemaDescriptions" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "parameter-keeps-aspnet", + "sentence": "So it keeps the description ASP.NET gives the type without your converters", + "axes": { + "source": [ + "decimal-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "source": [ + "IDescribedJsonConverter", + "JsonSchemaDescriptions" + ] + }, + "cite": "record:0099" + } + ] + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C13-patch-required", + "source": "docs/merge-patches.md#required-constructor-parameters", + "surfaces": [ + "http" + ], + "axes": { + "declared": [ + "init", + "ctor-parameter" + ], + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "doc-required-where-rejected", + "sentence": "So the document never calls a field optional when the endpoint rejects a request without it.", + "axes": { + "declared": [ + "ctor-parameter" + ], + "json.RespectRequiredConstructorParameters": [ + "true" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "doc-optional-where-absent", + "sentence": "`AddTypeKit()` leaves a patch out of `required` where the serializer lets it be absent, and keeps it there where the options reject a body without it.", + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "absent-is-default", + "sentence": "a property the request leaves out keeps its default, which is `Absent`." + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C14-declared-problem", + "source": "CHANGELOG.md#2.1.0-fixed", + "surfaces": [ + "http" + ], + "axes": { + "declared": [ + "ProducesProblem", + "ProducesResponseType", + "none" + ], + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "declared-describes-fault", + "sentence": "OpenAPI: a problem response an endpoint declares itself (`ProducesProblem`, `[ProducesResponseType]`) describes `fault`, and names its problem types.", + "axes": { + "declared": [ + "ProducesProblem", + "ProducesResponseType" + ] + } + }, + { + "id": "declared-names-types", + "sentence": "OpenAPI: a problem response an endpoint declares itself (`ProducesProblem`, `[ProducesResponseType]`) describes `fault`, and names its problem types.", + "axes": { + "declared": [ + "ProducesProblem", + "ProducesResponseType" + ] + } + }, + { + "id": "ascending-with-declared", + "sentence": "OpenAPI: an operation with fault responses lists every status ascending, the ones its endpoint declares itself among them", + "axes": { + "declared": [ + "ProducesProblem", + "ProducesResponseType" + ] + } + }, + { + "id": "application-json-declared", + "sentence": "OpenAPI: a problem response an endpoint declares as `application/json`, such as `Produces<ProblemDetails>(404)` or `[ProducesResponseType<ProblemDetails>(404)]`, describes `fault` and `rule` and names its problem types, and keeps its media type", + "axes": { + "declared": [ + "ProducesResponseType" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C15-mvc-fault-status-doc", + "source": "CHANGELOG.md#2.1.0-fixed", + "surfaces": [ + "http" + ], + "axes": { + "setter": [ + "MapControllers().WithFaultStatus", + "action-attribute", + "controller-attribute" + ], + "kind": [ + "status-only", + "WithoutFaults" + ], + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "builder-reaches-doc", + "sentence": "OpenAPI: a `WithFaultStatus` on the builder `MapControllers()` returns reaches the document, as it reached the response." + }, + { + "id": "without-faults-leaves-doc", + "sentence": "**`WithoutFaults`** and `[WithoutFaults<T>]`: a route leaves out of its document the members it never answers with, and reporting one throws" + }, + { + "id": "status-per-scope", + "sentence": "`WithFaultStatus`/`[FaultStatus<T>]` per endpoint, group or controller" + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C16-options-agreement", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "surfaces": [ + "http" + ], + "axes": { + "difference": [ + "converter-only-http", + "naming-policy", + "number-handling" + ], + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "differing-refused", + "sentence": "Otherwise building the document fails rather than describe JSON the controllers don't write.", + "axes": { + "difference": [ + "converter-only-http" + ], + "http.host": [ + "mvc", + "both", + "controller-without-ApiController-visible" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "error-names-type-and-descriptions", + "sentence": "The error names the type and what each set of options says of it, also for a type held only inside a patch.", + "axes": { + "difference": [ + "converter-only-http" + ], + "http.host": [ + "mvc", + "both", + "controller-without-ApiController-visible" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "patch-held-refused", + "sentence": "The error names the type and what each set of options says of it, also for a type held only inside a patch.", + "axes": { + "difference": [ + "converter-only-http" + ], + "http.host": [ + "mvc", + "both", + "controller-without-ApiController-visible" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "same-converters-build", + "sentence": "A host with controllers registers the same converters and descriptions on both, in the same order.", + "axes": { + "difference": [ + "naming-policy", + "number-handling" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "minimal-host-not-compared", + "sentence": "A host with controllers registers the same converters and descriptions on both, in the same order.", + "axes": { + "difference": [ + "converter-only-http" + ], + "http.host": [ + "minimal", + "route-group" + ] + }, + "inapplicable": [ + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + } + ] + } + ], + "not-promised": [ + { + "id": "only-converters-and-descriptions-compared", + "source": "design/adr/0071-converters-say-what-they-write.md#0071-json-converters-say-what-they-write-decimals-get-one-spelling", + "sentence": "Where the host has a controller, building the document fails when the two option sets do not carry the same described converters and description sets, by type, naming each that differs; a host of minimal APIs alone is never compared.", + "axes": { + "difference": [ + "naming-policy", + "number-handling" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "C17-cmtk0014", + "source": "src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0014", + "surfaces": [ + "analyzers" + ], + "axes": { + "value": [ + "(FaultKind)9", + "default", + "valid" + ], + "site": [ + "attribute-argument", + "cast", + "default-literal" + ] + }, + "obligations": [ + { + "id": "a-cast-naming-no-member", + "sentence": "A `FaultKind` or a `ConstructorAccessibility` that names no member: a cast", + "axes": { + "value": [ + "(FaultKind)9" + ] + } + }, + { + "id": "default-and-zero", + "sentence": "`default(FaultKind)`, a zero", + "axes": { + "value": [ + "default" + ] + } + }, + { + "id": "constructor-accessibility-too", + "sentence": "[RequireParameterlessConstructor((ConstructorAccessibility)7)] // CMTK0014: no member, read as public", + "axes": { + "site": [ + "attribute-argument" + ], + "value": [ + "(FaultKind)9" + ] + } + }, + { + "id": "reported-where-written", + "sentence": "Both are reported where the value is written:", + "axes": { + "value": [ + "(FaultKind)9", + "default" + ] + } + }, + { + "id": "a-member-is-silent", + "sentence": "public static readonly ImportFault Duplicate = new(FaultKind.Conflict);", + "axes": { + "value": [ + "valid" + ] + } + } + ], + "exclude": [ + { + "reason": "A default literal is the value 0, so it cannot spell (FaultKind)9 or a member: FaultKind's members start at 1.", + "pairs": [ + [ + "site=default-literal", + "value=(FaultKind)9" + ], + [ + "site=default-literal", + "value=valid" + ] + ] + }, + { + "reason": "A record struct has no base type, so it cannot derive from a base record or inherit a base's explicit IEquatable.", + "pairs": [ + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ] + ] + }, + { + "reason": "A closed set wraps no type, so a closed set over a wrapped type cannot be declared.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "Declared as .claude/probe-axes.md says (as C22's harness, BuildRefusals.cs, declares it), a closed set named as one of its generated members leaves their writing to the generators, so in this compilation without them it does not implement IClosedSet's members: the compiler's own CS0535, and no row can hold it beside the analyzer's places.", + "pairs": [ + [ + "shape.containing=class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=field", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=root", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=attribute-argument" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=cast" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=default-literal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "value=(FaultKind)9" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "value=default" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "value=valid" + ] + ] + }, + { + "reason": "A value object with a hand-written operator == and != between two of the type is C#'s own CS0111 without the generators (Generators README, Build errors: CMTK1011, 'which C# also reports as CS0111'), since a record already declares both, so this compilation without the generators cannot hold it.", + "pairs": [ + [ + "shape.containing=class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=field", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=root", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=attribute-argument" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=cast" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=default-literal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "value=(FaultKind)9" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "value=default" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "value=valid" + ] + ] + } + ] + }, + { + "id": "C18-cmtk1023", + "source": "src/CodoMetis.TypeKit.Generators/README.md#build-errors", + "surfaces": [ + "generation" + ], + "axes": { + "base-member": [ + "Equals", + "GetHashCode", + "IEquatable" + ] + }, + "obligations": [ + { + "id": "names-each-base-member", + "sentence": "So a hand-written `Equals(TBase)`, `GetHashCode()` or `IEquatable<TBase>.Equals` on a base record isn't used, and a warning names it (CMTK1023)." + }, + { + "id": "a-warning-and-generated", + "sentence": "A warning, and the value object is generated." + }, + { + "id": "generated-equality-never-calls-the-base", + "sentence": "Over a base record, the generated `Equals(TSelf)` and `GetHashCode()` compare the underlying value alone, and never call the base's." + } + ], + "exclude": [ + { + "reason": "A record struct or a closed set has no base record: C# gives a struct no base type, and a closed set is a sealed class that is never a record (CMTK1020). CMTK1023 is about a base record's equality.", + "pairs": [ + [ + "base-member=Equals", + "shape.kind=closed-set" + ], + [ + "base-member=Equals", + "shape.kind=record-struct" + ], + [ + "base-member=Equals", + "shape.kind=validated-struct" + ], + [ + "base-member=GetHashCode", + "shape.kind=closed-set" + ], + [ + "base-member=GetHashCode", + "shape.kind=record-struct" + ], + [ + "base-member=GetHashCode", + "shape.kind=validated-struct" + ], + [ + "base-member=IEquatable", + "shape.kind=closed-set" + ], + [ + "base-member=IEquatable", + "shape.kind=record-struct" + ], + [ + "base-member=IEquatable", + "shape.kind=validated-struct" + ], + [ + "shape.containing=class", + "shape.kind=closed-set" + ], + [ + "shape.containing=class", + "shape.kind=record-struct" + ], + [ + "shape.containing=class", + "shape.kind=validated-struct" + ], + [ + "shape.containing=generic-type", + "shape.kind=closed-set" + ], + [ + "shape.containing=generic-type", + "shape.kind=record-struct" + ], + [ + "shape.containing=generic-type", + "shape.kind=validated-struct" + ], + [ + "shape.containing=record-class", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-class", + "shape.kind=record-struct" + ], + [ + "shape.containing=record-class", + "shape.kind=validated-struct" + ], + [ + "shape.containing=record-struct", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-struct", + "shape.kind=record-struct" + ], + [ + "shape.containing=record-struct", + "shape.kind=validated-struct" + ], + [ + "shape.containing=struct", + "shape.kind=closed-set" + ], + [ + "shape.containing=struct", + "shape.kind=record-struct" + ], + [ + "shape.containing=struct", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=none", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=none", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=none", + "shape.kind=validated-struct" + ], + [ + "shape.kind=closed-set", + "shape.position=array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-key" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-value" + ], + [ + "shape.kind=closed-set", + "shape.position=field" + ], + [ + "shape.kind=closed-set", + "shape.position=immutable-array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=in-patch" + ], + [ + "shape.kind=closed-set", + "shape.position=list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=nullable-property" + ], + [ + "shape.kind=closed-set", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.kind=closed-set", + "shape.position=property" + ], + [ + "shape.kind=closed-set", + "shape.position=read-only-list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=record-ctor-parameter" + ], + [ + "shape.kind=closed-set", + "shape.position=required-init" + ], + [ + "shape.kind=closed-set", + "shape.position=root" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=closed-set", + "shape.site=in-generic-type" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-class" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=closed-set", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=closed-set", + "shape.site=top-level" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ], + [ + "shape.kind=record-struct", + "shape.position=array-element" + ], + [ + "shape.kind=record-struct", + "shape.position=dictionary-key" + ], + [ + "shape.kind=record-struct", + "shape.position=dictionary-value" + ], + [ + "shape.kind=record-struct", + "shape.position=field" + ], + [ + "shape.kind=record-struct", + "shape.position=immutable-array-element" + ], + [ + "shape.kind=record-struct", + "shape.position=in-patch" + ], + [ + "shape.kind=record-struct", + "shape.position=list-element" + ], + [ + "shape.kind=record-struct", + "shape.position=nullable-property" + ], + [ + "shape.kind=record-struct", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.kind=record-struct", + "shape.position=property" + ], + [ + "shape.kind=record-struct", + "shape.position=read-only-list-element" + ], + [ + "shape.kind=record-struct", + "shape.position=record-ctor-parameter" + ], + [ + "shape.kind=record-struct", + "shape.position=required-init" + ], + [ + "shape.kind=record-struct", + "shape.position=root" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-in-class" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=record-struct", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=record-struct", + "shape.site=top-level" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=Guid" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=Uri" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=bool" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=byte" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=char" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=decimal" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=double" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=enum" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=int" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=long" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=string" + ], + [ + "shape.kind=record-struct", + "shape.wrapped=ulong" + ], + [ + "shape.kind=validated-struct", + "shape.position=array-element" + ], + [ + "shape.kind=validated-struct", + "shape.position=dictionary-key" + ], + [ + "shape.kind=validated-struct", + "shape.position=dictionary-value" + ], + [ + "shape.kind=validated-struct", + "shape.position=field" + ], + [ + "shape.kind=validated-struct", + "shape.position=immutable-array-element" + ], + [ + "shape.kind=validated-struct", + "shape.position=in-patch" + ], + [ + "shape.kind=validated-struct", + "shape.position=list-element" + ], + [ + "shape.kind=validated-struct", + "shape.position=nullable-property" + ], + [ + "shape.kind=validated-struct", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.kind=validated-struct", + "shape.position=property" + ], + [ + "shape.kind=validated-struct", + "shape.position=read-only-list-element" + ], + [ + "shape.kind=validated-struct", + "shape.position=record-ctor-parameter" + ], + [ + "shape.kind=validated-struct", + "shape.position=required-init" + ], + [ + "shape.kind=validated-struct", + "shape.position=root" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-in-class" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-in-struct" + ], + [ + "shape.kind=validated-struct", + "shape.site=nested-two-levels" + ], + [ + "shape.kind=validated-struct", + "shape.site=top-level" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=Guid" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=Uri" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=bool" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=byte" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=char" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=decimal" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=double" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=enum" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=int" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=long" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=string" + ], + [ + "shape.kind=validated-struct", + "shape.wrapped=ulong" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ] + ] + }, + { + "reason": "A value object nested in a generic type fails the build with CMTK1005 (Generators README, Build errors), so it has no generated equality or parsing to check.", + "pairs": [ + [ + "base-member=Equals", + "shape.site=in-generic-type" + ], + [ + "base-member=GetHashCode", + "shape.site=in-generic-type" + ], + [ + "base-member=IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.containing=class", + "shape.site=in-generic-type" + ], + [ + "shape.containing=generic-type", + "shape.site=in-generic-type" + ], + [ + "shape.containing=record-class", + "shape.site=in-generic-type" + ], + [ + "shape.containing=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.containing=struct", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-1", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-2", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-3", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=none", + "shape.site=in-generic-type" + ], + [ + "shape.kind=sealed-record-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-class", + "shape.site=in-generic-type" + ], + [ + "shape.position=array-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=dictionary-key", + "shape.site=in-generic-type" + ], + [ + "shape.position=dictionary-value", + "shape.site=in-generic-type" + ], + [ + "shape.position=field", + "shape.site=in-generic-type" + ], + [ + "shape.position=immutable-array-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=in-patch", + "shape.site=in-generic-type" + ], + [ + "shape.position=list-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=nullable-property", + "shape.site=in-generic-type" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.site=in-generic-type" + ], + [ + "shape.position=property", + "shape.site=in-generic-type" + ], + [ + "shape.position=read-only-list-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=record-ctor-parameter", + "shape.site=in-generic-type" + ], + [ + "shape.position=required-init", + "shape.site=in-generic-type" + ], + [ + "shape.position=root", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=in-generic-type" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTime" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Guid" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeSpan" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Uri" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=bool" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=byte" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=char" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=decimal" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=double" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=flags-enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=int" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=long" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=own-record-class" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=string" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=in-generic-type" + ] + ] + }, + { + "reason": "Without a base record there is no base to declare Equals, GetHashCode or IEquatable on.", + "pairs": [ + [ + "base-member=Equals", + "shape.inheritance=none" + ], + [ + "base-member=GetHashCode", + "shape.inheritance=none" + ], + [ + "base-member=IEquatable", + "shape.inheritance=none" + ], + [ + "shape.containing=class", + "shape.inheritance=none" + ], + [ + "shape.containing=generic-type", + "shape.inheritance=none" + ], + [ + "shape.containing=record-class", + "shape.inheritance=none" + ], + [ + "shape.containing=record-struct", + "shape.inheritance=none" + ], + [ + "shape.containing=struct", + "shape.inheritance=none" + ], + [ + "shape.inheritance=none", + "shape.kind=sealed-record-class" + ], + [ + "shape.inheritance=none", + "shape.kind=validated-class" + ], + [ + "shape.inheritance=none", + "shape.position=array-element" + ], + [ + "shape.inheritance=none", + "shape.position=dictionary-key" + ], + [ + "shape.inheritance=none", + "shape.position=dictionary-value" + ], + [ + "shape.inheritance=none", + "shape.position=field" + ], + [ + "shape.inheritance=none", + "shape.position=immutable-array-element" + ], + [ + "shape.inheritance=none", + "shape.position=in-patch" + ], + [ + "shape.inheritance=none", + "shape.position=list-element" + ], + [ + "shape.inheritance=none", + "shape.position=nullable-property" + ], + [ + "shape.inheritance=none", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.inheritance=none", + "shape.position=property" + ], + [ + "shape.inheritance=none", + "shape.position=read-only-list-element" + ], + [ + "shape.inheritance=none", + "shape.position=record-ctor-parameter" + ], + [ + "shape.inheritance=none", + "shape.position=required-init" + ], + [ + "shape.inheritance=none", + "shape.position=root" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=none", + "shape.site=nested-in-class" + ], + [ + "shape.inheritance=none", + "shape.site=nested-in-struct" + ], + [ + "shape.inheritance=none", + "shape.site=nested-two-levels" + ], + [ + "shape.inheritance=none", + "shape.site=top-level" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateOnly" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateTime" + ], + [ + "shape.inheritance=none", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.inheritance=none", + "shape.wrapped=Guid" + ], + [ + "shape.inheritance=none", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.inheritance=none", + "shape.wrapped=TimeOnly" + ], + [ + "shape.inheritance=none", + "shape.wrapped=TimeSpan" + ], + [ + "shape.inheritance=none", + "shape.wrapped=Uri" + ], + [ + "shape.inheritance=none", + "shape.wrapped=bool" + ], + [ + "shape.inheritance=none", + "shape.wrapped=byte" + ], + [ + "shape.inheritance=none", + "shape.wrapped=char" + ], + [ + "shape.inheritance=none", + "shape.wrapped=decimal" + ], + [ + "shape.inheritance=none", + "shape.wrapped=double" + ], + [ + "shape.inheritance=none", + "shape.wrapped=enum" + ], + [ + "shape.inheritance=none", + "shape.wrapped=flags-enum" + ], + [ + "shape.inheritance=none", + "shape.wrapped=int" + ], + [ + "shape.inheritance=none", + "shape.wrapped=long" + ], + [ + "shape.inheritance=none", + "shape.wrapped=own-record-class" + ], + [ + "shape.inheritance=none", + "shape.wrapped=string" + ], + [ + "shape.inheritance=none", + "shape.wrapped=ulong" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ] + ] + }, + { + "reason": "CMTK1023 is 'A warning, and the value object is generated' (Generators README, Build errors); a refused declaration is not generated. A declaration of a refused kind fails the build (CMTK1005, CMTK1006), so it is generated into nothing; .claude/probe-axes.md: shape.refused-kind is taken only where the refusal is the claim.", + "pairs": [ + [ + "base-member=Equals", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.containing=class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=field", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=property", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.position=root", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=over-value-object (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.wrapped=ulong" + ], + [ + "base-member=Equals", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-member=Equals", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-member=Equals", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-member=GetHashCode", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "base-member=IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.containing=class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=field", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=field", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=field", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=property", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=root", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.position=root", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=root", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=ulong" + ] + ] + } + ] + }, + { + "id": "C19-cmtk0010-kinds", + "source": "docs/type-constraints.md#the-kind-of-a-type-argument", + "surfaces": [ + "analyzers" + ], + "axes": { + "attribute": [ + "Interface", + "Unsealed", + "ValueObject", + "ClosedSet", + "FlagsEnum", + "ReadOnlyStruct", + "Distinct", + "Unrelated" + ], + "site": [ + "call", + "foreach", + "await", + "Deconstruct", + "collection-expression" + ] + }, + "obligations": [ + { + "id": "interface-and-unsealed", + "sentence": "**`[Interface]` and `[Unsealed]`** are what a proxy needs: a contract to implement, or a class to derive from.", + "axes": { + "attribute": [ + "Interface", + "Unsealed" + ] + } + }, + { + "id": "value-object-and-closed-set", + "sentence": "**`[ValueObject]` and `[ClosedSet]`** accept their marker interface with any type argument.", + "axes": { + "attribute": [ + "ValueObject", + "ClosedSet" + ] + } + }, + { + "id": "flags-enum-and-read-only-struct", + "sentence": "**`[FlagsEnum]` and `[ReadOnlyStruct]`** narrow what `where T : Enum` and `where T : struct` admit.", + "axes": { + "attribute": [ + "FlagsEnum", + "ReadOnlyStruct" + ] + } + }, + { + "id": "a-pair-once-at-the-later-argument", + "sentence": "A breach is reported once, at the later argument.", + "axes": { + "attribute": [ + "Distinct", + "Unrelated" + ] + } + }, + { + "id": "methods-the-compiler-calls", + "sentence": "**Methods the compiler calls for you:** a `foreach`'s `GetEnumerator`, an `await`'s `GetAwaiter`, a `Deconstruct` (in `var (a, b) = x`, a `foreach`, or a pattern like `x is (1, 2)`), and a collection expression's `Add` or builder method.", + "axes": { + "site": [ + "foreach", + "await", + "Deconstruct", + "collection-expression" + ] + } + } + ], + "exclude": [ + { + "reason": "A record struct has no base type, so it cannot derive from a base record or inherit a base's explicit IEquatable.", + "pairs": [ + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ] + ] + }, + { + "reason": "A closed set wraps no type, so a closed set over a wrapped type cannot be declared.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "Declared as .claude/probe-axes.md says (as C22's harness, BuildRefusals.cs, declares it), a closed set named as one of its generated members leaves their writing to the generators, so in this compilation without them it does not implement IClosedSet's members: the compiler's own CS0535, and no row can hold it beside the analyzer's places.", + "pairs": [ + [ + "attribute=ClosedSet", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=Distinct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=FlagsEnum", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=Interface", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=ReadOnlyStruct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=Unrelated", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=Unsealed", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "attribute=ValueObject", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=field", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=property", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.position=root", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=Deconstruct" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=await" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=call" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=collection-expression" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "site=foreach" + ] + ] + }, + { + "reason": "A value object with a hand-written operator == and != between two of the type is C#'s own CS0111 without the generators (Generators README, Build errors: CMTK1011, 'which C# also reports as CS0111'), since a record already declares both, so this compilation without the generators cannot hold it.", + "pairs": [ + [ + "attribute=ClosedSet", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=Distinct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=FlagsEnum", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=Interface", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=ReadOnlyStruct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=Unrelated", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=Unsealed", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "attribute=ValueObject", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=generic-type", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.containing=struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.inheritance=none", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-key", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=dictionary-value", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=field", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=immutable-array-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=in-patch", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=nullable-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=property", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=read-only-list-element", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=record-ctor-parameter", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=required-init", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.position=root", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-in-struct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=nested-two-levels" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=top-level" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=Deconstruct" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=await" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=call" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=collection-expression" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "site=foreach" + ] + ] + } + ] + }, + { + "id": "C20-testing-shouldhold", + "source": "src/CodoMetis.TypeKit.Testing/README.md", + "surfaces": [ + "option-result" + ], + "axes": { + "arity": [ + "2", + "3" + ], + "set": [ + "first", + "last" + ], + "wrong-reason": [ + "no", + "yes" + ] + }, + "obligations": [ + { + "id": "fails-unless-it-holds-that-reason", + "sentence": "`ShouldHold(reason)` fails unless the fault holds that reason, whichever of its types the reason belongs to." + }, + { + "id": "whichever-of-its-types", + "sentence": "whichever of its types the reason belongs to", + "axes": { + "set": [ + "first", + "last" + ], + "arity": [ + "2", + "3" + ] + } + }, + { + "id": "hands-back-detail-and-rule", + "sentence": "It hands back the `Fault<TFault>` with its detail and rule intact.", + "axes": { + "wrong-reason": [ + "no" + ] + } + }, + { + "id": "uninitialized-fails", + "sentence": "**An uninitialized result fails every assertion.**", + "axes": { + "or.operation": [ + "default-instance" + ] + } + }, + { + "id": "a-failure-says-the-outcome", + "sentence": "**A failure says what the outcome was**", + "axes": { + "wrong-reason": [ + "yes" + ] + } + } + ], + "exclude": [ + { + "reason": "Result.Catch turns an exception into a Result, never an Option (src/CodoMetis.TypeKit/ResultExtensions.cs), so no row can catch into an Option.", + "pairs": [ + [ + "or.operation=Result.Catch", + "or.shape=Option" + ] + ] + }, + { + "reason": "Result<TError> has no Select or SelectMany, so no query expression produces one, and FirstOrNone returns an Option<T>, never a Result<TError> (src/CodoMetis.TypeKit/ResultExtensions.cs and OptionExtensions.cs).", + "pairs": [ + [ + "or.operation=FirstOrNone", + "or.shape=Result-E" + ], + [ + "or.operation=query-syntax", + "or.shape=Result-E" + ] + ] + } + ] + }, + { + "id": "C21-openapi-schema-parity", + "source": "README.md#what-you-get", + "surfaces": [ + "http", + "json" + ], + "axes": { + "http.host": [ + "minimal", + "mvc", + "both", + "route-group", + "controller-without-ApiController-visible" + ] + }, + "obligations": [ + { + "id": "the-underlying-types-schema", + "sentence": "The JSON, the database column and the OpenAPI schema are the same as the underlying type's.", + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "not-promised:own-converter-for-the-value-object" + }, + { + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + }, + "cite": "not-promised:own-converter-for-the-wrapped-type" + }, + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.NumberHandling": [ + "WriteAsString" + ], + "shape.wrapped": [ + "char" + ] + }, + "cite": "fact:KB-042" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "shape.wrapped": [ + "own-record-class" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "nullable-admits-null-as-the-wrapped-does", + "sentence": "Its OpenAPI schema is the one ASP.NET Core publishes for the underlying type.", + "axes": { + "http.nullability": [ + "nullable" + ] + }, + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "not-promised:own-converter-for-the-value-object" + }, + { + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + }, + "cite": "not-promised:own-converter-for-the-wrapped-type" + }, + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.NumberHandling": [ + "WriteAsString" + ], + "shape.wrapped": [ + "char" + ] + }, + "cite": "fact:KB-042" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "shape.wrapped": [ + "own-record-class" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "container-elements", + "sentence": "Its OpenAPI schema is the one ASP.NET Core publishes for the underlying type.", + "axes": { + "shape.position": [ + "list-element", + "array-element", + "immutable-array-element", + "read-only-list-element", + "dictionary-value" + ] + }, + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "not-promised:own-converter-for-the-value-object" + }, + { + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + }, + "cite": "not-promised:own-converter-for-the-wrapped-type" + }, + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.NumberHandling": [ + "WriteAsString" + ], + "shape.wrapped": [ + "char" + ] + }, + "cite": "fact:KB-042" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "shape.wrapped": [ + "own-record-class" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "dictionary-key", + "sentence": "Its OpenAPI schema is the one ASP.NET Core publishes for the underlying type.", + "axes": { + "shape.position": [ + "dictionary-key" + ] + }, + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "not-promised:own-converter-for-the-value-object" + }, + { + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + }, + "cite": "not-promised:own-converter-for-the-wrapped-type" + }, + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.NumberHandling": [ + "WriteAsString" + ], + "shape.wrapped": [ + "char" + ] + }, + "cite": "fact:KB-042" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "shape.wrapped": [ + "own-record-class" + ] + }, + "cite": "record:0099" + } + ] + }, + { + "id": "every-document-version", + "sentence": "Its OpenAPI schema is the one ASP.NET Core publishes for the underlying type.", + "axes": { + "openapi.OpenApiVersion": [ + "OpenApi2_0", + "OpenApi3_0" + ] + }, + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "not-promised:own-converter-for-the-value-object" + }, + { + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + }, + "cite": "not-promised:own-converter-for-the-wrapped-type" + }, + { + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ] + }, + "cite": "record:0099" + }, + { + "axes": { + "json.NumberHandling": [ + "WriteAsString" + ], + "shape.wrapped": [ + "char" + ] + }, + "cite": "fact:KB-042" + }, + { + "axes": { + "json.MaxDepth": [ + "3" + ], + "shape.wrapped": [ + "own-record-class" + ] + }, + "cite": "record:0099" + } + ] + } + ], + "not-promised": [ + { + "id": "a-closed-set-is-a-string-enum", + "source": "README.md#what-you-get", + "sentence": "One line per member gives you parsing by name, JSON as the name, an OpenAPI string enum and a text column.", + "axes": { + "shape.kind": [ + "closed-set" + ] + } + }, + { + "id": "own-converter-for-the-value-object", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "sentence": "That converter then writes it instead of the generated one, so it's described as that converter says (see \"A described type\" below).", + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + } + }, + { + "id": "own-converter-for-the-wrapped-type", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "sentence": "ASP.NET can't see through a JSON converter you write yourself. It writes the type as `{}`, or with only the `format` its CLR type maps to. It also drops a list's `items` and a dictionary's `additionalProperties` of that type.", + "axes": { + "json.Converters": [ + "own-for-wrapped" + ] + } + }, + { + "id": "a-class-value-object-admits-null", + "source": "design/adr/0036-nullable-value-object-admits-null-openapi.md#0036-a-nullable-value-object-admits-null-in-the-openapi-document-wherever-it-appears", + "sentence": "A nullable value object admits null in the OpenAPI document wherever it appears", + "axes": { + "shape.kind": [ + "sealed-record-class", + "validated-class" + ] + } + }, + { + "id": "a-nullable-value-object-admits-null", + "source": "design/adr/0036-nullable-value-object-admits-null-openapi.md#0036-a-nullable-value-object-admits-null-in-the-openapi-document-wherever-it-appears", + "sentence": "A nullable value object admits null in the OpenAPI document wherever it appears", + "axes": { + "http.nullability": [ + "nullable" + ] + } + }, + { + "id": "openapi-2-nullable-component-loses-its-type", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "sentence": "So under 2.0 a nullable value-object property loses its type, where the underlying type's nullable property keeps it.", + "axes": { + "openapi.OpenApiVersion": [ + "OpenApi2_0" + ], + "openapi.CreateSchemaReferenceId": [ + "default", + "own-prefixing" + ] + } + }, + { + "id": "enum-component-is-its-first-use", + "source": "design/knowledge/KB-027-an-enum-component-is-its-first-use.md#kb-027-the-component-aspnet-makes-for-an-enum-written-by-name-is-the-schema-of-the-first-use-it-meets", + "sentence": "So whether a parameter of the wrapped `E?` admits null in a document with components depends on the endpoints and their order, not on the parameter.", + "axes": { + "json.Converters": [ + "JsonStringEnumConverter" + ], + "http.nullability": [ + "nullable" + ], + "shape.wrapped": [ + "enum" + ] + } + }, + { + "id": "openapi-2-nullable-body-keeps-only-its-null", + "source": "design/knowledge/KB-001-openapi-2-nullable-body-drops-pattern.md#kb-001-under-openapi-20-aspnet-writes-a-nullable-request-body-as-allof-x-nullable-true-and-drops-its-pattern", + "sentence": "A minimal API writes it the same, and a nullable enum body, which has no `format`, keeps nothing but the null:", + "axes": { + "openapi.OpenApiVersion": [ + "OpenApi2_0" + ], + "http.binding": [ + "body" + ], + "http.nullability": [ + "nullable" + ], + "shape.kind": [ + "sealed-record-class", + "validated-class" + ] + } + }, + { + "id": "enum-component-lists-the-null-of-its-first-use", + "source": "design/knowledge/KB-027-an-enum-component-is-its-first-use.md#kb-027-the-component-aspnet-makes-for-an-enum-written-by-name-is-the-schema-of-the-first-use-it-meets", + "sentence": "What the component says is whatever schema ASP.NET built for the first use it met: met first as a nullable parameter, the component lists null among its values, for its non-nullable uses too; met first as itself, it does not, for its nullable uses too.", + "axes": { + "json.Converters": [ + "JsonStringEnumConverter" + ], + "shape.wrapped": [ + "enum" + ] + } + }, + { + "id": "openapi-2-nullable-element-loses-its-type", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "sentence": "A nullable element of a list or a dictionary loses its type the same way, since it's the component or null too.", + "axes": { + "openapi.OpenApiVersion": [ + "OpenApi2_0" + ], + "openapi.CreateSchemaReferenceId": [ + "default", + "own-prefixing" + ], + "shape.position": [ + "list-element", + "array-element", + "immutable-array-element", + "read-only-list-element", + "dictionary-value" + ] + } + }, + { + "id": "a-librarys-converter-for-the-wrapped-type", + "source": "src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says", + "sentence": "The same goes for a converter a library registers for its own type, such as NodaTime's for `Instant`.", + "axes": { + "shape.wrapped": [ + "NodaTime.Instant" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + }, + { + "reason": "Not promised (a-closed-set-is-a-string-enum): a closed set wraps nothing and is described as the string enum of its names, so it has no underlying type's schema to equal.", + "pairs": [ + [ + "http.binding=route", + "shape.kind=closed-set" + ], + [ + "http.binding=query", + "shape.kind=closed-set" + ], + [ + "http.binding=header", + "shape.kind=closed-set" + ], + [ + "http.binding=body", + "shape.kind=closed-set" + ], + [ + "http.binding=form", + "shape.kind=closed-set" + ], + [ + "http.binding=as-parameters", + "shape.kind=closed-set" + ], + [ + "http.host=minimal", + "shape.kind=closed-set" + ], + [ + "http.host=mvc", + "shape.kind=closed-set" + ], + [ + "http.host=both", + "shape.kind=closed-set" + ], + [ + "http.host=route-group", + "shape.kind=closed-set" + ], + [ + "http.host=controller-without-ApiController-visible", + "shape.kind=closed-set" + ], + [ + "http.nullability=as-declared", + "shape.kind=closed-set" + ], + [ + "http.nullability=nullable", + "shape.kind=closed-set" + ], + [ + "http.response=result-to-http-result", + "shape.kind=closed-set" + ], + [ + "http.response=composed-fault", + "shape.kind=closed-set" + ], + [ + "http.response=two-faults-same-name", + "shape.kind=closed-set" + ], + [ + "http.response=group-with-fault-status", + "shape.kind=closed-set" + ], + [ + "http.response=endpoint-without-faults", + "shape.kind=closed-set" + ], + [ + "http.response=declared-produces-problem", + "shape.kind=closed-set" + ], + [ + "input.json=valid", + "shape.kind=closed-set" + ], + [ + "input.json=refused-by-create", + "shape.kind=closed-set" + ], + [ + "input.json=wrong-token", + "shape.kind=closed-set" + ], + [ + "input.json=null", + "shape.kind=closed-set" + ], + [ + "input.json=empty-string", + "shape.kind=closed-set" + ], + [ + "input.json=at-type-limit", + "shape.kind=closed-set" + ], + [ + "input.json=quoted-number", + "shape.kind=closed-set" + ], + [ + "input.json=escape-sequence", + "shape.kind=closed-set" + ], + [ + "input.json=duplicate-property", + "shape.kind=closed-set" + ], + [ + "input.json=metadata-property", + "shape.kind=closed-set" + ], + [ + "json.AllowDuplicateProperties=default", + "shape.kind=closed-set" + ], + [ + "json.AllowDuplicateProperties=false", + "shape.kind=closed-set" + ], + [ + "json.AllowOutOfOrderMetadataProperties=default", + "shape.kind=closed-set" + ], + [ + "json.AllowOutOfOrderMetadataProperties=true", + "shape.kind=closed-set" + ], + [ + "json.AllowTrailingCommas=default", + "shape.kind=closed-set" + ], + [ + "json.AllowTrailingCommas=true", + "shape.kind=closed-set" + ], + [ + "json.Converters=default", + "shape.kind=closed-set" + ], + [ + "json.Converters=JsonStringEnumConverter", + "shape.kind=closed-set" + ], + [ + "json.Converters=StoredJsonConverterFactory", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=default", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWritingDefault", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWritingNull", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenWriting", + "shape.kind=closed-set" + ], + [ + "json.DefaultIgnoreCondition=WhenReading", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=default", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=CamelCase", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=SnakeCaseLower", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=KebabCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.DictionaryKeyPolicy=own-upper-case", + "shape.kind=closed-set" + ], + [ + "json.Encoder=default", + "shape.kind=closed-set" + ], + [ + "json.Encoder=UnsafeRelaxedJsonEscaping", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyFields=default", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyFields=true", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyProperties=default", + "shape.kind=closed-set" + ], + [ + "json.IgnoreReadOnlyProperties=true", + "shape.kind=closed-set" + ], + [ + "json.IncludeFields=default", + "shape.kind=closed-set" + ], + [ + "json.IncludeFields=true", + "shape.kind=closed-set" + ], + [ + "json.MaxDepth=default", + "shape.kind=closed-set" + ], + [ + "json.MaxDepth=3", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=default", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=AllowReadingFromString", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=WriteAsString", + "shape.kind=closed-set" + ], + [ + "json.NumberHandling=AllowNamedFloatingPointLiterals", + "shape.kind=closed-set" + ], + [ + "json.PreferredObjectCreationHandling=default", + "shape.kind=closed-set" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "shape.kind=closed-set" + ], + [ + "json.PropertyNameCaseInsensitive=default", + "shape.kind=closed-set" + ], + [ + "json.PropertyNameCaseInsensitive=true", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=default", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=CamelCase", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=SnakeCaseLower", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=SnakeCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=KebabCaseLower", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=KebabCaseUpper", + "shape.kind=closed-set" + ], + [ + "json.PropertyNamingPolicy=own-upper-case", + "shape.kind=closed-set" + ], + [ + "json.ReadCommentHandling=default", + "shape.kind=closed-set" + ], + [ + "json.ReadCommentHandling=Skip", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=default", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=Preserve", + "shape.kind=closed-set" + ], + [ + "json.ReferenceHandler=IgnoreCycles", + "shape.kind=closed-set" + ], + [ + "json.RespectNullableAnnotations=default", + "shape.kind=closed-set" + ], + [ + "json.RespectNullableAnnotations=true", + "shape.kind=closed-set" + ], + [ + "json.RespectRequiredConstructorParameters=default", + "shape.kind=closed-set" + ], + [ + "json.RespectRequiredConstructorParameters=true", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=default", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=source-generated-context", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolver=default-with-renaming-modifier", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolverChain=default", + "shape.kind=closed-set" + ], + [ + "json.TypeInfoResolverChain=context-then-default", + "shape.kind=closed-set" + ], + [ + "json.UnknownTypeHandling=default", + "shape.kind=closed-set" + ], + [ + "json.UnknownTypeHandling=JsonNode", + "shape.kind=closed-set" + ], + [ + "json.UnmappedMemberHandling=default", + "shape.kind=closed-set" + ], + [ + "json.UnmappedMemberHandling=Disallow", + "shape.kind=closed-set" + ], + [ + "mvc.AllowEmptyInputInBodyModelBinding=default", + "shape.kind=closed-set" + ], + [ + "mvc.AllowEmptyInputInBodyModelBinding=true", + "shape.kind=closed-set" + ], + [ + "mvc.Conventions=default", + "shape.kind=closed-set" + ], + [ + "mvc.Conventions=own-route-prefix", + "shape.kind=closed-set" + ], + [ + "mvc.InputFormatters=default", + "shape.kind=closed-set" + ], + [ + "mvc.InputFormatters=xml-added", + "shape.kind=closed-set" + ], + [ + "mvc.MaxModelBindingCollectionSize=default", + "shape.kind=closed-set" + ], + [ + "mvc.MaxModelBindingCollectionSize=2", + "shape.kind=closed-set" + ], + [ + "mvc.MaxModelValidationErrors=default", + "shape.kind=closed-set" + ], + [ + "mvc.MaxModelValidationErrors=1", + "shape.kind=closed-set" + ], + [ + "mvc.MaxValidationDepth=default", + "shape.kind=closed-set" + ], + [ + "mvc.MaxValidationDepth=1", + "shape.kind=closed-set" + ], + [ + "mvc.ModelBinderProviders=default", + "shape.kind=closed-set" + ], + [ + "mvc.ModelBinderProviders=own-provider-first", + "shape.kind=closed-set" + ], + [ + "mvc.OutputFormatters=default", + "shape.kind=closed-set" + ], + [ + "mvc.OutputFormatters=xml-added", + "shape.kind=closed-set" + ], + [ + "mvc.RespectBrowserAcceptHeader=default", + "shape.kind=closed-set" + ], + [ + "mvc.RespectBrowserAcceptHeader=true", + "shape.kind=closed-set" + ], + [ + "mvc.ReturnHttpNotAcceptable=default", + "shape.kind=closed-set" + ], + [ + "mvc.ReturnHttpNotAcceptable=true", + "shape.kind=closed-set" + ], + [ + "mvc.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes=default", + "shape.kind=closed-set" + ], + [ + "mvc.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes=true", + "shape.kind=closed-set" + ], + [ + "mvc.ValidateComplexTypesIfChildValidationFails=default", + "shape.kind=closed-set" + ], + [ + "mvc.ValidateComplexTypesIfChildValidationFails=true", + "shape.kind=closed-set" + ], + [ + "openapi.CreateSchemaReferenceId=default", + "shape.kind=closed-set" + ], + [ + "openapi.CreateSchemaReferenceId=own-prefixing", + "shape.kind=closed-set" + ], + [ + "openapi.CreateSchemaReferenceId=null-inlines-all", + "shape.kind=closed-set" + ], + [ + "openapi.OpenApiVersion=default", + "shape.kind=closed-set" + ], + [ + "openapi.OpenApiVersion=OpenApi2_0", + "shape.kind=closed-set" + ], + [ + "openapi.OpenApiVersion=OpenApi3_0", + "shape.kind=closed-set" + ], + [ + "openapi.transformers=default", + "shape.kind=closed-set" + ], + [ + "openapi.transformers=own-schema-transformer", + "shape.kind=closed-set" + ], + [ + "openapi.transformers=own-operation-transformer", + "shape.kind=closed-set" + ], + [ + "openapi.transformers=own-document-transformer", + "shape.kind=closed-set" + ], + [ + "process.converter-lookup=none", + "shape.kind=closed-set" + ], + [ + "process.converter-lookup=wrapped-type-converter-looked-up-first", + "shape.kind=closed-set" + ], + [ + "shape.containing=class", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-class", + "shape.kind=closed-set" + ], + [ + "shape.containing=struct", + "shape.kind=closed-set" + ], + [ + "shape.containing=record-struct", + "shape.kind=closed-set" + ], + [ + "shape.containing=generic-type", + "shape.kind=closed-set" + ], + [ + "shape.kind=closed-set", + "shape.position=root" + ], + [ + "shape.kind=closed-set", + "shape.position=property" + ], + [ + "shape.kind=closed-set", + "shape.position=nullable-property" + ], + [ + "shape.kind=closed-set", + "shape.position=field" + ], + [ + "shape.kind=closed-set", + "shape.position=list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=immutable-array-element" + ], + [ + "shape.kind=closed-set", + "shape.position=read-only-list-element" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-key" + ], + [ + "shape.kind=closed-set", + "shape.position=dictionary-value" + ], + [ + "shape.kind=closed-set", + "shape.position=record-ctor-parameter" + ], + [ + "shape.kind=closed-set", + "shape.position=required-init" + ], + [ + "shape.kind=closed-set", + "shape.position=in-patch" + ], + [ + "shape.kind=closed-set", + "shape.position=polymorphic-derived-property" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ] + ] + } + ] + }, + { + "id": "E01-column-parity", + "source": "docs/ef-core.md#the-model", + "surfaces": [ + "ef" + ], + "axes": { + "ef.mapping": [ + "property", + "nullable", + "key", + "owned", + "complex" + ], + "ef.facet": [ + "none", + "HasMaxLength", + "HavePrecision", + "HasColumnType" + ], + "shape.wrapped": [ + "string", + "Guid", + "int", + "long", + "byte", + "ulong", + "decimal", + "double", + "bool", + "char", + "DateTime", + "DateTimeOffset", + "DateOnly", + "TimeOnly", + "TimeSpan", + "enum", + "flags-enum", + "Uri" + ], + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ] + }, + "narrow": { + "ef.query": "Every property maps as a column of the underlying type, nullable where the property is." + }, + "obligations": [ + { + "id": "column-type", + "sentence": "Every property maps as a column of the underlying type, nullable where the property is.", + "axes": { + "ef.mapping": [ + "property", + "key", + "owned", + "complex" + ] + } + }, + { + "id": "nullable", + "sentence": "nullable where the property is", + "axes": { + "ef.mapping": [ + "nullable" + ] + } + }, + { + "id": "facet-kept", + "sentence": "A facet you configure on it is kept: a precision, a length, a column type.", + "axes": { + "ef.facet": [ + "HasMaxLength", + "HavePrecision", + "HasColumnType" + ] + } + } + ], + "not-promised": [ + { + "id": "char-key-not-generated", + "source": "docs/ef-core.md#keys", + "sentence": "A key over `char` is always yours to assign", + "axes": { + "ef.mapping": [ + "key" + ], + "shape.wrapped": [ + "char" + ] + } + }, + { + "id": "guid-key-not-generated", + "source": "docs/ef-core.md#keys", + "sentence": "A key over a `Guid` is yours to assign", + "axes": { + "ef.mapping": [ + "key" + ], + "shape.wrapped": [ + "Guid" + ] + } + } + ] + }, + { + "id": "E02-value-translates-to-column", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "surfaces": [ + "ef" + ], + "axes": { + "ef.query": [ + "where-equals", + "where-value-compare", + "order-by", + "contains", + "select", + "group-by" + ], + "ef.accessor": [ + "Value", + "GetValue", + "ValueOrNull" + ], + "shape.wrapped": [ + "string", + "Guid", + "int", + "long", + "byte", + "ulong", + "decimal", + "double", + "bool", + "char", + "DateTime", + "DateTimeOffset", + "DateOnly", + "TimeOnly", + "TimeSpan", + "enum", + "flags-enum", + "Uri" + ], + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ] + }, + "narrow": { + "ef.mapping": "`.Value`, `GetValue()` and `ValueOrNull()` translate to the column itself." + }, + "obligations": [ + { + "id": "value", + "sentence": "`.Value`, `GetValue()` and `ValueOrNull()` translate to the column itself.", + "axes": { + "ef.accessor": [ + "Value" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.wrapped": [ + "DateTimeOffset", + "TimeSpan", + "ulong" + ], + "ef.query": [ + "where-value-compare", + "order-by" + ] + }, + "cite": "fact:KB-039" + } + ] + }, + { + "id": "get-value", + "sentence": "db.Orders.Where(o => o.Total.GetValue() > 100m);", + "axes": { + "ef.accessor": [ + "GetValue" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.wrapped": [ + "DateTimeOffset", + "TimeSpan", + "ulong" + ], + "ef.query": [ + "where-value-compare", + "order-by" + ] + }, + "cite": "fact:KB-039" + } + ] + }, + { + "id": "value-or-null", + "sentence": "Use `ValueOrNull()` for an optional value object, where `.Value` would first unwrap the `Nullable`.", + "axes": { + "ef.accessor": [ + "ValueOrNull" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.wrapped": [ + "DateTimeOffset", + "TimeSpan", + "ulong" + ], + "ef.query": [ + "where-value-compare", + "order-by" + ] + }, + "cite": "fact:KB-039" + } + ] + }, + { + "id": "column-not-cast", + "sentence": "A table's column is re-typed, not cast, so an index on it still serves the query.", + "inapplicable": [ + { + "axes": { + "shape.wrapped": [ + "DateTimeOffset", + "TimeSpan", + "ulong" + ], + "ef.query": [ + "where-value-compare", + "order-by" + ] + }, + "cite": "fact:KB-039" + } + ] + } + ], + "not-promised": [ + { + "id": "null-passes-no-comparison", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "sentence": "A null passes no `<`, `<=`, `>` or `>=` in the database.", + "axes": { + "ef.accessor": [ + "ValueOrNull" + ], + "ef.query": [ + "where-value-compare" + ] + } + }, + { + "id": "collation-orders", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "sentence": "Ordering in SQL follows the database's collation", + "axes": { + "ef.query": [ + "order-by" + ], + "shape.wrapped": [ + "string" + ] + } + } + ], + "exclude": [ + { + "reason": "C# defines no `>=` for a `string`, a `bool` or a `Uri`, so no query compares their `.Value` with it; E02ValueTranslatesProbe writes where-equals in its place there, so no row holds the pair.", + "pairs": [ + [ + "ef.query=where-value-compare", + "shape.wrapped=string" + ], + [ + "ef.query=where-value-compare", + "shape.wrapped=bool" + ], + [ + "ef.query=where-value-compare", + "shape.wrapped=Uri" + ] + ] + } + ] + }, + { + "id": "E03-column-round-trip", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#what-maps", + "surfaces": [ + "ef" + ], + "axes": { + "ef.mapping": [ + "property", + "nullable", + "key", + "owned", + "complex" + ], + "shape.wrapped": [ + "string", + "Guid", + "int", + "long", + "byte", + "ulong", + "decimal", + "double", + "bool", + "char", + "DateTime", + "DateTimeOffset", + "DateOnly", + "TimeOnly", + "TimeSpan", + "enum", + "flags-enum", + "Uri" + ], + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ] + }, + "narrow": { + "ef.query": "Keys, foreign keys, nullable properties, primitive collections and query parameters all map as plain columns of the underlying type." + }, + "obligations": [ + { + "id": "stored-as-underlying", + "sentence": "Every value object is stored in a plain column of its underlying type", + "axes": { + "ef.mapping": [ + "property", + "owned", + "complex" + ] + } + }, + { + "id": "keys", + "sentence": "Keys, foreign keys, nullable properties, primitive collections and query parameters all map as plain columns of the underlying type.", + "axes": { + "ef.mapping": [ + "key" + ] + } + }, + { + "id": "nullable-properties", + "sentence": "Keys, foreign keys, nullable properties, primitive collections and query parameters all map as plain columns of the underlying type.", + "axes": { + "ef.mapping": [ + "nullable" + ] + } + } + ] + }, + { + "id": "E04-integer-key-generated", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "surfaces": [ + "ef" + ], + "axes": { + "shape.wrapped": [ + "int", + "long", + "byte", + "ulong", + "char", + "Guid", + "string" + ], + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "ef.key-config": [ + "default", + "ValueGeneratedOnAdd", + "ValueGeneratedNever" + ] + }, + "narrow": { + "ef.query": "A single-column key over an integer is generated on add, like a key of that integer type.", + "ef.mapping": "A single-column key over an integer is generated on add, like a key of that integer type.", + "shape.site": "A single-column key over an integer is generated on add, like a key of that integer type." + }, + "obligations": [ + { + "id": "generated-on-add", + "sentence": "A single-column key over an integer is generated on add, like a key of that integer type.", + "axes": { + "shape.wrapped": [ + "int", + "long", + "ulong" + ] + } + }, + { + "id": "byte-not-generated", + "sentence": "That's EF's own list: `int`, `long`, `short`, `sbyte`, `uint`, `ulong` and `ushort`, and not `byte`.", + "axes": { + "shape.wrapped": [ + "byte" + ] + } + }, + { + "id": "char-key-yours", + "sentence": "A key over `char` is yours to assign.", + "axes": { + "shape.wrapped": [ + "char" + ], + "ef.key-config": [ + "default" + ] + } + }, + { + "id": "guid-key-yours", + "sentence": "A key over a `Guid` is yours to assign", + "axes": { + "shape.wrapped": [ + "Guid" + ], + "ef.key-config": [ + "default" + ] + } + }, + { + "id": "not-on-efs-list", + "sentence": "That's EF's own list: `int`, `long`, `short`, `sbyte`, `uint`, `ulong` and `ushort`, and not `byte`.", + "axes": { + "shape.wrapped": [ + "string" + ], + "ef.key-config": [ + "default" + ] + } + }, + { + "id": "own-configuration-wins", + "sentence": "A configuration you write yourself always wins over these defaults.", + "axes": { + "shape.wrapped": [ + "char", + "Guid", + "string" + ], + "ef.key-config": [ + "ValueGeneratedOnAdd", + "ValueGeneratedNever" + ] + } + } + ], + "not-promised": [ + { + "id": "meaningful-char-key-unchecked", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "sentence": "TypeKit stops that invented value, but it doesn't check that you set a meaningful one.", + "axes": { + "shape.wrapped": [ + "char" + ], + "ef.key-config": [ + "default" + ] + } + }, + { + "id": "ulong-key-on-a-decimal-column", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "sentence": "A default key over `ulong` throws at `Add` where the column is a `decimal`", + "axes": { + "shape.wrapped": [ + "ulong" + ], + "provider": [ + "SQL Server", + "PostgreSQL" + ] + } + } + ] + }, + { + "id": "E05-own-compareto-rejected", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "surfaces": [ + "ef" + ], + "axes": { + "ef.op": [ + "lt", + "gt", + "le", + "ge", + "CompareTo", + "OrderBy", + "OrderByDescending", + "ThenBy", + "Order", + "Min", + "Max", + "equals", + "HasQueryFilter", + "named-query-filter", + "value-order" + ], + "ef.vo": [ + "int-struct", + "string-struct", + "decimal-struct", + "DateTime-struct", + "int-class", + "string-class" + ] + }, + "narrow": { + "ef.query": "Your own `CompareTo(TSelf)` can't run in the database.", + "ef.mapping": "Your own `CompareTo(TSelf)` can't run in the database.", + "shape.kind": "Your own `CompareTo(TSelf)` can't run in the database.", + "shape.wrapped": "Your own `CompareTo(TSelf)` can't run in the database.", + "shape.site": "Your own `CompareTo(TSelf)` can't run in the database." + }, + "obligations": [ + { + "id": "comparison", + "sentence": "a comparison (`<`, `>`, `<=`, `>=`, `CompareTo`);", + "axes": { + "ef.op": [ + "lt", + "gt", + "le", + "ge", + "CompareTo" + ] + } + }, + { + "id": "ordering", + "sentence": "an ordering (`OrderBy`, `ThenBy`, their descending forms, `Order`);", + "axes": { + "ef.op": [ + "OrderBy", + "OrderByDescending", + "ThenBy", + "Order" + ] + } + }, + { + "id": "min-max", + "sentence": "`Min` or `Max`.", + "axes": { + "ef.op": [ + "Min", + "Max" + ] + } + }, + { + "id": "equality-allowed", + "sentence": "Equality isn't ordering, and stays allowed.", + "axes": { + "ef.op": [ + "equals" + ] + } + }, + { + "id": "value-order-allowed", + "sentence": "Compare or order by `.Value` where the underlying value's order is what you mean", + "axes": { + "ef.op": [ + "value-order" + ] + } + }, + { + "id": "query-filter", + "sentence": "A query filter (`HasQueryFilter`, named or not) that does the same fails when the model is built, naming the entity type, since it applies to every query of that type.", + "axes": { + "ef.op": [ + "HasQueryFilter", + "named-query-filter" + ] + } + } + ], + "not-promised": [ + { + "id": "collation-orders", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "sentence": "Ordering in SQL follows the database's collation", + "axes": { + "ef.op": [ + "value-order" + ], + "ef.vo": [ + "string-struct", + "string-class" + ] + } + } + ] + }, + { + "id": "E06-own-converter-no-value", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "surfaces": [ + "ef" + ], + "axes": { + "ef.converter": [ + "HasConversion-lambda", + "converter-type", + "ConfigureConventions" + ], + "ef.op": [ + "value-access", + "vo-equality" + ], + "shape.wrapped": [ + "string", + "int", + "Guid", + "decimal" + ] + }, + "narrow": { + "ef.query": "A property your application converts itself", + "ef.mapping": "A property your application converts itself", + "shape.kind": "A property your application converts itself", + "shape.site": "A property your application converts itself" + }, + "obligations": [ + { + "id": "value-not-translated", + "sentence": "So a filter, a sort, a grouping or an aggregate that reads `.Value` there fails with EF's \"could not be translated\", rather than comparing against what the column really holds.", + "axes": { + "ef.op": [ + "value-access" + ] + } + }, + { + "id": "value-object-comparison", + "sentence": "Comparing the value object itself (`o.Code == code`) goes through that converter and still works.", + "axes": { + "ef.op": [ + "vo-equality" + ] + } + } + ] + }, + { + "id": "E07-closed-set-stored-as-name", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#closed-sets", + "surfaces": [ + "ef" + ], + "axes": { + "ef.site": [ + "table", + "primitive-collection", + "json-column" + ], + "ef.event": [ + "round-trip", + "unknown-name-on-read", + "non-member-on-write" + ] + }, + "narrow": { + "ef.query": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself.", + "ef.mapping": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself.", + "shape.kind": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself.", + "shape.wrapped": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself.", + "shape.site": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself." + }, + "obligations": [ + { + "id": "stored-as-name", + "sentence": "A closed set (`IClosedSet<TSelf>`) is stored as a text column of the member's name, and read back as the member itself.", + "axes": { + "ef.event": [ + "round-trip" + ] + } + }, + { + "id": "in-collections-and-json", + "sentence": "That holds in a table, in a primitive collection of names (`text[]` on PostgreSQL, a JSON array elsewhere) and in a JSON column alike, with nothing registered per set.", + "axes": { + "ef.site": [ + "primitive-collection", + "json-column" + ], + "ef.event": [ + "round-trip" + ] + } + }, + { + "id": "unknown-name-throws", + "sentence": "A name the set no longer has throws when it's read, rather than reading as null.", + "axes": { + "ef.event": [ + "unknown-name-on-read" + ] + } + }, + { + "id": "non-member-throws", + "sentence": "An instance that isn't a member throws when it's written.", + "axes": { + "ef.event": [ + "non-member-on-write" + ] + } + } + ] + }, + { + "id": "E08-option-result-not-columns", + "source": "docs/ef-core.md#option-and-result", + "surfaces": [ + "ef" + ], + "axes": { + "ef.type": [ + "Option-int", + "Option-VO", + "Result-int-E", + "Result-VO-E", + "Result-E" + ], + "ef.position": [ + "property", + "key", + "collection", + "owned-property" + ], + "shape.wrapped": [ + "string", + "int", + "Guid" + ] + }, + "narrow": { + "ef.query": "EF can't map an `Option` or a `Result` property, and building the model fails with an error that says so.", + "ef.mapping": "EF can't map an `Option` or a `Result` property, and building the model fails with an error that says so.", + "shape.kind": "EF can't map an `Option` or a `Result` property, and building the model fails with an error that says so.", + "shape.site": "EF can't map an `Option` or a `Result` property, and building the model fails with an error that says so." + }, + "obligations": [ + { + "id": "model-fails", + "sentence": "EF can't map an `Option` or a `Result` property" + }, + { + "id": "error-says-so", + "sentence": "building the model fails with an error that says so" + } + ] + }, + { + "id": "E09-or-none-in-database", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found", + "surfaces": [ + "ef" + ], + "axes": { + "ef.operator": [ + "FirstOrNoneAsync", + "FirstOrNone", + "SingleOrNoneAsync", + "SingleOrNone", + "ElementAtOrNone", + "FirstOrError" + ], + "ef.data": [ + "empty", + "one-match", + "many-match" + ], + "ef.condition": [ + "lambda", + "expression-variable" + ], + "shape.wrapped": [ + "int", + "Guid", + "string" + ] + }, + "narrow": { + "ef.query": "These ask the database for one row and let it run the condition:", + "ef.mapping": "These ask the database for one row and let it run the condition:", + "shape.kind": "These ask the database for one row and let it run the condition:", + "shape.site": "These ask the database for one row and let it run the condition:" + }, + "obligations": [ + { + "id": "first-reads-one-row", + "sentence": "read one row: `WHERE ... LIMIT @p`.", + "axes": { + "ef.operator": [ + "FirstOrNoneAsync", + "FirstOrNone", + "FirstOrError" + ] + } + }, + { + "id": "single-asks-for-two", + "sentence": "ask for two rows, and throw for more than one.", + "axes": { + "ef.operator": [ + "SingleOrNoneAsync", + "SingleOrNone" + ] + } + }, + { + "id": "element-at-skips", + "sentence": "skips to its row: `OFFSET @p`.", + "axes": { + "ef.operator": [ + "ElementAtOrNone" + ] + } + } + ], + "not-promised": [ + { + "id": "func-condition-runs-in-memory", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found", + "sentence": "Then every row is read and the condition runs in memory, without a warning.", + "axes": { + "ef.condition": [ + "func-variable", + "method-group" + ] + } + }, + { + "id": "last-on-a-query-does-not-compile", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found", + "sentence": "`LastOrNone` and `LastOrError` on a query don't compile.", + "axes": { + "ef.operator": [ + "LastOrNone", + "LastOrError" + ] + } + }, + { + "id": "not-precompiled-under-native-aot", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found", + "sentence": "Under Native AOT these throw \"Query wasn't precompiled\"", + "axes": { + "process": [ + "native-aot" + ] + } + } + ] + }, + { + "id": "E10-read-back-skips-validation", + "source": "docs/ef-core.md#reading-back-without-validation", + "surfaces": [ + "ef" + ], + "axes": { + "ef.mapping": [ + "property", + "key", + "nullable", + "owned", + "complex" + ], + "shape.wrapped": [ + "string", + "int", + "Guid", + "decimal", + "DateTime", + "Uri" + ], + "shape.kind": [ + "validated-struct", + "validated-class" + ] + }, + "narrow": { + "ef.query": "EF reads a column back without validation, through the value object's `Materialize` rather than `Create`.", + "shape.site": "EF reads a column back without validation, through the value object's `Materialize` rather than `Create`." + }, + "obligations": [ + { + "id": "reads-without-create", + "sentence": "EF reads a column back without validation, through the value object's `Materialize` rather than `Create`." + }, + { + "id": "revalidate-finds", + "sentence": "`Revalidate()` finds the rows that today's rules reject, as above." + } + ] + }, + { + "id": "E11-usetypekit-order", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#setup", + "surfaces": [ + "ef" + ], + "axes": { + "ef.order": [ + "before-provider", + "after-provider" + ], + "ef.mapping": [ + "property", + "key", + "integer-key", + "nullable", + "owned", + "complex", + "closed-set" + ] + }, + "narrow": { + "ef.query": "it doesn't matter whether you call it before or after `UseNpgsql` or `UseSqlite`", + "shape.kind": "it doesn't matter whether you call it before or after `UseNpgsql` or `UseSqlite`", + "shape.site": "it doesn't matter whether you call it before or after `UseNpgsql` or `UseSqlite`", + "shape.wrapped": "it doesn't matter whether you call it before or after `UseNpgsql` or `UseSqlite`" + }, + "obligations": [ + { + "id": "order-does-not-matter", + "sentence": "it doesn't matter whether you call it before or after `UseNpgsql` or `UseSqlite`" + } + ], + "not-promised": [ + { + "id": "in-memory-provider-maps-nothing", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#setup", + "sentence": "EF's in-memory provider has no relational type mapping, so nothing is mapped there.", + "axes": { + "ef.provider": [ + "InMemory" + ] + } + } + ] + }, + { + "id": "E12-convention-by-vo-type", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#model-wide-conventions-follow-the-propertys-type", + "surfaces": [ + "ef" + ], + "axes": { + "ef.convention": [ + "HaveMaxLength", + "HavePrecision", + "HaveColumnType" + ], + "ef.convention-type": [ + "value-object", + "underlying" + ], + "shape.wrapped": [ + "string", + "decimal", + "Uri", + "int" + ] + }, + "narrow": { + "ef.query": "Configure the value object instead:", + "ef.mapping": "Configure the value object instead:", + "shape.site": "Configure the value object instead:", + "shape.kind": "Configure the value object instead:" + }, + "obligations": [ + { + "id": "value-object-type-reaches", + "sentence": "Configure the value object instead: `configurationBuilder.Properties<Amount>().HavePrecision(18, 2)`.", + "axes": { + "ef.convention-type": [ + "value-object" + ] + } + }, + { + "id": "underlying-type-does-not-reach", + "sentence": "So `configurationBuilder.Properties<decimal>().HavePrecision(18, 2)` reaches `decimal` properties, and not an `Amount` that wraps one.", + "axes": { + "ef.convention-type": [ + "underlying" + ] + } + } + ], + "not-promised": [ + { + "id": "underlying-type-convention", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#model-wide-conventions-follow-the-propertys-type", + "sentence": "EF applies `ConfigureConventions` by CLR type.", + "axes": { + "ef.convention-type": [ + "underlying" + ] + } + } + ] + }, + { + "id": "E13-primitive-collections", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "surfaces": [ + "ef" + ], + "axes": { + "ef.query": [ + "contains", + "any-value-equals" + ], + "shape.wrapped": [ + "string", + "int", + "Guid", + "long", + "decimal" + ] + }, + "narrow": { + "ef.mapping": "db.Orders.Where(o => o.Tags.Contains(tag));", + "shape.site": "db.Orders.Where(o => o.Tags.Contains(tag));", + "shape.kind": "db.Orders.Where(o => o.Tags.Contains(tag));" + }, + "obligations": [ + { + "id": "contains", + "sentence": "db.Orders.Where(o => o.Tags.Contains(tag));", + "axes": { + "ef.query": [ + "contains" + ] + } + }, + { + "id": "element-value", + "sentence": "db.Orders.Where(o => o.Tags.Any(t => t.Value == \"x\"));", + "axes": { + "ef.query": [ + "any-value-equals" + ] + } + } + ], + "not-promised": [ + { + "id": "element-converted-may-cast", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries", + "sentence": "These are converted instead, which works everywhere and may add a cast:", + "axes": { + "ef.query": [ + "any-value-equals" + ] + } + } + ] + }, + { + "id": "E14-inheritance-key-generation", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "surfaces": [ + "ef" + ], + "axes": { + "ef.inheritance": [ + "TPH", + "TPT", + "TPC" + ], + "shape.wrapped": [ + "int", + "long", + "Guid" + ] + }, + "narrow": { + "ef.query": "The root of an inheritance hierarchy follows the same rules under TPH and TPT.", + "ef.mapping": "The root of an inheritance hierarchy follows the same rules under TPH and TPT.", + "shape.site": "The root of an inheritance hierarchy follows the same rules under TPH and TPT.", + "shape.kind": "The root of an inheritance hierarchy follows the same rules under TPH and TPT." + }, + "obligations": [ + { + "id": "tph-tpt-root", + "sentence": "The root of an inheritance hierarchy follows the same rules under TPH and TPT.", + "axes": { + "ef.inheritance": [ + "TPH", + "TPT" + ] + } + }, + { + "id": "tpc-assign-on-sqlite", + "sentence": "SQLite has no such sequence, so there you assign it.", + "axes": { + "ef.inheritance": [ + "TPC" + ] + } + } + ], + "not-promised": [ + { + "id": "guid-key-not-generated", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "sentence": "A key over a `Guid` is yours to assign", + "axes": { + "shape.wrapped": [ + "Guid" + ] + } + } + ] + }, + { + "id": "E15-composite-key", + "source": "docs/ef-core.md#keys", + "surfaces": [ + "ef" + ], + "axes": { + "shape.wrapped": [ + "int", + "Guid", + "string", + "long" + ], + "ef.key-position": [ + "first", + "second" + ] + }, + "narrow": { + "ef.query": "A composite key of value objects is `HasKey(x => new { x.Order, x.Line })`, as for primitives.", + "ef.mapping": "A composite key of value objects is `HasKey(x => new { x.Order, x.Line })`, as for primitives.", + "shape.site": "A composite key of value objects is `HasKey(x => new { x.Order, x.Line })`, as for primitives.", + "shape.kind": "A composite key of value objects is `HasKey(x => new { x.Order, x.Line })`, as for primitives." + }, + "obligations": [ + { + "id": "as-for-primitives", + "sentence": "A composite key of value objects is `HasKey(x => new { x.Order, x.Line })`, as for primitives." + } + ] + }, + { + "id": "E16-own-configuration-wins", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "surfaces": [ + "ef" + ], + "axes": { + "ef.config": [ + "ValueGeneratedNever", + "ValueGeneratedOnAdd", + "HasConversion", + "HasColumnType", + "IsRequired-false" + ], + "shape.wrapped": [ + "int", + "long", + "char", + "Guid", + "string" + ] + }, + "narrow": { + "ef.query": "A configuration you write yourself always wins over these defaults.", + "ef.mapping": "A configuration you write yourself always wins over these defaults.", + "shape.site": "A configuration you write yourself always wins over these defaults.", + "shape.kind": "A configuration you write yourself always wins over these defaults." + }, + "obligations": [ + { + "id": "never", + "sentence": "A configuration you write yourself always wins over these defaults.", + "axes": { + "ef.config": [ + "ValueGeneratedNever" + ] + } + }, + { + "id": "on-add", + "sentence": "A configuration you write yourself always wins over these defaults.", + "axes": { + "ef.config": [ + "ValueGeneratedOnAdd" + ] + } + }, + { + "id": "column-type", + "sentence": "A facet you configure on the property, such as a maximum length or a column type, is kept.", + "axes": { + "ef.config": [ + "HasColumnType" + ] + } + }, + { + "id": "conversion", + "sentence": "A configuration you write yourself always wins over these defaults.", + "axes": { + "ef.config": [ + "HasConversion" + ] + } + }, + { + "id": "optional", + "sentence": "A configuration you write yourself always wins over these defaults.", + "axes": { + "ef.config": [ + "IsRequired-false" + ] + } + } + ], + "not-promised": [ + { + "id": "char-key-not-generated", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "sentence": "A key over `char` is yours to assign.", + "axes": { + "ef.config": [ + "HasColumnType", + "HasConversion" + ], + "shape.wrapped": [ + "char" + ] + } + }, + { + "id": "guid-key-not-generated", + "source": "src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys", + "sentence": "A key over a `Guid` is yours to assign", + "axes": { + "ef.config": [ + "HasColumnType", + "HasConversion" + ], + "shape.wrapped": [ + "Guid" + ] + } + } + ] + }, + { + "id": "L01-json-bytes", + "source": "README.md#what-typekit-guarantees", + "surfaces": [ + "json" + ], + "obligations": [ + { + "id": "the-underlying-types-json", + "sentence": "Its JSON is the underlying type's under your options", + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "byte" + ], + "shape.position": [ + "array-element" + ] + }, + "cite": "not-promised:a-byte-array-is-base64" + } + ] + }, + { + "id": "as-a-dictionary-key-too", + "sentence": "Its JSON is the underlying type's under your options", + "axes": { + "shape.position": [ + "dictionary-key" + ] + }, + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-value-object" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says" + }, + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "shape.position": [ + "dictionary-key" + ], + "json.DictionaryKeyPolicy": [ + "CamelCase", + "SnakeCaseLower", + "KebabCaseUpper", + "own-upper-case" + ] + }, + "cite": "not-promised:a-closed-set-key-is-its-name" + } + ] + }, + { + "id": "read-as-the-underlying-type-reads", + "sentence": "**Swapping a primitive for a value object keeps the JSON, the column and the schema.**", + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "byte" + ], + "shape.position": [ + "array-element" + ] + }, + "cite": "not-promised:a-byte-array-is-base64" + }, + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "shape.position": [ + "dictionary-key" + ], + "json.DictionaryKeyPolicy": [ + "CamelCase", + "SnakeCaseLower", + "KebabCaseUpper", + "own-upper-case" + ] + }, + "cite": "not-promised:a-closed-set-key-is-its-name" + } + ] + } + ], + "not-promised": [ + { + "id": "a-datetime-is-written-as-utc", + "source": "README.md#what-typekit-guarantees", + "sentence": "Its JSON is the underlying type's under your options, with two exceptions: a `DateTime` is written as UTC", + "axes": { + "shape.wrapped": [ + "DateTime" + ] + } + }, + { + "id": "number-handling-on-the-containing-type", + "source": "README.md#what-typekit-guarantees", + "sentence": "and no converter sees number handling set by an attribute on the containing type" + }, + { + "id": "a-struct-refuses-a-json-null", + "source": "README.md#what-typekit-guarantees", + "sentence": "What changes is what the value object rejects, a JSON `null` for a struct value object included.", + "axes": { + "input.json": [ + "null" + ], + "shape.kind": [ + "record-struct", + "validated-struct" + ] + } + }, + { + "id": "a-validated-value-object-refuses-what-create-refuses", + "source": "README.md#what-typekit-guarantees", + "sentence": "What changes is what the value object rejects", + "axes": { + "input.json": [ + "refused-by-create" + ], + "shape.kind": [ + "validated-struct", + "validated-class" + ] + } + }, + { + "id": "an-enum-under-a-source-generated-context", + "source": "README.md#what-typekit-guarantees", + "sentence": "And under a source-generated context, a value object over an enum needs its enum listed", + "axes": { + "json.TypeInfoResolver": [ + "source-generated-context" + ], + "shape.wrapped": [ + "enum", + "flags-enum" + ] + } + }, + { + "id": "preserved-references-over-a-walked-class", + "source": "src/CodoMetis.TypeKit.Generators/README.md#json", + "sentence": "**Preserved references** (`ReferenceHandler.Preserve`) throw a `NotSupportedException` where the serializer could write `$id` or `$ref` inside the underlying value.", + "axes": { + "json.ReferenceHandler": [ + "Preserve" + ], + "shape.wrapped": [ + "own-record-class" + ] + } + }, + { + "id": "a-closed-set-key-is-its-name", + "source": "docs/closed-sets.md#send-it-as-json", + "sentence": "No naming policy renames it, because a renamed name wouldn't read back.", + "axes": { + "shape.kind": [ + "closed-set" + ], + "shape.position": [ + "dictionary-key" + ] + } + }, + { + "id": "a-byte-array-is-base64", + "source": "docs/json.md#the-wrapped-types-json", + "sentence": "An array of value objects over `byte` is a JSON array of numbers.", + "axes": { + "shape.wrapped": [ + "byte" + ], + "shape.position": [ + "array-element" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "L02-no-quote", + "source": "README.md#what-typekit-guarantees", + "surfaces": [ + "json", + "text", + "logging" + ], + "obligations": [ + { + "id": "parsing-never-repeats", + "sentence": "**Rejections never repeat the input.**", + "axes": { + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "own-record-class" + ], + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "text.entry": [ + "TryFrom", + "ToString-format", + "TryFormat-short-span" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#when-text-is-rejected" + } + ] + }, + { + "id": "json-never-repeats", + "sentence": "A JSON or parsing rejection names the value object and the rule, or the underlying type it could not read.", + "inapplicable": [ + { + "axes": { + "json.Converters": [ + "own-for-wrapped", + "own-for-value-object" + ] + }, + "cite": "not-promised:the-applications-own-converter" + } + ] + }, + { + "id": "no-inner-that-quotes", + "sentence": "It carries no inner exception that quotes the text.", + "axes": { + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "own-record-class" + ], + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "text.entry": [ + "TryFrom", + "ToString-format", + "TryFormat-short-span" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#when-text-is-rejected" + } + ] + } + ], + "not-promised": [ + { + "id": "a-rejected-dictionary-key-in-the-path", + "source": "README.md#what-typekit-guarantees", + "sentence": "One exception comes from System.Text.Json: a rejected dictionary key is in the `JsonException`'s `Path`, and so in MVC's 400", + "axes": { + "shape.position": [ + "dictionary-key" + ] + } + }, + { + "id": "the-applications-own-converter", + "source": "README.md#what-typekit-guarantees", + "sentence": "So a secret doesn't end up in a message or a log through this package, as long as your fault type doesn't carry it.", + "axes": { + "json.Converters": [ + "own-for-wrapped", + "own-for-value-object" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + }, + { + "reason": "System.Text.Json writes no dictionary key of a record class but through a converter of the application's (NotSupportedException for the wrapped type alike, L01's rows), where the JSON half is not promised (not-promised the-applications-own-converter), and a value object over a record class has no parsing (Generators README#what-is-generated), so no check of the claim can read the pair.", + "pairs": [ + [ + "shape.position=dictionary-key", + "shape.wrapped=own-record-class" + ] + ] + } + ] + }, + { + "id": "L03-one-rule", + "source": "README.md#what-typekit-guarantees", + "surfaces": [ + "text", + "json" + ], + "narrow": { + "input.json": "The generated `TryFrom`, `FromKnownGood`, JSON converter, parsing and type converter all run it, and each reports the fault you return from `Create`" + }, + "obligations": [ + { + "id": "tryfrom-runs-create", + "sentence": "The generated `TryFrom`, `FromKnownGood`, JSON converter, parsing and type converter all run it", + "axes": { + "text.entry": [ + "TryFrom" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "TryFrom" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + } + ] + }, + { + "id": "parsing-runs-create", + "sentence": "The generated `TryFrom`, `FromKnownGood`, JSON converter, parsing and type converter all run it", + "axes": { + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "own-record-class" + ], + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + } + ] + }, + { + "id": "json-runs-create", + "sentence": "The generated `TryFrom`, `FromKnownGood`, JSON converter, parsing and type converter all run it" + }, + { + "id": "refusals-name-the-fault", + "sentence": "the `JsonException`, the `FormatException` and the `FromKnownGood` exception all name your `EmailFault.NoAt`" + } + ], + "not-promised": [ + { + "id": "stored-json-skips-validation", + "source": "README.md#what-typekit-guarantees", + "sentence": "`StoredJsonConverterFactory` reads JSON the application stored itself.", + "axes": { + "json.Converters": [ + "StoredJsonConverterFactory" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "L04-round-trip", + "source": "src/CodoMetis.TypeKit.Generators/README.md#parsing-and-formatting", + "surfaces": [ + "text", + "json" + ], + "obligations": [ + { + "id": "invariant-whatever-the-culture", + "sentence": "A null `IFormatProvider` means the invariant culture in every generated `Parse`, `TryParse`, `ToString` and `TryFormat`, so the text doesn't depend on the culture the process runs in." + }, + { + "id": "the-type-converter-writes-the-same", + "sentence": "`ToString()` is the underlying type's own default text under the invariant culture, and the type converter and `$\"{x}\"` write the same.", + "axes": { + "text.entry": [ + "ToString-format", + "TryFormat-short-span", + "type-converter" + ] + }, + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "ToString-format", + "TryFormat-short-span" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "own-record-class" + ], + "text.entry": [ + "type-converter" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + } + ] + }, + { + "id": "text-reads-back", + "sentence": "That text reads back as the same value wherever the underlying type's own does", + "inapplicable": [ + { + "axes": { + "shape.kind": [ + "closed-set" + ], + "text.entry": [ + "TryFrom", + "ISpanParsable", + "ToString-format", + "TryFormat-short-span" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#closed-sets" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "own-record-class" + ], + "text.entry": [ + "Parse", + "TryParse", + "IParsable", + "ISpanParsable", + "type-converter", + "ToString-format", + "TryFormat-short-span" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "Uri", + "enum", + "flags-enum", + "NodaTime.Instant" + ], + "text.entry": [ + "ISpanParsable" + ] + }, + "cite": "docs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated" + }, + { + "axes": { + "shape.kind": [ + "record-struct", + "sealed-record-class", + "validated-struct", + "validated-class" + ], + "shape.wrapped": [ + "TimeOnly", + "DateTime", + "DateTimeOffset" + ], + "text.entry": [ + "type-converter" + ] + }, + "cite": "not-promised:dates-and-times-lose-detail-as-text" + } + ] + }, + { + "id": "json-refusal-is-a-json-exception", + "sentence": "Input it can't read throws a `JsonException` naming the value object and the underlying type" + } + ], + "not-promised": [ + { + "id": "dates-and-times-lose-detail-as-text", + "source": "src/CodoMetis.TypeKit.Generators/README.md#parsing-and-formatting", + "sentence": "**`TimeOnly`, `DateTime` and `DateTimeOffset` lose detail as text.**", + "axes": { + "shape.wrapped": [ + "TimeOnly", + "DateTime", + "DateTimeOffset" + ] + } + } + ], + "exclude": [ + { + "reason": "System.Text.Json refuses PreferredObjectCreationHandling.Populate together with a ReferenceHandler on the options object; JsonRow substitutes the default, so the pair never applies.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ] + ] + } + ] + }, + { + "id": "L05-equality", + "source": "src/CodoMetis.TypeKit.Generators/README.md#equality", + "surfaces": [ + "generation" + ], + "narrow": { + "shape.refused-kind": "A declaration that can't be generated fails the build, so no type is left half-generated." + }, + "obligations": [ + { + "id": "equality-is-the-underlying-values", + "sentence": "Equality is the underlying value's, like the ordering, the JSON and the column." + }, + { + "id": "ordering-follows-the-underlying-type", + "sentence": "`CompareTo` and the operators follow the underlying type's ordering, ordinal for a string." + }, + { + "id": "through-every-base", + "sentence": "The value object implements each base's `IEquatable<TBase>` again (`IReimplementedEquatable<TBase>`), so a collection of the base compares it the way the value object does.", + "axes": { + "shape.inheritance": [ + "base-depth-1", + "base-depth-2", + "base-depth-3", + "base-explicit-IEquatable" + ] + } + } + ], + "exclude": [ + { + "reason": "A struct cannot derive from a base type (C# structs inherit only from ValueType), so it has no base record.", + "pairs": [ + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ] + ] + }, + { + "reason": "A closed set wraps nothing (Generators README#closed-sets: a sealed class whose instances are its members), so it has no shape.wrapped.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A value object or closed set nested in a generic type fails the build (CMTK1005, CMTK1018; Generators README#build-errors), so no such type exists to compare.", + "pairs": [ + [ + "shape.containing=class", + "shape.site=in-generic-type" + ], + [ + "shape.containing=generic-type", + "shape.site=in-generic-type" + ], + [ + "shape.containing=record-class", + "shape.site=in-generic-type" + ], + [ + "shape.containing=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.containing=struct", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-1", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-2", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-3", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=none", + "shape.site=in-generic-type" + ], + [ + "shape.kind=closed-set", + "shape.site=in-generic-type" + ], + [ + "shape.kind=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=sealed-record-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-struct", + "shape.site=in-generic-type" + ], + [ + "shape.position=array-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=dictionary-key", + "shape.site=in-generic-type" + ], + [ + "shape.position=dictionary-value", + "shape.site=in-generic-type" + ], + [ + "shape.position=field", + "shape.site=in-generic-type" + ], + [ + "shape.position=immutable-array-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=in-patch", + "shape.site=in-generic-type" + ], + [ + "shape.position=list-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=nullable-property", + "shape.site=in-generic-type" + ], + [ + "shape.position=polymorphic-derived-property", + "shape.site=in-generic-type" + ], + [ + "shape.position=property", + "shape.site=in-generic-type" + ], + [ + "shape.position=read-only-list-element", + "shape.site=in-generic-type" + ], + [ + "shape.position=record-ctor-parameter", + "shape.site=in-generic-type" + ], + [ + "shape.position=required-init", + "shape.site=in-generic-type" + ], + [ + "shape.position=root", + "shape.site=in-generic-type" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTime" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Guid" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeSpan" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Uri" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=bool" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=byte" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=char" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=decimal" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=double" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=flags-enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=int" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=long" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=own-record-class" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=string" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A value at the root is not held by a containing type, so shape.containing has nothing to apply to.", + "pairs": [ + [ + "shape.containing=class", + "shape.position=root" + ], + [ + "shape.containing=generic-type", + "shape.position=root" + ], + [ + "shape.containing=record-class", + "shape.position=root" + ], + [ + "shape.containing=record-struct", + "shape.position=root" + ], + [ + "shape.containing=struct", + "shape.position=root" + ] + ] + } + ] + }, + { + "id": "L06-off-the-wire", + "source": "README.md#what-typekit-guarantees", + "surfaces": [ + "json", + "option-result" + ], + "obligations": [ + { + "id": "never-written", + "sentence": "System.Text.Json throws `NotSupportedException` for either, in both directions, instead of writing `{}` that reads back as `None`." + }, + { + "id": "never-read", + "sentence": "System.Text.Json throws `NotSupportedException` for either, in both directions, instead of writing `{}` that reads back as `None`." + } + ], + "not-promised": [ + { + "id": "a-source-generated-context-refuses-a-nullable", + "source": "src/CodoMetis.TypeKit/README.md#not-wire-types", + "sentence": "**A source-generated context can't describe an `Option<T>?` or `Result<...>?` property.**", + "axes": { + "json.TypeInfoResolver": [ + "source-generated-context" + ], + "shape.position": [ + "nullable-property" + ] + } + }, + { + "id": "a-missing-property-reaches-no-converter", + "source": "src/CodoMetis.TypeKit/README.md#not-wire-types", + "sentence": "**A missing property reaches no converter.**" + } + ], + "exclude": [ + { + "reason": "A closed set wraps nothing (Generators README#closed-sets: a sealed class whose instances are its members), so it has no shape.wrapped.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A value at the root is not held by a containing type, so shape.containing has nothing to apply to.", + "pairs": [ + [ + "shape.containing=class", + "shape.position=root" + ], + [ + "shape.containing=generic-type", + "shape.position=root" + ], + [ + "shape.containing=record-class", + "shape.position=root" + ], + [ + "shape.containing=record-struct", + "shape.position=root" + ], + [ + "shape.containing=struct", + "shape.position=root" + ] + ] + }, + { + "reason": "Result<TError> has no Select or SelectMany, so no query expression produces one, and FirstOrNone returns an Option<T>, never a Result<TError> (src/CodoMetis.TypeKit/ResultExtensions.cs and OptionExtensions.cs).", + "pairs": [ + [ + "or.operation=FirstOrNone", + "or.shape=Result-E" + ], + [ + "or.operation=query-syntax", + "or.shape=Result-E" + ] + ] + }, + { + "reason": "System.Text.Json serializes a field only under IncludeFields (or [JsonInclude]), so without it the member is in no document and no converter runs in either direction, for the plain type alike.", + "pairs": [ + [ + "json.IncludeFields=default", + "shape.position=field" + ] + ] + }, + { + "reason": "System.Text.Json refuses JsonObjectCreationHandling.Populate together with a reference handler (InvalidOperationException when the options are used; harness/JsonRow.cs runs such a row without Populate), so no row can set both.", + "pairs": [ + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=IgnoreCycles" + ], + [ + "json.PreferredObjectCreationHandling=Populate", + "json.ReferenceHandler=Preserve" + ] + ] + } + ] + }, + { + "id": "C22-generator-refusals", + "source": "src/CodoMetis.TypeKit.Generators/README.md#build-errors", + "surfaces": [ + "generation", + "analyzers" + ], + "axes": { + "build-host": [ + "sdk-build" + ] + }, + "narrow": { + "shape.position": "A declaration that can't be generated fails the build, so no type is left half-generated.", + "shape.containing": "A declaration that can't be generated fails the build, so no type is left half-generated." + }, + "obligations": [ + { + "id": "no-type-half-generated", + "sentence": "A declaration that can't be generated fails the build, so no type is left half-generated." + }, + { + "id": "every-error-links-the-table", + "sentence": "Every error ends with a link to this table." + }, + { + "id": "unsealed-record-class", + "sentence": "A record class not declared `sealed`.", + "axes": { + "shape.refused-kind": [ + "unsealed-record-class (CMTK1006)" + ] + } + }, + { + "id": "over-value-object", + "sentence": "or a value object (itself included). Wrap what the other value object wraps instead.", + "axes": { + "shape.refused-kind": [ + "over-value-object (CMTK1005)" + ] + } + }, + { + "id": "generic-wrapped", + "sentence": "A generic underlying type, a tuple included, fails the build (CMTK1005).", + "axes": { + "shape.refused-kind": [ + "generic-wrapped (CMTK1005)" + ] + } + }, + { + "id": "abstract", + "sentence": "An abstract type with a value-object marker. It would get no member, and a value held as it, of a type derived from it, would have no `Value` and be written as `{}`.", + "axes": { + "shape.refused-kind": [ + "abstract (CMTK1005)" + ] + } + }, + { + "id": "over-object", + "sentence": "or wrapping `object`, `System.Enum`, an array, a pointer, a nullable type, a generic type (a tuple included) or a value object (itself included).", + "axes": { + "shape.refused-kind": [ + "over-object (CMTK1005)" + ] + } + }, + { + "id": "over-system-enum", + "sentence": "or wrapping `object`, `System.Enum`, an array, a pointer, a nullable type, a generic type (a tuple included) or a value object (itself included).", + "axes": { + "shape.refused-kind": [ + "over-System.Enum (CMTK1005)" + ] + } + }, + { + "id": "named-as-generated-member", + "sentence": "named `Value` or as another member the generators add (`From`, `TryFrom`, `FromKnownGood`, `Revalidate`, `Parse`, `TryParse`, `TryFormat`, `MinValue`, `MaxValue`, `CompareTo`)", + "axes": { + "shape.refused-kind": [ + "named-as-generated-member (CMTK1005)" + ] + } + }, + { + "id": "closed-set-named-as-generated-member", + "sentence": "a set itself named as one of them (a member can't have the name of its type)", + "axes": { + "shape.refused-kind": [ + "closed-set-named-as-generated-member (CMTK1021)" + ] + } + }, + { + "id": "equality-operator", + "sentence": "So a hand-written `Equals(TSelf)`, `GetHashCode()`, `IEquatable<TSelf>.Equals`, or `operator ==` or `!=` between two of the type, fails the build (CMTK1011).", + "axes": { + "shape.refused-kind": [ + "equality-operator (CMTK1011)" + ] + } + } + ], + "exclude": [ + { + "reason": "A declaration nested in a generic type is refused for that alone (Generators README, Build errors: CMTK1005, \"Generic or nested in a generic type\"; the axes file's refused list), so it cannot show which refusal of its own the generators report.", + "pairs": [ + [ + "build-host=sdk-build", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-1", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-2", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-depth-3", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=explicit-interface-member", + "shape.site=in-generic-type" + ], + [ + "shape.inheritance=none", + "shape.site=in-generic-type" + ], + [ + "shape.kind=closed-set", + "shape.site=in-generic-type" + ], + [ + "shape.kind=record-struct", + "shape.site=in-generic-type" + ], + [ + "shape.kind=sealed-record-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-class", + "shape.site=in-generic-type" + ], + [ + "shape.kind=validated-struct", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=abstract (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=equality-operator (CMTK1011)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=generic-wrapped (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=named-as-generated-member (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=over-value-object (CMTK1005)", + "shape.site=in-generic-type" + ], + [ + "shape.refused-kind=unsealed-record-class (CMTK1006)", + "shape.site=in-generic-type" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTime" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Guid" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeOnly" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=TimeSpan" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=Uri" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=bool" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=byte" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=char" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=decimal" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=double" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=flags-enum" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=int" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=long" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=own-record-class" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=string" + ], + [ + "shape.site=in-generic-type", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "C# seals every struct and refuses an abstract one, so a record struct can be neither an unsealed nor an abstract record class; a closed set is never a record (CMTK1020), and an unsealed or abstract one is CMTK1014, a refusal of its own.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=record-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=abstract (CMTK1005)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=unsealed-record-class (CMTK1006)" + ] + ] + }, + { + "reason": "A closed set wraps no type and has no equality of the underlying value to write by hand (Generators README, Closed sets), so it cannot be declared over a type or with a value object's equality; its own name clash is closed-set-named-as-generated-member (CMTK1021).", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.refused-kind=equality-operator (CMTK1011)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=generic-wrapped (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=named-as-generated-member (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-System.Enum (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-object (CMTK1005)" + ], + [ + "shape.kind=closed-set", + "shape.refused-kind=over-value-object (CMTK1005)" + ] + ] + }, + { + "reason": "CMTK1021 is a closed set's refusal; a value object named as a generated member is named-as-generated-member (CMTK1005).", + "pairs": [ + [ + "shape.kind=record-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=sealed-record-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-class", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.kind=validated-struct", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ] + ] + }, + { + "reason": "The refused kind fixes what is wrapped: object, System.Enum; and a closed set wraps no type (Generators README, Closed sets), so shape.wrapped has nothing to apply to.", + "pairs": [ + [ + "shape.kind=closed-set", + "shape.wrapped=DateOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTime" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Guid" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeOnly" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=TimeSpan" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=Uri" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=bool" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=byte" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=char" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=decimal" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=double" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=flags-enum" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=int" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=long" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=own-record-class" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=string" + ], + [ + "shape.kind=closed-set", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-System.Enum (CMTK1005)", + "shape.wrapped=ulong" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTime" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=DateTimeOffset" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Guid" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=NodaTime.Instant" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeOnly" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=TimeSpan" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=Uri" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=bool" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=byte" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=char" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=decimal" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=double" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=flags-enum" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=int" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=long" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=own-record-class" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=string" + ], + [ + "shape.refused-kind=over-object (CMTK1005)", + "shape.wrapped=ulong" + ] + ] + }, + { + "reason": "A record struct has no base type, and a closed set is a sealed class that is never a record (CMTK1020), so neither derives from the base records of shape.inheritance; an explicit member of another interface they implement themselves.", + "pairs": [ + [ + "shape.inheritance=base-depth-1", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-1", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-2", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-depth-3", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=closed-set" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=record-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.kind=validated-struct" + ], + [ + "shape.inheritance=base-explicit-IEquatable", + "shape.refused-kind=closed-set-named-as-generated-member (CMTK1021)" + ] + ] + } + ] } - }, - { - "id": "L01-json-bytes", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "json" - ] - }, - { - "id": "L02-no-quote", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "json", - "text", - "logging" - ] - }, - { - "id": "L03-one-rule", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "text", - "json" - ] - }, - { - "id": "L04-round-trip", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "text", - "json" - ] - }, - { - "id": "L05-equality", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "generation" - ] - }, - { - "id": "L06-off-the-wire", - "source": "README.md#what-typekit-guarantees", - "surfaces": [ - "json", - "option-result" - ] - } -] \ No newline at end of file +] diff --git a/tools/probe/corpus/harness/BaseProbe.cs b/tools/probe/corpus/harness/BaseProbe.cs index 82d70f4c..13b5e370 100644 --- a/tools/probe/corpus/harness/BaseProbe.cs +++ b/tools/probe/corpus/harness/BaseProbe.cs @@ -1,9 +1,11 @@ -// Calibration of the shared HTTP host (not a claim): runs the baseline over the rows of claim C07 to find harness mistakes before the claims use it. +// Calibration of the shared HTTP host (not a claim): runs the baseline's wire half over its own rows of claim C07 to find harness mistakes +// before the claims use it. The schema half is claim C21's (C21OpenapiSchemaParityProbe). Each comparison is a check of its own (HttpBaseline.Run), +// of the implicit obligation claim: C07 promises nothing of the wire, and its seed names no obligation. using Probes.Shapes; namespace Probes; public sealed class BaseProbe { - public static TheoryData<string, int> Data() => Rows.Data("C07-fault-responses-blanket"); + public static TheoryData<string, int> Data() => Rows.Data("BaseProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run("BASE", idx, async r => await HttpBaseline.Run(r, Registry.Of(r)), claim); + public Task Row(string probe, int idx) => Rec.Run("BASE", idx, async r => await HttpBaseline.Run(r, Registry.Of(r), schema: false), probe); } diff --git a/tools/probe/corpus/harness/BuildConsumer.cs b/tools/probe/corpus/harness/BuildConsumer.cs new file mode 100644 index 00000000..15dddbc5 --- /dev/null +++ b/tools/probe/corpus/harness/BuildConsumer.cs @@ -0,0 +1,125 @@ +// A throwaway consumer of the generators, built once per probe run with every row's declarations in a file of its own, for what +// only a build can show: the generators' diagnostics (CMTK1xxx). It builds as test/CodoMetis.TypeKit.Generators.Tests' BuildOutcomeTests +// does: under artifacts/, against the projects this run already built (--no-dependencies), outside the repository's build settings. +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text.RegularExpressions; +namespace Probes.Build; + +public sealed record BuildDiagnostic(string File, string Severity, string Id, string Message); + +public sealed record ConsumerOutcome(int ExitCode, string Output, IReadOnlyList<BuildDiagnostic> Diagnostics, IReadOnlyDictionary<string, HashSet<string>> Methods) +{ + /// <summary>The diagnostics reported in one of the consumer's files.</summary> + public IEnumerable<BuildDiagnostic> In(string file) => Diagnostics.Where(d => Path.GetFileName(d.File) == file); +} + +public static partial class Consumer +{ + static readonly Dictionary<string, Lazy<Task<ConsumerOutcome>>> Builds = new(); + + /// <summary>Builds the consumer of <paramref name="files"/> once per <paramref name="key"/> in this process.</summary> + public static Task<ConsumerOutcome> Build(string key, Func<IReadOnlyDictionary<string, string>> files) + { + lock (Builds) + { + if (!Builds.TryGetValue(key, out var build)) Builds[key] = build = new(() => Run(files())); + return build.Value; + } + } + + public static string RepositoryRoot() + { + for (var d = new DirectoryInfo(AppContext.BaseDirectory); d is not null; d = d.Parent) + if (File.Exists(Path.Combine(d.FullName, "CodoMetis.TypeKit.slnx"))) return d.FullName; + throw new InvalidOperationException($"no CodoMetis.TypeKit.slnx above {AppContext.BaseDirectory}"); + } + + static string Configuration => typeof(Consumer).Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration + ?? throw new InvalidOperationException("the probe assembly carries no AssemblyConfigurationAttribute"); + + static async Task<ConsumerOutcome> Run(IReadOnlyDictionary<string, string> files) + { + var root = RepositoryRoot(); + var dir = Path.Combine(root, "artifacts", $"probe-consumer-{Guid.NewGuid():N}"); + Directory.CreateDirectory(dir); + try + { + var src = Path.Combine(root, "src"); + await File.WriteAllTextAsync(Path.Combine(dir, "Consumer.csproj"), $""" + <Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>disable</ImplicitUsings> + <AssemblyName>ProbeConsumer</AssemblyName> + </PropertyGroup> + <ItemGroup> + <ProjectReference Include="{src}/CodoMetis.TypeKit.Generators/CodoMetis.TypeKit.Generators.csproj" /> + <Reference Include="NodaTime"><HintPath>{typeof(NodaTime.Instant).Assembly.Location}</HintPath></Reference> + </ItemGroup> + </Project> + """); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Build.props"), "<Project />"); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Build.targets"), "<Project />"); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Packages.props"), + "<Project><PropertyGroup><ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally></PropertyGroup></Project>"); + foreach (var (name, text) in files) await File.WriteAllTextAsync(Path.Combine(dir, name), text); + + var dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") is { Length: > 0 } host ? host : "dotnet"; + var info = new ProcessStartInfo(dotnet, ["build", dir, "-c", Configuration, "--no-dependencies", "-p:RestoreRecursive=false", "-nodeReuse:false", "-clp:NoSummary", "-v:q", "-nologo"]) + { + RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = dir, + }; + // The run's code coverage instruments this process through its environment; the build is not the probe's code. + foreach (var k in info.Environment.Keys.ToList()) + if (k.StartsWith("CORECLR_", StringComparison.OrdinalIgnoreCase) || k.StartsWith("COR_", StringComparison.OrdinalIgnoreCase) + || k.Contains("CODECOVERAGE", StringComparison.OrdinalIgnoreCase) || k.StartsWith("MicrosoftInstrumentationEngine", StringComparison.OrdinalIgnoreCase) + || k.Equals("DOTNET_STARTUP_HOOKS", StringComparison.OrdinalIgnoreCase)) + info.Environment.Remove(k); + info.Environment["DOTNET_CLI_UI_LANGUAGE"] = "en"; + using var process = Process.Start(info)!; + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + using (var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(10))) await process.WaitForExitAsync(timeout.Token); + var output = await stdout + await stderr; + var diagnostics = Line().Matches(output) + .Select(m => new BuildDiagnostic(m.Groups["file"].Value.Trim(), m.Groups["sev"].Value, m.Groups["id"].Value, m.Groups["msg"].Value.Trim())) + .Distinct().ToList(); + var dll = Path.Combine(dir, "bin", Configuration, "net10.0", "ProbeConsumer.dll"); + return new ConsumerOutcome(process.ExitCode, output, diagnostics, File.Exists(dll) ? Methods(dll) : new Dictionary<string, HashSet<string>>()); + } + finally + { + try { Directory.Delete(dir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + } + + /// <summary>The methods each type of the built assembly declares, by the type's metadata name (nesting with '+'), read without loading it.</summary> + static Dictionary<string, HashSet<string>> Methods(string dll) + { + using var stream = File.OpenRead(dll); + using var pe = new PEReader(stream); + var md = pe.GetMetadataReader(); + string Name(TypeDefinition t) + { + var name = md.GetString(t.Name); + var outer = t.GetDeclaringType(); + if (!outer.IsNil) return Name(md.GetTypeDefinition(outer)) + "+" + name; + var ns = md.GetString(t.Namespace); + return ns.Length == 0 ? name : ns + "." + name; + } + var result = new Dictionary<string, HashSet<string>>(); + foreach (var h in md.TypeDefinitions) + { + var t = md.GetTypeDefinition(h); + result[Name(t)] = [.. t.GetMethods().Select(m => md.GetString(md.GetMethodDefinition(m).Name))]; + } + return result; + } + + [GeneratedRegex(@"^(?<file>[^\r\n(]+)\((?<line>\d+),(?<col>\d+)\): (?<sev>warning|error) (?<id>[A-Z]+\d+): (?<msg>.*?)(?: \[[^\]\r\n]*\])?\r?$", RegexOptions.Multiline)] + private static partial Regex Line(); +} diff --git a/tools/probe/corpus/harness/BuildDecls.cs b/tools/probe/corpus/harness/BuildDecls.cs new file mode 100644 index 00000000..7efdb659 --- /dev/null +++ b/tools/probe/corpus/harness/BuildDecls.cs @@ -0,0 +1,91 @@ +// The hand half of C09Shapes.g.cs and C11Shapes.g.cs (generated from the rows, one value object per row): the tables' rows, and the +// wrapped types of C11's parse shapes. +namespace Probes.Build; + +/// <summary>One C09 row as declared: the effective settings, the value object, its bases from the direct one up, and its factory.</summary> +public sealed record C09Case(int Index, string Key, string Kind, string Site, string Inheritance, Type Vo, Type[] Bases, Func<object, object> Make, (string Axis, string Value)[] Substituted); + +/// <summary> +/// One C11 row as declared: the effective settings, the value object and the type it wraps, and for a parse shape its control: a value +/// object of the same kind, site and inheritance over the nearest type that does parse. +/// </summary> +public sealed record C11Case(int Index, string Key, string Kind, string Site, string Inheritance, Type Vo, Type Wrapped, Type? Control, Type? ControlWrapped, (string Axis, string Value)[] Substituted); + +public static class Decls +{ + /// <summary> + /// The row's settings as the generator keyed them; a table generated from other rows is a harness error, not a pass. Read from + /// the cached copy of the row, so the run's own row marks no axis read. + /// </summary> + public static string Key(string probe, int index) => + string.Join(";", Probes.Shapes.Rows.Get(probe, index).All.OrderBy(kv => kv.Key, StringComparer.Ordinal).Select(kv => $"{kv.Key}={kv.Value}")); +} + +// C11's parse shapes, each the whole wrapped type (CHANGELOG 2.1.0, Fixed): "A wrapped type's `Parse` whose result can't be assigned +// to it, or that takes its text by `ref`, or an abstract class's string constructor". + +/// <summary>implicit-result: a static Parse whose result, an int, cannot be assigned to the type.</summary> +public sealed class C11ParsesToInt +{ + public static int Parse(string text) => text.Length; + public override string ToString() => "parses-to-int"; +} + +/// <summary>ref-text: a static Parse that takes its text by ref.</summary> +public sealed class C11ParsesByRef +{ + public static C11ParsesByRef Parse(ref string text) { text = ""; return new(); } + public override string ToString() => "parses-by-ref"; +} + +/// <summary>ref-text: a string constructor that takes its text by ref.</summary> +public sealed class C11ConstructedByRef +{ + public C11ConstructedByRef(ref string text) => text = ""; + private C11ConstructedByRef() { } + public static C11ConstructedByRef Sample() => new(); + public override string ToString() => "constructed-by-ref"; +} + +/// <summary>abstract-string-ctor: an abstract class with a public string constructor, which only a derived class can call.</summary> +public abstract class C11AbstractText +{ + public C11AbstractText(string text) => Text = text; + public string Text { get; } + public override string ToString() => Text; +} + +public sealed class C11ConcreteText(string text) : C11AbstractText(text); + +// The controls, each the nearest type that can be parsed (Generators README, "What you can wrap": "Parsing where it can be parsed"): +// a Parse whose result converts to the type by a user-defined implicit operator, so it can be assigned; a Parse taking its text by in, +// which a call passes as it is; a sealed class's public string constructor. + +/// <summary>Control of implicit-result: Parse gives a type that converts to this one implicitly.</summary> +public sealed class C11ParsedShape +{ + public string Text { get; init; } = ""; + public static C11ParsedCircle Parse(string text) => new() { Text = text }; + public override string ToString() => Text; +} + +public sealed class C11ParsedCircle +{ + public string Text { get; init; } = ""; + public static implicit operator C11ParsedShape(C11ParsedCircle circle) => new() { Text = circle.Text }; +} + +/// <summary>Control of ref-text: Parse takes its text by in.</summary> +public sealed class C11ParsesFromIn +{ + public string Text { get; private init; } = ""; + public static C11ParsesFromIn Parse(in string text) => new() { Text = text }; + public override string ToString() => Text; +} + +/// <summary>Control of abstract-string-ctor: a sealed class with a public string constructor.</summary> +public sealed class C11ConstructedConcrete(string text) +{ + public string Text { get; } = text; + public override string ToString() => Text; +} diff --git a/tools/probe/corpus/harness/BuildRefusals.cs b/tools/probe/corpus/harness/BuildRefusals.cs new file mode 100644 index 00000000..be3d79da --- /dev/null +++ b/tools/probe/corpus/harness/BuildRefusals.cs @@ -0,0 +1,280 @@ +// The declarations the generators refuse (shape.refused-kind), written as C# per row, and the consumer build that shows what the +// generators did with them (C22). The build is BuildConsumer.cs's (a throwaway consumer under artifacts/, against the projects this run +// already built, outside the repository's build settings) with Metalama's transformed files kept, since a refused declaration fails the +// build and leaves no assembly to read: the transformed source is where a member the generators added to a refused type would show. +using System.Diagnostics; +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace Probes.Build; + +/// <summary>A consumer build: its exit code, its output, its diagnostics, and Metalama's transformed source of each file.</summary> +public sealed record RefusalOutcome(int ExitCode, string Output, IReadOnlyList<BuildDiagnostic> Diagnostics, IReadOnlyDictionary<string, string> Transformed) +{ + /// <summary>The diagnostics reported in one of the consumer's files.</summary> + public IEnumerable<BuildDiagnostic> In(string file) => Diagnostics.Where(d => Path.GetFileName(d.File) == file); +} + +/// <summary> +/// One refusal of the Generators README's "Build errors" table, as a row declares it: the id the table gives it, the obligation of C22 +/// that states it, the consumer it is built in, and the compiler errors that may stand beside it in its own file. +/// </summary> +/// <remarks> +/// A compiler error in a consumer hid every LAMA0611 of the other files (e4b751c), so the declarations whose refusal comes with a +/// compiler error of its own are built apart: a refused closed set leaves its interface's members unimplemented (CS0535, as +/// BuildOutcomeTests allows it), and a hand-written equality operator of a record is also C#'s CS0111 (the README says so). +/// </remarks> +public sealed record Refusal(string Kind, string Id, string Obligation, string Group, IReadOnlyList<string> Companions); + +/// <summary>One row's declarations: the refused type, as the diagnostics name it, and a control the generators must generate.</summary> +public sealed record RefusedDeclaration(int Index, Refusal Refusal, string Name, string Display, string Control, string File, string Source); + +public static partial class Refusals +{ + /// <summary>The refusals C22 declares, by their shape.refused-kind value; any other value is the harness's mistake, never a pass.</summary> + public static readonly IReadOnlyDictionary<string, Refusal> Table = new Refusal[] + { + new("unsealed-record-class (CMTK1006)", "CMTK1006", "unsealed-record-class", "value-objects", []), + new("over-value-object (CMTK1005)", "CMTK1005", "over-value-object", "value-objects", []), + new("generic-wrapped (CMTK1005)", "CMTK1005", "generic-wrapped", "value-objects", []), + // The label keeps the id it had when C09's and C11's rows were generated; an abstract value object is CMTK1022 since 1ef30be. + new("abstract (CMTK1005)", "CMTK1022", "abstract", "value-objects", []), + new("over-object (CMTK1005)", "CMTK1005", "over-object", "value-objects", []), + new("over-System.Enum (CMTK1005)", "CMTK1005", "over-system-enum", "value-objects", []), + new("named-as-generated-member (CMTK1005)", "CMTK1005", "named-as-generated-member", "value-objects", []), + new("closed-set-named-as-generated-member (CMTK1021)", "CMTK1021", "closed-set-named-as-generated-member", "closed-sets", ["CS0535"]), + new("equality-operator (CMTK1011)", "CMTK1011", "equality-operator", "operators", ["CS0111"]), + }.ToDictionary(r => r.Kind); + + /// <summary> + /// The value-object names CMTK1005 refuses (the README's list), and the closed-set names CMTK1021 refuses; a row takes one by its + /// index. C17's and C19's probes name their named-as-generated-member declaration from the same list. + /// </summary> + public static readonly string[] ValueObjectNames = ["Value", "From", "TryFrom", "FromKnownGood", "Revalidate", "Parse", "TryParse", "TryFormat", "MinValue", "MaxValue", "CompareTo"]; + static readonly string[] ClosedSetNames = ["Name", "QualifiedName", "All", "Match", "Parse", "TryParse"]; + + /// <summary>The link every generator error ends with (Generators README, Build errors: "Every error ends with a link to this table").</summary> + public const string TableLink = "https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#build-errors"; + + static readonly string[] Structs = ["record-struct", "validated-struct"]; + + /// <summary> + /// The row's declarations. Reads shape.refused-kind, shape.kind, shape.site, shape.inheritance and build-host, and shape.wrapped where + /// the refused kind wraps it. A refused kind the table does not hold, a combination the claim excludes, and a refusal the README's + /// Build errors table no longer lists are harness errors: the row would show nothing about a refusal the README states. + /// </summary> + public static RefusedDeclaration Declare(Probes.Shapes.Row r) + { + if (r["build-host"] != "sdk-build") throw new InvalidOperationException("build-host " + r["build-host"] + " has no build here"); + var kindValue = r["shape.refused-kind"]; + if (!Table.TryGetValue(kindValue, out var refusal)) throw new InvalidOperationException($"shape.refused-kind '{kindValue}' is no refusal this harness declares"); + if (!ReadmeLists(refusal.Id)) throw new InvalidOperationException($"the Generators README's Build errors table has no row for {refusal.Id}"); + var kind = r["shape.kind"]; var site = r["shape.site"]; var inheritance = r["shape.inheritance"]; + var n = r.Index; var id = n.ToString(); + var closedSet = kind == "closed-set"; + if (closedSet != (refusal.Group == "closed-sets")) throw new InvalidOperationException($"{kindValue} cannot be declared as a {kind} (excluded in claims.json)"); + if (Structs.Contains(kind) && refusal.Obligation is "unsealed-record-class" or "abstract") throw new InvalidOperationException($"a {kind} cannot be {refusal.Obligation} (excluded in claims.json)"); + if ((Structs.Contains(kind) || closedSet) && inheritance is not ("none" or "explicit-interface-member")) throw new InvalidOperationException($"a {kind} has no base record for {inheritance} (excluded in claims.json)"); + if (site == "in-generic-type") throw new InvalidOperationException("in-generic-type is a refusal of its own (excluded in claims.json)"); + + var name = refusal.Obligation switch + { + "named-as-generated-member" => ValueObjectNames[n % ValueObjectNames.Length], + "closed-set-named-as-generated-member" => ClosedSetNames[n % ClosedSetNames.Length], + _ => $"V{id}", + }; + var control = closedSet ? $"S{id}" : $"K{id}"; + var body = new System.Text.StringBuilder(); + body.AppendLine($"public enum VF{id} {{ No }}"); + var explicitMember = inheritance == "explicit-interface-member"; + if (explicitMember) body.AppendLine($"public interface IMark{id} {{ int Mark(); }}"); + + // The bases, from the topmost down: plain abstract records, the direct one with an explicit IEquatable of its own or an explicit + // member of another interface where shape.inheritance says so. A struct or a closed set implements that interface itself. + var depth = inheritance switch { "base-depth-2" => 2, "base-depth-3" => 3, "none" => 0, _ => 1 }; + if (closedSet || Structs.Contains(kind)) depth = 0; + for (var d = depth; d >= 1; d--) + { + var b = $"B{id}_{d}"; + var parents = new List<string>(); + if (d < depth) parents.Add($"B{id}_{d + 1}"); + var members = ""; + if (d == 1 && inheritance == "base-explicit-IEquatable") { parents.Add($"IEquatable<{b}>"); members = $"bool IEquatable<{b}>.Equals({b}? other) => ReferenceEquals(this, other);"; } + if (d == 1 && explicitMember) { parents.Add($"IMark{id}"); members = $"int IMark{id}.Mark() => 0;"; } + body.AppendLine($"public abstract record {b}{(parents.Count > 0 ? " : " + string.Join(", ", parents) : "")} {{ {members} }}"); + } + var baseList = depth > 0 ? $"B{id}_1, " : ""; + var ownMark = explicitMember && depth == 0; + var markList = ownMark ? $", IMark{id}" : ""; + var markMember = ownMark ? $" int IMark{id}.Mark() => 0;" : ""; + + if (closedSet) + { + body.AppendLine($"public sealed partial class {name} : IClosedSet<{name}>{markList} {{ public static readonly {name} A{id} = new();{markMember} }}"); + body.AppendLine($"public sealed partial class {control} : IClosedSet<{control}>{markList} {{ public static readonly {control} A{id} = new();{markMember} }}"); + } + else + { + var w = refusal.Obligation is "over-object" or "over-system-enum" ? null : Src.Wrapped(r["shape.wrapped"], id); + var wrapped = refusal.Obligation switch + { + "over-value-object" => $"I{id}", + "generic-wrapped" => $"List<{w}>", + "over-object" => "object", + "over-system-enum" => "System.Enum", + _ => w!, + }; + if (refusal.Obligation == "over-value-object") body.AppendLine($"public readonly partial record struct I{id} : IValue<{w}>;"); + var modifier = refusal.Obligation switch { "unsealed-record-class" => "", "abstract" => "abstract ", _ => "sealed " }; + var operators = refusal.Obligation == "equality-operator"; + body.AppendLine(ValueObject(kind, name, modifier, baseList, wrapped, id, markList, markMember, operators)); + body.AppendLine(ValueObject(kind, control, "sealed ", baseList, w ?? "int", id, markList, markMember, operators: false)); + } + + var source = Src.Usings + $"namespace C22R{id};\n" + Src.WrappedDeclarations(id) + Src.AtSite(site, id, body.ToString()) + "\n"; + return new(n, refusal, name, Nesting(site, id) + name, control, $"R{id}.cs", source); + } + + static string ValueObject(string kind, string name, string modifier, string baseList, string wrapped, string id, string markList, string markMember, bool operators) + { + var validated = kind.StartsWith("validated", StringComparison.Ordinal); + var marker = validated ? $"IValidatedValue<{name}, {wrapped}, VF{id}>" : $"IValue<{wrapped}>"; + var head = Structs.Contains(kind) + ? $"public readonly partial record struct {name} : {marker}{markList}" + : $"public {modifier}partial record {name} : {baseList}{marker}{markList}"; + var members = new List<string>(); + if (validated) members.Add($"public static Result<{name}, VF{id}> Create({wrapped} value) => Result.Error(VF{id}.No);"); + if (operators) + { + var t = Structs.Contains(kind) ? name : name + "?"; + members.Add($"public static bool operator ==({t} left, {t} right) => true;"); + members.Add($"public static bool operator !=({t} left, {t} right) => false;"); + } + if (markMember.Length > 0) members.Add(markMember.Trim()); + return members.Count == 0 ? head + ";" : $"{head}\n{{\n {string.Join("\n ", members)}\n}}"; + } + + /// <summary>shape.site's nesting as a diagnostic names a type: its containing types, dotted.</summary> + static string Nesting(string site, string id) => site switch + { + "top-level" => "", "nested-in-class" or "nested-in-struct" => $"O{id}.", "nested-two-levels" => $"O{id}.P{id}.", + _ => throw new ArgumentException("shape.site " + site), + }; + + static readonly Lazy<string> Readme = new(() => File.ReadAllText(Path.Combine(Consumer.RepositoryRoot(), "src", "CodoMetis.TypeKit.Generators", "README.md"))); + + /// <summary>Whether the README's Build errors table has a row for <paramref name="id"/>.</summary> + public static bool ReadmeLists(string id) => + Readme.Value.IndexOf("\n## Build errors", StringComparison.Ordinal) is var at and >= 0 + && Readme.Value.IndexOf($"\n| {id} |", at, StringComparison.Ordinal) >= 0; + + /// <summary>Whether the README still has the heading the table link points at.</summary> + public static bool ReadmeHasBuildErrorsHeading() => Readme.Value.Split('\n').Any(l => l.TrimEnd('\r') == "## Build errors"); + + /// <summary> + /// How the generators left a type in Metalama's transformed source: <c>null</c> where its declaration is as it was written (no base + /// type and no member added, and no <c>{Name}Extensions</c> companion), else what they added. Throws where the type is not there. + /// </summary> + public static string? Added(RefusalOutcome build, RefusedDeclaration d, string name) + { + if (!build.Transformed.TryGetValue(d.File, out var transformed)) throw new InvalidOperationException($"Metalama wrote no transformed {d.File}"); + var written = One(CSharpSyntaxTree.ParseText(d.Source).GetRoot(), name, "the row's source"); + var woven = One(CSharpSyntaxTree.ParseText(transformed).GetRoot(), name, "the transformed " + d.File); + var added = new List<string>(); + var bw = written.BaseList?.Types.Count ?? 0; var bt = woven.BaseList?.Types.Count ?? 0; + if (bt != bw) added.Add($"{bt - bw} base type(s)"); + if (woven.Members.Count != written.Members.Count) added.Add($"{woven.Members.Count - written.Members.Count} member(s)"); + var companion = name + "Extensions"; + foreach (var (file, text) in build.Transformed) + if (text.Contains($"namespace C22R{d.Index}", StringComparison.Ordinal) && text.Contains(companion, StringComparison.Ordinal) + && CSharpSyntaxTree.ParseText(text).GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Any(c => c.Identifier.Text == companion)) + added.Add($"the companion {companion} ({file})"); + return added.Count == 0 ? null : string.Join(", ", added); + } + + static TypeDeclarationSyntax One(Microsoft.CodeAnalysis.SyntaxNode root, string name, string where) + { + var found = root.DescendantNodes().OfType<TypeDeclarationSyntax>().Where(t => t.Identifier.Text == name).ToList(); + return found.Count == 1 ? found[0] : throw new InvalidOperationException($"{found.Count} declarations of {name} in {where}"); + } + + static readonly Dictionary<string, Lazy<Task<RefusalOutcome>>> Builds = new(); + + /// <summary>Builds the consumer of <paramref name="files"/> once per <paramref name="key"/> in this process.</summary> + public static Task<RefusalOutcome> Build(string key, Func<IReadOnlyDictionary<string, string>> files) + { + lock (Builds) + { + if (!Builds.TryGetValue(key, out var build)) Builds[key] = build = new(() => Run(files())); + return build.Value; + } + } + + static string Configuration => typeof(Refusals).Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration + ?? throw new InvalidOperationException("the probe assembly carries no AssemblyConfigurationAttribute"); + + static async Task<RefusalOutcome> Run(IReadOnlyDictionary<string, string> files) + { + var root = Consumer.RepositoryRoot(); + var dir = Path.Combine(root, "artifacts", $"probe-refusals-{Guid.NewGuid():N}"); + Directory.CreateDirectory(dir); + try + { + var src = Path.Combine(root, "src"); + await File.WriteAllTextAsync(Path.Combine(dir, "Consumer.csproj"), $""" + <Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>disable</ImplicitUsings> + <AssemblyName>ProbeRefusals</AssemblyName> + <MetalamaEmitCompilerTransformedFiles>true</MetalamaEmitCompilerTransformedFiles> + </PropertyGroup> + <ItemGroup> + <ProjectReference Include="{src}/CodoMetis.TypeKit.Generators/CodoMetis.TypeKit.Generators.csproj" /> + <Reference Include="NodaTime"><HintPath>{typeof(NodaTime.Instant).Assembly.Location}</HintPath></Reference> + </ItemGroup> + </Project> + """); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Build.props"), "<Project />"); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Build.targets"), "<Project />"); + await File.WriteAllTextAsync(Path.Combine(dir, "Directory.Packages.props"), + "<Project><PropertyGroup><ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally></PropertyGroup></Project>"); + foreach (var (name, text) in files) await File.WriteAllTextAsync(Path.Combine(dir, name), text); + + var dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") is { Length: > 0 } host ? host : "dotnet"; + var info = new ProcessStartInfo(dotnet, ["build", dir, "-c", Configuration, "--no-dependencies", "-p:RestoreRecursive=false", "-nodeReuse:false", "-clp:NoSummary", "-v:q", "-nologo"]) + { + RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = dir, + }; + // The run's code coverage instruments this process through its environment; the build is not the probe's code. + foreach (var k in info.Environment.Keys.ToList()) + if (k.StartsWith("CORECLR_", StringComparison.OrdinalIgnoreCase) || k.StartsWith("COR_", StringComparison.OrdinalIgnoreCase) + || k.Contains("CODECOVERAGE", StringComparison.OrdinalIgnoreCase) || k.StartsWith("MicrosoftInstrumentationEngine", StringComparison.OrdinalIgnoreCase) + || k.Equals("DOTNET_STARTUP_HOOKS", StringComparison.OrdinalIgnoreCase)) + info.Environment.Remove(k); + info.Environment["DOTNET_CLI_UI_LANGUAGE"] = "en"; + using var process = Process.Start(info)!; + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + using (var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(10))) await process.WaitForExitAsync(timeout.Token); + var output = await stdout + await stderr; + var diagnostics = Line().Matches(output) + .Select(m => new BuildDiagnostic(m.Groups["file"].Value.Trim(), m.Groups["sev"].Value, m.Groups["id"].Value, m.Groups["msg"].Value.Trim())) + .Distinct().ToList(); + var woven = Path.Combine(dir, "obj", Configuration, "net10.0", "metalama"); + var transformed = Directory.Exists(woven) + ? Directory.GetFiles(woven, "*.cs").ToDictionary(f => Path.GetFileName(f), f => File.ReadAllText(f)) + : new Dictionary<string, string>(); + return new RefusalOutcome(process.ExitCode, output, diagnostics, transformed); + } + finally + { + try { Directory.Delete(dir, recursive: true); } catch (IOException) { } catch (UnauthorizedAccessException) { } + } + } + + [GeneratedRegex(@"^(?<file>[^\r\n(]+)\((?<line>\d+),(?<col>\d+)\): (?<sev>warning|error) (?<id>[A-Z]+\d+): (?<msg>.*?)(?: \[[^\]\r\n]*\])?\r?$", RegexOptions.Multiline)] + private static partial Regex Line(); +} diff --git a/tools/probe/corpus/harness/BuildRoslyn.cs b/tools/probe/corpus/harness/BuildRoslyn.cs new file mode 100644 index 00000000..2f3584d1 --- /dev/null +++ b/tools/probe/corpus/harness/BuildRoslyn.cs @@ -0,0 +1,167 @@ +// The analyzer probes' compiler (C17 CMTK0014, C19 CMTK0010): a CSharpCompilation over the real CodoMetis.TypeKit and the +// running framework, with the CMTK analyzer that reports the claim's id attached, as test/CodoMetis.TypeKit.Analyzers.Tests +// runs it. Sources carry markup {|#label:...|}; a marked span is where the probe expects (or forbids) a report. +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +namespace Probes.Build; + +/// <summary>One marked span of a source file, by its label.</summary> +public sealed record Mark(string Label, string File, TextSpan Span, string Text); + +/// <summary>A source file whose markup was taken out, with the spans it marked.</summary> +public sealed record Marked(string File, string Text, IReadOnlyList<Mark> Marks) +{ + /// <summary>Strips <c>{|#label:…|}</c> (not nested) and records each span in the stripped text.</summary> + public static Marked Parse(string file, string source) + { + var sb = new System.Text.StringBuilder(); var marks = new List<Mark>(); var i = 0; + while (i < source.Length) + { + var open = source.IndexOf("{|#", i, StringComparison.Ordinal); + if (open < 0) { sb.Append(source, i, source.Length - i); break; } + sb.Append(source, i, open - i); + var colon = source.IndexOf(':', open); + var label = source[(open + 3)..colon]; + var close = source.IndexOf("|}", colon, StringComparison.Ordinal); + if (close < 0) throw new InvalidOperationException($"unclosed markup {label} in {file}"); + var inner = source[(colon + 1)..close]; + if (inner.Contains("{|", StringComparison.Ordinal)) throw new InvalidOperationException($"nested markup in {label} of {file}"); + marks.Add(new Mark(label, file, new TextSpan(sb.Length, inner.Length), inner)); + sb.Append(inner); + i = close + 2; + } + return new Marked(file, sb.ToString(), marks); + } +} + +/// <summary>What one compilation reported: compiler errors, the analyzer's diagnostics, analyzer failures.</summary> +public sealed record Analysis(IReadOnlyList<Diagnostic> Errors, IReadOnlyList<Diagnostic> Reported, IReadOnlyList<Diagnostic> AnalyzerFailures, IReadOnlyList<Marked> Files) +{ + public IEnumerable<Mark> Marks => Files.SelectMany(f => f.Marks); + public static string Where(Diagnostic d) => $"{d.Location.SourceTree?.FilePath}:{d.Location.SourceSpan} '{d.Location.SourceTree?.GetText().ToString(d.Location.SourceSpan)}'"; + public string ErrorText() => string.Join("; ", Errors.Take(5).Select(e => $"{e.Id} {Where(e)}: {e.GetMessage()}")); +} + +public static class Rz +{ + static readonly Lazy<ImmutableArray<MetadataReference>> Refs = new(() => + { + // The running shared framework (implementation assemblies, as the probe's own process binds), plus the real base package and NodaTime. + var framework = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + var tpa = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator) + .Where(p => Path.GetDirectoryName(p) == framework && Path.GetFileName(p) is var n && (n.StartsWith("System.") || n.StartsWith("Microsoft.") || n is "mscorlib.dll" or "netstandard.dll")); + return [.. tpa.Concat([typeof(CodoMetis.TypeKit.Option<>).Assembly.Location, typeof(NodaTime.Instant).Assembly.Location]) + .Distinct().Select(p => (MetadataReference)MetadataReference.CreateFromFile(p))]; + }); + + /// <summary>The CMTK analyzers that report <paramref name="id"/>, found by the id they declare, never by type name.</summary> + public static ImmutableArray<DiagnosticAnalyzer> AnalyzersFor(string id) => + [ + .. typeof(CodoMetis.TypeKit.Analyzers.TypeArgumentConstraintAnalyzer).Assembly.GetTypes() + .Where(t => !t.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(t) && t.GetCustomAttribute<DiagnosticAnalyzerAttribute>() is not null) + .Select(t => (DiagnosticAnalyzer)Activator.CreateInstance(t)!) + .Where(a => a.SupportedDiagnostics.Any(d => d.Id == id)), + ]; + + public static async Task<Analysis> Analyze(string id, IEnumerable<(string File, string Source)> sources) + { + var files = sources.Select(s => Marked.Parse(s.File, s.Source)).ToList(); + var parse = new CSharpParseOptions(LanguageVersion.Latest); + var trees = files.Select(f => CSharpSyntaxTree.ParseText(f.Text, parse, path: f.File)).ToList(); + var compilation = CSharpCompilation.Create("ProbeBuild" + Guid.NewGuid().ToString("N"), trees, Refs.Value, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable)); + var analyzers = AnalyzersFor(id); + if (analyzers.Length == 0) throw new InvalidOperationException($"no CMTK analyzer declares {id}"); + var failures = new List<Diagnostic>(); + var options = new CompilationWithAnalyzersOptions(new AnalyzerOptions([]), (ex, analyzer, diagnostic) => { lock (failures) failures.Add(diagnostic); }, + concurrentAnalysis: false, logAnalyzerExecutionTime: false); + var with = compilation.WithAnalyzers(analyzers, options); + var analyzerDiagnostics = await with.GetAnalyzerDiagnosticsAsync(CancellationToken.None); + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + failures.AddRange(analyzerDiagnostics.Where(d => d.Id == "AD0001")); + return new Analysis(errors, [.. analyzerDiagnostics.Where(d => d.Id == id)], failures, files); + } + + /// <summary> + /// Compares the reports with the marks: every mark in <paramref name="expected"/> reported exactly once at its span, nothing + /// reported anywhere else. Null when they agree, else what differs. + /// </summary> + public static string? Agree(Analysis a, IReadOnlySet<string> expected, Func<Diagnostic, Mark, string?>? message = null) => + Agree(a, expected, _ => true, outside: true, message); + + /// <summary> + /// The same over the marks <paramref name="judged"/> selects, one check's share of a compilation: each of them in + /// <paramref name="expected"/> reported exactly once at its span, the others silent, and with <paramref name="outside"/>, nothing + /// reported outside every mark. A report at a mark the check does not judge is another check's. + /// </summary> + public static string? Agree(Analysis a, IReadOnlySet<string> expected, Func<Mark, bool> judged, bool outside, Func<Diagnostic, Mark, string?>? message = null) + { + var marks = a.Marks.ToList(); + foreach (var label in expected) if (!marks.Any(m => m.Label == label)) throw new InvalidOperationException($"no mark {label}"); + var problems = new List<string>(); + foreach (var m in marks.Where(judged)) + { + var at = At(a, m); + if (expected.Contains(m.Label)) + { + if (at.Count != 1) problems.Add($"{m.Label} '{m.Text}': reported {at.Count} times, expected once"); + else if (message?.Invoke(at[0], m) is { } bad) problems.Add($"{m.Label} '{m.Text}': {bad}"); + } + else if (at.Count != 0) problems.Add($"{m.Label} '{m.Text}': reported, expected silent ({at[0].GetMessage()})"); + } + if (outside) problems.AddRange(Outside(a).Select(d => $"reported outside every mark at {Analysis.Where(d)}: {d.GetMessage()}")); + return problems.Count == 0 ? null : string.Join("; ", problems); + } + + /// <summary>The reports exactly at a mark's span.</summary> + public static List<Diagnostic> At(Analysis a, Mark m) => + [.. a.Reported.Where(d => d.Location.SourceTree?.FilePath == m.File && d.Location.SourceSpan == m.Span)]; + + /// <summary>The reports whose span holds a mark's: at it, or at the syntax around it.</summary> + public static List<Diagnostic> Around(Analysis a, Mark m) => + [.. a.Reported.Where(d => d.Location.SourceTree?.FilePath == m.File && d.Location.SourceSpan.Contains(m.Span))]; + + /// <summary>The reports whose span holds no mark.</summary> + public static List<Diagnostic> Outside(Analysis a) => + [.. a.Reported.Where(d => !a.Marks.Any(m => d.Location.SourceTree?.FilePath == m.File && d.Location.SourceSpan.Contains(m.Span)))]; + + /// <summary> + /// Whether a value is reported, wherever around it: each mark <paramref name="judged"/> names that is in <paramref name="expected"/> + /// has exactly one report at or around it, the others none. Where the report sits is <see cref="WhereWritten"/>'s question, so a + /// report moved to the syntax around its value still counts here. Null when they agree, else what differs. + /// </summary> + public static string? Reported(Analysis a, IEnumerable<string> judged, IReadOnlySet<string> expected, Func<Diagnostic, Mark, string?>? message = null) + { + var problems = new List<string>(); + foreach (var label in judged) + { + var m = a.Marks.SingleOrDefault(x => x.Label == label) ?? throw new InvalidOperationException($"no mark {label}"); + var around = Around(a, m); + if (expected.Contains(label)) + { + if (around.Count != 1) problems.Add($"{m.Label} '{m.Text}': reported {around.Count} times, expected once"); + else if (message?.Invoke(around[0], m) is { } bad) problems.Add($"{m.Label} '{m.Text}': {bad}"); + } + else if (around.Count != 0) problems.Add($"{m.Label} '{m.Text}': reported, expected silent ({around[0].GetMessage()})"); + } + return problems.Count == 0 ? null : string.Join("; ", problems); + } + + /// <summary> + /// Where the reports sit: every report at or around a mark in <paramref name="expected"/> is at the mark's own span, the value as + /// written, and none is outside every mark. Null when so, else what differs. + /// </summary> + public static string? WhereWritten(Analysis a, IReadOnlySet<string> expected) + { + var problems = new List<string>(); + foreach (var m in a.Marks.Where(m => expected.Contains(m.Label))) + foreach (var d in Around(a, m).Where(d => d.Location.SourceSpan != m.Span)) + problems.Add($"{m.Label} '{m.Text}': reported at {Analysis.Where(d)}, not at the value"); + problems.AddRange(Outside(a).Select(d => $"reported outside every mark at {Analysis.Where(d)}: {d.GetMessage()}")); + return problems.Count == 0 ? null : string.Join("; ", problems); + } +} diff --git a/tools/probe/corpus/harness/BuildSource.cs b/tools/probe/corpus/harness/BuildSource.cs new file mode 100644 index 00000000..bc37edfc --- /dev/null +++ b/tools/probe/corpus/harness/BuildSource.cs @@ -0,0 +1,58 @@ +// Source text for the build family's probes (C17, C18, C19): a row's shape.* axes written as C#, for a compilation the probe +// makes itself (an analyzer run, or the consumer build of C18). Each piece names its types after a suffix, so the pieces of +// several rows can sit in one compilation. +namespace Probes.Build; + +public static class Src +{ + public const string Usings = + """ + #nullable enable + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Threading.Tasks; + using CodoMetis.TypeKit; + using CodoMetis.TypeKit.ClosedSets; + using CodoMetis.TypeKit.ValueObjects; + + """; + + /// <summary>shape.site: <paramref name="inner"/> declared at the top level, nested in a class or a struct, two levels down, or in a generic type.</summary> + public static string AtSite(string site, string id, string inner) => site switch + { + "top-level" => inner, + "nested-in-class" => $"public static partial class O{id}\n{{\n{inner}\n}}", + "nested-in-struct" => $"public partial struct O{id}\n{{\n{inner}\n}}", + "nested-two-levels" => $"public static partial class O{id}\n{{\npublic sealed partial class P{id}\n{{\n{inner}\n}}\n}}", + "in-generic-type" => $"public static partial class O{id}<TO>\n{{\n{inner}\n}}", + _ => throw new ArgumentException("shape.site " + site), + }; + + /// <summary>shape.wrapped as a type name; enum, flags-enum and own-record-class are declared by <see cref="WrappedDeclarations"/>.</summary> + public static string Wrapped(string wrapped, string id) => wrapped switch + { + "string" or "int" or "long" or "byte" or "ulong" or "decimal" or "double" or "bool" or "char" => wrapped, + "Guid" or "DateTime" or "DateTimeOffset" or "DateOnly" or "TimeOnly" or "TimeSpan" or "Uri" => "System." + wrapped, + "enum" => $"WEnum{id}", "flags-enum" => $"WFlags{id}", "NodaTime.Instant" => "global::NodaTime.Instant", "own-record-class" => $"WRec{id}", + _ => throw new ArgumentException("shape.wrapped " + wrapped), + }; + + public static string WrappedDeclarations(string id) => + $"public enum WEnum{id} {{ A, B }}\n[Flags] public enum WFlags{id} {{ None = 0, P = 1, Q = 2 }}\npublic sealed record WRec{id} {{ public int N {{ get; init; }} }}\n"; + + public static bool IsStructKind(string kind) => kind is "record-struct" or "validated-struct"; + + /// <summary>The type head of shape.containing, and the type as written from inside it (generic-type is closed over int outside).</summary> + public static string ContainerHead(string containing, string name, string primary = "") => containing switch + { + "class" => $"public sealed partial class {name}{primary}", + "record-class" => $"public sealed partial record {name}{primary}", + "struct" => $"public partial struct {name}{primary}", + "record-struct" => $"public partial record struct {name}{primary}", + "generic-type" => $"public sealed partial class {name}<TC>{primary}", + _ => throw new ArgumentException("shape.containing " + containing), + }; + + public static string ContainerSelf(string containing, string name) => containing == "generic-type" ? $"{name}<TC>" : name; +} diff --git a/tools/probe/corpus/harness/C09Shapes.g.cs b/tools/probe/corpus/harness/C09Shapes.g.cs new file mode 100644 index 00000000..2a365946 --- /dev/null +++ b/tools/probe/corpus/harness/C09Shapes.g.cs @@ -0,0 +1,1031 @@ +// <auto-generated/> by tools/probe/generators/decls.cs from rows/C09BaseRecordEqualityProbe.json.gz: one value object per row. Do not edit; regenerate with the rows. +#nullable enable +// CMTK1023: the bases' own equality is the point (C09) or beside it (C11); CS8851: a base's Equals without GetHashCode, on purpose. +#pragma warning disable CMTK1023, CS8851, CS1591 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +namespace Probes.Build.C09; + +// row 0 +public static partial class O0 +{ +public sealed partial class P0 +{ +public abstract record B0_1 : System.IEquatable<B0_1> { bool System.IEquatable<B0_1>.Equals(B0_1? other) => ReferenceEquals(this, other); } +public sealed partial record V0 : B0_1, IValidatedValue<V0, global::Probes.Shapes.PRec, global::Probes.Shapes.ShapeFault> +{ + public static Result<V0, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PRec value) => new V0(value); +} +} +} + +// row 1 +public static partial class O1 +{ +public sealed partial class P1 +{ +public abstract record B1_3 { public virtual bool Equals(B1_3? other) => ReferenceEquals(this, other); } +public abstract record B1_2 : B1_3 { } +public abstract record B1_1 : B1_2 { } +public sealed partial record V1 : B1_1, IValue<global::System.DateTimeOffset>; +} +} + +// row 2 +public partial struct O2 +{ +public abstract record B2_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V2 : B2_1, IValidatedValue<V2, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V2, global::Probes.Shapes.ShapeFault> Create(long value) => new V2(value); +} +} + +// row 3 +public abstract record B3_1 { } +public sealed partial record V3 : B3_1, IValue<global::System.DateOnly>; + +// row 4 +public interface IMark4 { int Mark(); } +public abstract record B4_1 : IMark4 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); int IMark4.Mark() => 1; } +public sealed partial record V4 : B4_1, IValidatedValue<V4, string, global::Probes.Shapes.ShapeFault> +{ + public static Result<V4, global::Probes.Shapes.ShapeFault> Create(string value) => new V4(value); +} + +// row 5 +public static partial class O5 +{ +public interface IMark5 { int Mark(); } +public abstract record B5_1 : System.IEquatable<B5_1>, IMark5 { bool System.IEquatable<B5_1>.Equals(B5_1? other) => ReferenceEquals(this, other); int IMark5.Mark() => 1; } +public sealed partial record V5 : B5_1, IValue<global::System.Uri>; +} + +// row 6 +public static partial class O6 +{ +public abstract record B6_2 { } +public abstract record B6_1 : B6_2 { } +public sealed partial record V6 : B6_1, IValidatedValue<V6, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V6, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V6(value); +} +} + +// row 7 +public partial struct O7 +{ +public abstract record B7_2 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B7_1 : B7_2 { } +public sealed partial record V7 : B7_1, IValue<global::System.TimeSpan>; +} + +// row 8 +public partial struct O8 +{ +public abstract record B8_1 : System.IEquatable<B8_1> { bool System.IEquatable<B8_1>.Equals(B8_1? other) => ReferenceEquals(this, other); } +public sealed partial record V8 : B8_1, IValue<global::System.TimeOnly>; +} + +// row 9 +public static partial class O9 +{ +public sealed partial class P9 +{ +public interface IMark9 { int Mark(); } +public abstract record B9_1 : IMark9 { int IMark9.Mark() => 1; } +public sealed partial record V9 : B9_1, IValue<byte>; +} +} + +// row 10 +public abstract record B10_2 { public virtual bool Equals(B10_2? other) => ReferenceEquals(this, other); } +public abstract record B10_1 : B10_2 { } +public sealed partial record V10 : B10_1, IValidatedValue<V10, global::System.DateOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V10, global::Probes.Shapes.ShapeFault> Create(global::System.DateOnly value) => new V10(value); +} + +// row 11 +public static partial class O11 +{ +public abstract record B11_1 { public virtual bool Equals(B11_1? other) => ReferenceEquals(this, other); } +public sealed partial record V11 : B11_1, IValidatedValue<V11, global::System.Guid, global::Probes.Shapes.ShapeFault> +{ + public static Result<V11, global::Probes.Shapes.ShapeFault> Create(global::System.Guid value) => new V11(value); +} +} + +// row 12 +public partial struct O12 +{ +public abstract record B12_2 : System.IEquatable<B12_2> { bool System.IEquatable<B12_2>.Equals(B12_2? other) => ReferenceEquals(this, other); } +public abstract record B12_1 : B12_2 { } +public sealed partial record V12 : B12_1, IValidatedValue<V12, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V12, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V12(value); +} +} + +// row 13 +public partial struct O13 +{ +public abstract record B13_3 { public virtual bool Equals(B13_3? other) => ReferenceEquals(this, other); } +public abstract record B13_2 : B13_3 { } +public abstract record B13_1 : B13_2 { } +public sealed partial record V13 : B13_1, IValidatedValue<V13, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V13, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V13(value); +} +} + +// row 14 +public static partial class O14 +{ +public abstract record B14_3 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B14_2 : B14_3 { } +public abstract record B14_1 : B14_2 { } +public sealed partial record V14 : B14_1, IValue<int>; +} + +// row 15 +public partial struct O15 +{ +public interface IMark15 { int Mark(); } +public abstract record B15_1 : IMark15 { public virtual bool Equals(B15_1? other) => ReferenceEquals(this, other); int IMark15.Mark() => 1; } +public sealed partial record V15 : B15_1, IValidatedValue<V15, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V15, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V15(value); +} +} + +// row 16 +public abstract record B16_3 : System.IEquatable<B16_3> { bool System.IEquatable<B16_3>.Equals(B16_3? other) => ReferenceEquals(this, other); } +public abstract record B16_2 : B16_3 { } +public abstract record B16_1 : B16_2 { } +public sealed partial record V16 : B16_1, IValue<double>; + +// row 17 +public static partial class O17 +{ +public abstract record B17_1 : System.IEquatable<B17_1> { public virtual bool Equals(B17_1? other) => ReferenceEquals(this, other); bool System.IEquatable<B17_1>.Equals(B17_1? other) => ReferenceEquals(this, other); } +public sealed partial record V17 : B17_1, IValue<global::System.TimeSpan>; +} + +// row 18 +public static partial class O18 +{ +public sealed partial class P18 +{ +public abstract record B18_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V18 : B18_1, IValue<global::Probes.Shapes.PFlags>; +} +} + +// row 19 +public abstract record B19_1 : System.IEquatable<B19_1> { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); bool System.IEquatable<B19_1>.Equals(B19_1? other) => ReferenceEquals(this, other); } +public sealed partial record V19 : B19_1, IValidatedValue<V19, char, global::Probes.Shapes.ShapeFault> +{ + public static Result<V19, global::Probes.Shapes.ShapeFault> Create(char value) => new V19(value); +} + +// row 20 +public abstract record B20_3 { } +public abstract record B20_2 : B20_3 { } +public abstract record B20_1 : B20_2 { } +public sealed partial record V20 : B20_1, IValidatedValue<V20, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V20, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V20(value); +} + +// row 21 +public static partial class O21 +{ +public abstract record B21_2 : System.IEquatable<B21_2> { bool System.IEquatable<B21_2>.Equals(B21_2? other) => ReferenceEquals(this, other); } +public abstract record B21_1 : B21_2 { } +public sealed partial record V21 : B21_1, IValue<global::System.DateTime>; +} + +// row 22 +public static partial class O22 +{ +public sealed partial class P22 +{ +public abstract record B22_2 { } +public abstract record B22_1 : B22_2 { } +public sealed partial record V22 : B22_1, IValue<global::NodaTime.Instant>; +} +} + +// row 23 +public abstract record B23_1 : System.IEquatable<B23_1> { bool System.IEquatable<B23_1>.Equals(B23_1? other) => ReferenceEquals(this, other); } +public sealed partial record V23 : B23_1, IValidatedValue<V23, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V23, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V23(value); +} + +// row 24 +public static partial class O24 +{ +public sealed partial class P24 +{ +public abstract record B24_1 { } +public sealed partial record V24 : B24_1, IValidatedValue<V24, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V24, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V24(value); +} +} +} + +// row 25 +public static partial class O25 +{ +public abstract record B25_1 { } +public sealed partial record V25 : B25_1, IValidatedValue<V25, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V25, global::Probes.Shapes.ShapeFault> Create(bool value) => new V25(value); +} +} + +// row 26 +public static partial class O26 +{ +public sealed partial class P26 +{ +public abstract record B26_3 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B26_2 : B26_3 { } +public abstract record B26_1 : B26_2 { } +public sealed partial record V26 : B26_1, IValidatedValue<V26, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V26, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V26(value); +} +} +} + +// row 27 +public abstract record B27_2 { } +public abstract record B27_1 : B27_2 { } +public sealed partial record V27 : B27_1, IValue<long>; + +// row 28 +public partial struct O28 +{ +public abstract record B28_1 { } +public sealed partial record V28 : B28_1, IValue<global::Probes.Shapes.PRec>; +} + +// row 29 +public partial struct O29 +{ +public interface IMark29 { int Mark(); } +public abstract record B29_1 : System.IEquatable<B29_1>, IMark29 { bool System.IEquatable<B29_1>.Equals(B29_1? other) => ReferenceEquals(this, other); int IMark29.Mark() => 1; } +public sealed partial record V29 : B29_1, IValue<bool>; +} + +// row 30 +public abstract record B30_2 : System.IEquatable<B30_2> { bool System.IEquatable<B30_2>.Equals(B30_2? other) => ReferenceEquals(this, other); } +public abstract record B30_1 : B30_2 { } +public sealed partial record V30 : B30_1, IValue<int>; + +// row 31 +public static partial class O31 +{ +public sealed partial class P31 +{ +public abstract record B31_3 { } +public abstract record B31_2 : B31_3 { } +public abstract record B31_1 : B31_2 { } +public sealed partial record V31 : B31_1, IValue<ulong>; +} +} + +// row 32 +public partial struct O32 +{ +public abstract record B32_1 : System.IEquatable<B32_1> { bool System.IEquatable<B32_1>.Equals(B32_1? other) => ReferenceEquals(this, other); } +public sealed partial record V32 : B32_1, IValidatedValue<V32, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V32, global::Probes.Shapes.ShapeFault> Create(double value) => new V32(value); +} +} + +// row 33 +public static partial class O33 +{ +public sealed partial class P33 +{ +public abstract record B33_1 : System.IEquatable<B33_1> { public virtual bool Equals(B33_1? other) => ReferenceEquals(this, other); bool System.IEquatable<B33_1>.Equals(B33_1? other) => ReferenceEquals(this, other); } +public sealed partial record V33 : B33_1, IValue<string>; +} +} + +// row 34 +public partial struct O34 +{ +public abstract record B34_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V34 : B34_1, IValidatedValue<V34, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V34, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V34(value); +} +} + +// row 35 +public abstract record B35_3 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B35_2 : B35_3 { } +public abstract record B35_1 : B35_2 { } +public sealed partial record V35 : B35_1, IValue<global::System.Guid>; + +// row 36 +public static partial class O36 +{ +public abstract record B36_3 { } +public abstract record B36_2 : B36_3 { } +public abstract record B36_1 : B36_2 { } +public sealed partial record V36 : B36_1, IValue<char>; +} + +// row 37 +public static partial class O37 +{ +public abstract record B37_1 : System.IEquatable<B37_1> { bool System.IEquatable<B37_1>.Equals(B37_1? other) => ReferenceEquals(this, other); } +public sealed partial record V37 : B37_1, IValue<decimal>; +} + +// row 38 +public partial struct O38 +{ +public abstract record B38_2 : System.IEquatable<B38_2> { bool System.IEquatable<B38_2>.Equals(B38_2? other) => ReferenceEquals(this, other); } +public abstract record B38_1 : B38_2 { } +public sealed partial record V38 : B38_1, IValue<global::Probes.Shapes.PEnum>; +} + +// row 39 +public abstract record B39_2 { public virtual bool Equals(B39_2? other) => ReferenceEquals(this, other); } +public abstract record B39_1 : B39_2 { } +public sealed partial record V39 : B39_1, IValidatedValue<V39, byte, global::Probes.Shapes.ShapeFault> +{ + public static Result<V39, global::Probes.Shapes.ShapeFault> Create(byte value) => new V39(value); +} + +// row 40 +public partial struct O40 +{ +public abstract record B40_3 { public virtual bool Equals(B40_3? other) => ReferenceEquals(this, other); } +public abstract record B40_2 : B40_3 { } +public abstract record B40_1 : B40_2 { } +public sealed partial record V40 : B40_1, IValidatedValue<V40, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V40, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V40(value); +} +} + +// row 41 +public abstract record B41_3 { } +public abstract record B41_2 : B41_3 { } +public abstract record B41_1 : B41_2 { } +public sealed partial record V41 : B41_1, IValidatedValue<V41, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V41, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V41(value); +} + +// row 42 +public partial struct O42 +{ +public interface IMark42 { int Mark(); } +public abstract record B42_1 : IMark42 { public virtual bool Equals(B42_1? other) => ReferenceEquals(this, other); int IMark42.Mark() => 1; } +public sealed partial record V42 : B42_1, IValue<char>; +} + +// row 43 +public partial struct O43 +{ +public abstract record B43_1 { public virtual bool Equals(B43_1? other) => ReferenceEquals(this, other); } +public sealed partial record V43 : B43_1, IValue<int>; +} + +// row 44 +public partial struct O44 +{ +public abstract record B44_1 : System.IEquatable<B44_1> { public virtual bool Equals(B44_1? other) => ReferenceEquals(this, other); bool System.IEquatable<B44_1>.Equals(B44_1? other) => ReferenceEquals(this, other); } +public sealed partial record V44 : B44_1, IValue<global::System.Uri>; +} + +// row 45 +public static partial class O45 +{ +public abstract record B45_1 { } +public sealed partial record V45 : B45_1, IValidatedValue<V45, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V45, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V45(value); +} +} + +// row 46 +public static partial class O46 +{ +public interface IMark46 { int Mark(); } +public abstract record B46_1 : System.IEquatable<B46_1>, IMark46 { bool System.IEquatable<B46_1>.Equals(B46_1? other) => ReferenceEquals(this, other); int IMark46.Mark() => 1; } +public sealed partial record V46 : B46_1, IValidatedValue<V46, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V46, global::Probes.Shapes.ShapeFault> Create(long value) => new V46(value); +} +} + +// row 47 +public abstract record B47_3 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B47_2 : B47_3 { } +public abstract record B47_1 : B47_2 { } +public sealed partial record V47 : B47_1, IValue<global::NodaTime.Instant>; + +// row 48 +public partial struct O48 +{ +public abstract record B48_3 { } +public abstract record B48_2 : B48_3 { } +public abstract record B48_1 : B48_2 { } +public sealed partial record V48 : B48_1, IValue<string>; +} + +// row 49 +public interface IMark49 { int Mark(); } +public abstract record B49_1 : IMark49 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); int IMark49.Mark() => 1; } +public sealed partial record V49 : B49_1, IValue<global::Probes.Shapes.PRec>; + +// row 50 +public static partial class O50 +{ +public sealed partial class P50 +{ +public abstract record B50_3 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B50_2 : B50_3 { } +public abstract record B50_1 : B50_2 { } +public sealed partial record V50 : B50_1, IValue<global::System.DateOnly>; +} +} + +// row 51 +public abstract record B51_1 : System.IEquatable<B51_1> { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); bool System.IEquatable<B51_1>.Equals(B51_1? other) => ReferenceEquals(this, other); } +public sealed partial record V51 : B51_1, IValidatedValue<V51, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V51, global::Probes.Shapes.ShapeFault> Create(bool value) => new V51(value); +} + +// row 52 +public abstract record B52_2 { } +public abstract record B52_1 : B52_2 { } +public sealed partial record V52 : B52_1, IValue<global::System.DateTimeOffset>; + +// row 53 +public partial struct O53 +{ +public interface IMark53 { int Mark(); } +public abstract record B53_1 : IMark53 { int IMark53.Mark() => 1; } +public sealed partial record V53 : B53_1, IValue<global::System.Guid>; +} + +// row 54 +public partial struct O54 +{ +public abstract record B54_1 : System.IEquatable<B54_1> { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); bool System.IEquatable<B54_1>.Equals(B54_1? other) => ReferenceEquals(this, other); } +public sealed partial record V54 : B54_1, IValidatedValue<V54, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V54, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V54(value); +} +} + +// row 55 +public partial struct O55 +{ +public abstract record B55_3 : System.IEquatable<B55_3> { bool System.IEquatable<B55_3>.Equals(B55_3? other) => ReferenceEquals(this, other); } +public abstract record B55_2 : B55_3 { } +public abstract record B55_1 : B55_2 { } +public sealed partial record V55 : B55_1, IValue<byte>; +} + +// row 56 +public interface IMark56 { int Mark(); } +public abstract record B56_1 : IMark56 { int IMark56.Mark() => 1; } +public sealed partial record V56 : B56_1, IValidatedValue<V56, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V56, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V56(value); +} + +// row 57 +public static partial class O57 +{ +public abstract record B57_1 { public virtual bool Equals(B57_1? other) => ReferenceEquals(this, other); } +public sealed partial record V57 : B57_1, IValidatedValue<V57, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V57, global::Probes.Shapes.ShapeFault> Create(double value) => new V57(value); +} +} + +// row 58 +public interface IMark58 { int Mark(); } +public abstract record B58_1 : IMark58 { public virtual bool Equals(B58_1? other) => ReferenceEquals(this, other); int IMark58.Mark() => 1; } +public sealed partial record V58 : B58_1, IValidatedValue<V58, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V58, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V58(value); +} + +// row 59 +public partial struct O59 +{ +public abstract record B59_1 : System.IEquatable<B59_1> { bool System.IEquatable<B59_1>.Equals(B59_1? other) => ReferenceEquals(this, other); } +public sealed partial record V59 : B59_1, IValue<global::System.DateOnly>; +} + +// row 60 +public abstract record B60_1 : System.IEquatable<B60_1> { public virtual bool Equals(B60_1? other) => ReferenceEquals(this, other); bool System.IEquatable<B60_1>.Equals(B60_1? other) => ReferenceEquals(this, other); } +public sealed partial record V60 : B60_1, IValidatedValue<V60, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V60, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V60(value); +} + +// row 61 +public abstract record B61_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V61 : B61_1, IValue<ulong>; + +// row 62 +public static partial class O62 +{ +public abstract record B62_2 { public virtual bool Equals(B62_2? other) => ReferenceEquals(this, other); } +public abstract record B62_1 : B62_2 { } +public sealed partial record V62 : B62_1, IValue<global::Probes.Shapes.PRec>; +} + +// row 63 +public static partial class O63 +{ +public sealed partial class P63 +{ +public abstract record B63_1 : System.IEquatable<B63_1> { public virtual bool Equals(B63_1? other) => ReferenceEquals(this, other); bool System.IEquatable<B63_1>.Equals(B63_1? other) => ReferenceEquals(this, other); } +public sealed partial record V63 : B63_1, IValidatedValue<V63, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V63, global::Probes.Shapes.ShapeFault> Create(long value) => new V63(value); +} +} +} + +// row 64 +public static partial class O64 +{ +public sealed partial class P64 +{ +public abstract record B64_1 : System.IEquatable<B64_1> { bool System.IEquatable<B64_1>.Equals(B64_1? other) => ReferenceEquals(this, other); } +public sealed partial record V64 : B64_1, IValidatedValue<V64, int, global::Probes.Shapes.ShapeFault> +{ + public static Result<V64, global::Probes.Shapes.ShapeFault> Create(int value) => new V64(value); +} +} +} + +// row 65 +public static partial class O65 +{ +public abstract record B65_1 : System.IEquatable<B65_1> { bool System.IEquatable<B65_1>.Equals(B65_1? other) => ReferenceEquals(this, other); } +public sealed partial record V65 : B65_1, IValue<string>; +} + +// row 66 +public static partial class O66 +{ +public sealed partial class P66 +{ +public abstract record B66_1 : System.IEquatable<B66_1> { bool System.IEquatable<B66_1>.Equals(B66_1? other) => ReferenceEquals(this, other); } +public sealed partial record V66 : B66_1, IValidatedValue<V66, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V66, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V66(value); +} +} +} + +// row 67 +public static partial class O67 +{ +public sealed partial class P67 +{ +public abstract record B67_1 { public virtual bool Equals(B67_1? other) => ReferenceEquals(this, other); } +public sealed partial record V67 : B67_1, IValue<global::System.DateTime>; +} +} + +// row 68 +public static partial class O68 +{ +public abstract record B68_1 { public virtual bool Equals(B68_1? other) => ReferenceEquals(this, other); } +public sealed partial record V68 : B68_1, IValidatedValue<V68, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V68, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V68(value); +} +} + +// row 69 +public static partial class O69 +{ +public sealed partial class P69 +{ +public abstract record B69_2 { public virtual bool Equals(B69_2? other) => ReferenceEquals(this, other); } +public abstract record B69_1 : B69_2 { } +public sealed partial record V69 : B69_1, IValue<bool>; +} +} + +// row 70 +public abstract record B70_2 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B70_1 : B70_2 { } +public sealed partial record V70 : B70_1, IValidatedValue<V70, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V70, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V70(value); +} + +// row 71 +public interface IMark71 { int Mark(); } +public abstract record B71_1 : System.IEquatable<B71_1>, IMark71 { bool System.IEquatable<B71_1>.Equals(B71_1? other) => ReferenceEquals(this, other); int IMark71.Mark() => 1; } +public sealed partial record V71 : B71_1, IValidatedValue<V71, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V71, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V71(value); +} + +// row 72 +public static partial class O72 +{ +public abstract record B72_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V72 : B72_1, IValue<byte>; +} + +// row 73 +public static partial class O73 +{ +public sealed partial class P73 +{ +public abstract record B73_1 : System.IEquatable<B73_1> { bool System.IEquatable<B73_1>.Equals(B73_1? other) => ReferenceEquals(this, other); } +public sealed partial record V73 : B73_1, IValue<global::System.Guid>; +} +} + +// row 74 +public static partial class O74 +{ +public sealed partial class P74 +{ +public abstract record B74_2 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B74_1 : B74_2 { } +public sealed partial record V74 : B74_1, IValidatedValue<V74, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V74, global::Probes.Shapes.ShapeFault> Create(double value) => new V74(value); +} +} +} + +// row 75 +public static partial class O75 +{ +public abstract record B75_1 : System.IEquatable<B75_1> { bool System.IEquatable<B75_1>.Equals(B75_1? other) => ReferenceEquals(this, other); } +public sealed partial record V75 : B75_1, IValue<global::NodaTime.Instant>; +} + +// row 76 +public static partial class O76 +{ +public interface IMark76 { int Mark(); } +public abstract record B76_1 : System.IEquatable<B76_1>, IMark76 { bool System.IEquatable<B76_1>.Equals(B76_1? other) => ReferenceEquals(this, other); int IMark76.Mark() => 1; } +public sealed partial record V76 : B76_1, IValidatedValue<V76, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V76, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V76(value); +} +} + +// row 77 +public static partial class O77 +{ +public sealed partial class P77 +{ +public abstract record B77_2 : System.IEquatable<B77_2> { bool System.IEquatable<B77_2>.Equals(B77_2? other) => ReferenceEquals(this, other); } +public abstract record B77_1 : B77_2 { } +public sealed partial record V77 : B77_1, IValidatedValue<V77, char, global::Probes.Shapes.ShapeFault> +{ + public static Result<V77, global::Probes.Shapes.ShapeFault> Create(char value) => new V77(value); +} +} +} + +// row 78 +public static partial class O78 +{ +public sealed partial class P78 +{ +public abstract record B78_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V78 : B78_1, IValue<decimal>; +} +} + +// row 79 +public static partial class O79 +{ +public abstract record B79_2 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B79_1 : B79_2 { } +public sealed partial record V79 : B79_1, IValue<global::System.TimeOnly>; +} + +// row 80 +public static partial class O80 +{ +public sealed partial class P80 +{ +public abstract record B80_3 { public virtual bool Equals(B80_3? other) => ReferenceEquals(this, other); } +public abstract record B80_2 : B80_3 { } +public abstract record B80_1 : B80_2 { } +public sealed partial record V80 : B80_1, IValidatedValue<V80, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V80, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V80(value); +} +} +} + +// row 81 +public static partial class O81 +{ +public interface IMark81 { int Mark(); } +public abstract record B81_1 : System.IEquatable<B81_1>, IMark81 { bool System.IEquatable<B81_1>.Equals(B81_1? other) => ReferenceEquals(this, other); int IMark81.Mark() => 1; } +public sealed partial record V81 : B81_1, IValue<ulong>; +} + +// row 82 +public static partial class O82 +{ +public interface IMark82 { int Mark(); } +public abstract record B82_1 : IMark82 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); int IMark82.Mark() => 1; } +public sealed partial record V82 : B82_1, IValue<global::System.DateOnly>; +} + +// row 83 +public abstract record B83_1 : System.IEquatable<B83_1> { bool System.IEquatable<B83_1>.Equals(B83_1? other) => ReferenceEquals(this, other); } +public sealed partial record V83 : B83_1, IValue<ulong>; + +// row 84 +public abstract record B84_1 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public sealed partial record V84 : B84_1, IValidatedValue<V84, char, global::Probes.Shapes.ShapeFault> +{ + public static Result<V84, global::Probes.Shapes.ShapeFault> Create(char value) => new V84(value); +} + +// row 85 +public static partial class O85 +{ +public interface IMark85 { int Mark(); } +public abstract record B85_1 : System.IEquatable<B85_1>, IMark85 { bool System.IEquatable<B85_1>.Equals(B85_1? other) => ReferenceEquals(this, other); int IMark85.Mark() => 1; } +public sealed partial record V85 : B85_1, IValue<global::System.TimeSpan>; +} + +// row 86 +public partial struct O86 +{ +public abstract record B86_2 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record B86_1 : B86_2 { } +public sealed partial record V86 : B86_1, IValue<decimal>; +} + +// row 87 +public partial struct O87 +{ +public abstract record B87_1 : System.IEquatable<B87_1> { bool System.IEquatable<B87_1>.Equals(B87_1? other) => ReferenceEquals(this, other); } +public sealed partial record V87 : B87_1, IValidatedValue<V87, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V87, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V87(value); +} +} + +// row 88 +public static partial class O88 +{ +public abstract record B88_2 : System.IEquatable<B88_2> { bool System.IEquatable<B88_2>.Equals(B88_2? other) => ReferenceEquals(this, other); } +public abstract record B88_1 : B88_2 { } +public sealed partial record V88 : B88_1, IValidatedValue<V88, global::System.Guid, global::Probes.Shapes.ShapeFault> +{ + public static Result<V88, global::Probes.Shapes.ShapeFault> Create(global::System.Guid value) => new V88(value); +} +} + +// row 89 +public static partial class O89 +{ +public abstract record B89_1 : System.IEquatable<B89_1> { bool System.IEquatable<B89_1>.Equals(B89_1? other) => ReferenceEquals(this, other); } +public sealed partial record V89 : B89_1, IValue<byte>; +} + +// row 90 +public abstract record B90_3 : System.IEquatable<B90_3> { bool System.IEquatable<B90_3>.Equals(B90_3? other) => ReferenceEquals(this, other); } +public abstract record B90_2 : B90_3 { } +public abstract record B90_1 : B90_2 { } +public sealed partial record V90 : B90_1, IValidatedValue<V90, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V90, global::Probes.Shapes.ShapeFault> Create(long value) => new V90(value); +} + +// row 91 +public static partial class O91 +{ +public sealed partial class P91 +{ +public abstract record B91_2 : System.IEquatable<B91_2> { bool System.IEquatable<B91_2>.Equals(B91_2? other) => ReferenceEquals(this, other); } +public abstract record B91_1 : B91_2 { } +public sealed partial record V91 : B91_1, IValue<string>; +} +} + +// row 92 +public abstract record B92_3 : System.IEquatable<B92_3> { bool System.IEquatable<B92_3>.Equals(B92_3? other) => ReferenceEquals(this, other); } +public abstract record B92_2 : B92_3 { } +public abstract record B92_1 : B92_2 { } +public sealed partial record V92 : B92_1, IValidatedValue<V92, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V92, global::Probes.Shapes.ShapeFault> Create(bool value) => new V92(value); +} + +// row 93 +public abstract record B93_1 : System.IEquatable<B93_1> { bool System.IEquatable<B93_1>.Equals(B93_1? other) => ReferenceEquals(this, other); } +public sealed partial record V93 : B93_1, IValidatedValue<V93, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V93, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V93(value); +} + +// row 94 +public static partial class O94 +{ +public sealed partial class P94 +{ +public interface IMark94 { int Mark(); } +public abstract record B94_1 : System.IEquatable<B94_1>, IMark94 { bool System.IEquatable<B94_1>.Equals(B94_1? other) => ReferenceEquals(this, other); int IMark94.Mark() => 1; } +public sealed partial record V94 : B94_1, IValue<int>; +} +} + +// row 95 +public static partial class O95 +{ +public abstract record B95_1 : System.IEquatable<B95_1> { bool System.IEquatable<B95_1>.Equals(B95_1? other) => ReferenceEquals(this, other); } +public sealed partial record V95 : B95_1, IValidatedValue<V95, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V95, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V95(value); +} +} + +// row 96 +public partial struct O96 +{ +public interface IMark96 { int Mark(); } +public abstract record B96_1 : IMark96 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); int IMark96.Mark() => 1; } +public sealed partial record V96 : B96_1, IValue<global::Probes.Shapes.PEnum>; +} + +// row 97 +public static partial class O97 +{ +public sealed partial class P97 +{ +public abstract record B97_3 { } +public abstract record B97_2 : B97_3 { } +public abstract record B97_1 : B97_2 { } +public sealed partial record V97 : B97_1, IValue<global::Probes.Shapes.PRec>; +} +} + +// row 98 +public static partial class O98 +{ +public interface IMark98 { int Mark(); } +public abstract record B98_1 : System.IEquatable<B98_1>, IMark98 { bool System.IEquatable<B98_1>.Equals(B98_1? other) => ReferenceEquals(this, other); int IMark98.Mark() => 1; } +public sealed partial record V98 : B98_1, IValue<global::System.DateTime>; +} + +// row 99 +public static partial class O99 +{ +public interface IMark99 { int Mark(); } +public abstract record B99_1 : IMark99 { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); int IMark99.Mark() => 1; } +public sealed partial record V99 : B99_1, IValidatedValue<V99, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V99, global::Probes.Shapes.ShapeFault> Create(double value) => new V99(value); +} +} + +// row 100 +public static partial class O100 +{ +public abstract record B100_3 : System.IEquatable<B100_3> { bool System.IEquatable<B100_3>.Equals(B100_3? other) => ReferenceEquals(this, other); } +public abstract record B100_2 : B100_3 { } +public abstract record B100_1 : B100_2 { } +public sealed partial record V100 : B100_1, IValidatedValue<V100, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V100, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V100(value); +} +} + +public static partial class C09Table +{ + public static readonly C09Case[] Cases = + [ + new(0, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=own-record-class", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O0.P0.V0), [typeof(O0.P0.B0_1)], x => O0.P0.V0.FromKnownGood((global::Probes.Shapes.PRec)x), []), + new(1, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTimeOffset", "sealed-record-class", "nested-two-levels", "base-depth-3", typeof(O1.P1.V1), [typeof(O1.P1.B1_1), typeof(O1.P1.B1_2), typeof(O1.P1.B1_3)], x => O1.P1.V1.From((global::System.DateTimeOffset)x), []), + new(2, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=long", "validated-class", "nested-in-struct", "base-depth-1", typeof(O2.V2), [typeof(O2.B2_1)], x => O2.V2.FromKnownGood((long)x), []), + new(3, "base-equality=none;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=DateOnly", "sealed-record-class", "top-level", "base-depth-1", typeof(V3), [typeof(B3_1)], x => V3.From((global::System.DateOnly)x), []), + new(4, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=string", "validated-class", "top-level", "explicit-interface-member", typeof(V4), [typeof(B4_1)], x => V4.FromKnownGood((string)x), []), + new(5, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=Uri", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O5.V5), [typeof(O5.B5_1)], x => O5.V5.From((global::System.Uri)x), []), + new(6, "base-equality=none;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=flags-enum", "validated-class", "nested-in-class", "base-depth-2", typeof(O6.V6), [typeof(O6.B6_1), typeof(O6.B6_2)], x => O6.V6.FromKnownGood((global::Probes.Shapes.PFlags)x), []), + new(7, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeSpan", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O7.V7), [typeof(O7.B7_1), typeof(O7.B7_2)], x => O7.V7.From((global::System.TimeSpan)x), []), + new(8, "base-equality=none;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeOnly", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O8.V8), [typeof(O8.B8_1)], x => O8.V8.From((global::System.TimeOnly)x), []), + new(9, "base-equality=none;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=byte", "sealed-record-class", "nested-two-levels", "explicit-interface-member", typeof(O9.P9.V9), [typeof(O9.P9.B9_1)], x => O9.P9.V9.From((byte)x), []), + new(10, "base-equality=override-Equals;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=DateOnly", "validated-class", "top-level", "base-depth-2", typeof(V10), [typeof(B10_1), typeof(B10_2)], x => V10.FromKnownGood((global::System.DateOnly)x), []), + new(11, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=Guid", "validated-class", "nested-in-class", "base-depth-1", typeof(O11.V11), [typeof(O11.B11_1)], x => O11.V11.FromKnownGood((global::System.Guid)x), []), + new(12, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=ulong", "validated-class", "nested-in-struct", "base-depth-2", typeof(O12.V12), [typeof(O12.B12_1), typeof(O12.B12_2)], x => O12.V12.FromKnownGood((ulong)x), []), + new(13, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=decimal", "validated-class", "nested-in-struct", "base-depth-3", typeof(O13.V13), [typeof(O13.B13_1), typeof(O13.B13_2), typeof(O13.B13_3)], x => O13.V13.FromKnownGood((decimal)x), []), + new(14, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=int", "sealed-record-class", "nested-in-class", "base-depth-3", typeof(O14.V14), [typeof(O14.B14_1), typeof(O14.B14_2), typeof(O14.B14_3)], x => O14.V14.From((int)x), [("shape.kind", "sealed-record-class")]), + new(15, "base-equality=override-Equals;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=NodaTime.Instant", "validated-class", "nested-in-struct", "explicit-interface-member", typeof(O15.V15), [typeof(O15.B15_1)], x => O15.V15.FromKnownGood((global::NodaTime.Instant)x), []), + new(16, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=double", "sealed-record-class", "top-level", "base-depth-3", typeof(V16), [typeof(B16_1), typeof(B16_2), typeof(B16_3)], x => V16.From((double)x), []), + new(17, "base-equality=override-Equals;collection=Dictionary-key;shape.inheritance=base-explicit-IEquatable;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeSpan", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O17.V17), [typeof(O17.B17_1)], x => O17.V17.From((global::System.TimeSpan)x), [("shape.kind", "sealed-record-class")]), + new(18, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=flags-enum", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O18.P18.V18), [typeof(O18.P18.B18_1)], x => O18.P18.V18.From((global::Probes.Shapes.PFlags)x), []), + new(19, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=char", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V19), [typeof(B19_1)], x => V19.FromKnownGood((char)x), []), + new(20, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=DateTime", "validated-class", "top-level", "base-depth-3", typeof(V20), [typeof(B20_1), typeof(B20_2), typeof(B20_3)], x => V20.FromKnownGood((global::System.DateTime)x), []), + new(21, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTime", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O21.V21), [typeof(O21.B21_1), typeof(O21.B21_2)], x => O21.V21.From((global::System.DateTime)x), []), + new(22, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=NodaTime.Instant", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O22.P22.V22), [typeof(O22.P22.B22_1), typeof(O22.P22.B22_2)], x => O22.P22.V22.From((global::NodaTime.Instant)x), []), + new(23, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=TimeOnly", "validated-class", "top-level", "base-depth-1", typeof(V23), [typeof(B23_1)], x => V23.FromKnownGood((global::System.TimeOnly)x), []), + new(24, "base-equality=none;collection=Distinct;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri", "validated-class", "nested-two-levels", "base-depth-1", typeof(O24.P24.V24), [typeof(O24.P24.B24_1)], x => O24.P24.V24.FromKnownGood((global::System.Uri)x), []), + new(25, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=bool", "validated-class", "nested-in-class", "base-depth-1", typeof(O25.V25), [typeof(O25.B25_1)], x => O25.V25.FromKnownGood((bool)x), []), + new(26, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum", "validated-class", "nested-two-levels", "base-depth-3", typeof(O26.P26.V26), [typeof(O26.P26.B26_1), typeof(O26.P26.B26_2), typeof(O26.P26.B26_3)], x => O26.P26.V26.FromKnownGood((global::Probes.Shapes.PEnum)x), []), + new(27, "base-equality=none;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=long", "sealed-record-class", "top-level", "base-depth-2", typeof(V27), [typeof(B27_1), typeof(B27_2)], x => V27.From((long)x), []), + new(28, "base-equality=none;collection=Distinct;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=own-record-class", "sealed-record-class", "nested-in-struct", "base-depth-1", typeof(O28.V28), [typeof(O28.B28_1)], x => O28.V28.From((global::Probes.Shapes.PRec)x), []), + new(29, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=bool", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O29.V29), [typeof(O29.B29_1)], x => O29.V29.From((bool)x), []), + new(30, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=int", "sealed-record-class", "top-level", "base-depth-2", typeof(V30), [typeof(B30_1), typeof(B30_2)], x => V30.From((int)x), []), + new(31, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=ulong", "sealed-record-class", "nested-two-levels", "base-depth-3", typeof(O31.P31.V31), [typeof(O31.P31.B31_1), typeof(O31.P31.B31_2), typeof(O31.P31.B31_3)], x => O31.P31.V31.From((ulong)x), []), + new(32, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=double", "validated-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O32.V32), [typeof(O32.B32_1)], x => O32.V32.FromKnownGood((double)x), []), + new(33, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=string", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O33.P33.V33), [typeof(O33.P33.B33_1)], x => O33.P33.V33.From((string)x), []), + new(34, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset", "validated-class", "nested-in-struct", "base-depth-1", typeof(O34.V34), [typeof(O34.B34_1)], x => O34.V34.FromKnownGood((global::System.DateTimeOffset)x), []), + new(35, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=Guid", "sealed-record-class", "top-level", "base-depth-3", typeof(V35), [typeof(B35_1), typeof(B35_2), typeof(B35_3)], x => V35.From((global::System.Guid)x), []), + new(36, "base-equality=none;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=char", "sealed-record-class", "nested-in-class", "base-depth-3", typeof(O36.V36), [typeof(O36.B36_1), typeof(O36.B36_2), typeof(O36.B36_3)], x => O36.V36.From((char)x), []), + new(37, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=decimal", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O37.V37), [typeof(O37.B37_1)], x => O37.V37.From((decimal)x), []), + new(38, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=enum", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O38.V38), [typeof(O38.B38_1), typeof(O38.B38_2)], x => O38.V38.From((global::Probes.Shapes.PEnum)x), []), + new(39, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=byte", "validated-class", "top-level", "base-depth-2", typeof(V39), [typeof(B39_1), typeof(B39_2)], x => V39.FromKnownGood((byte)x), []), + new(40, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=flags-enum", "validated-class", "nested-in-struct", "base-depth-3", typeof(O40.V40), [typeof(O40.B40_1), typeof(O40.B40_2), typeof(O40.B40_3)], x => O40.V40.FromKnownGood((global::Probes.Shapes.PFlags)x), []), + new(41, "base-equality=none;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=TimeSpan", "validated-class", "top-level", "base-depth-3", typeof(V41), [typeof(B41_1), typeof(B41_2), typeof(B41_3)], x => V41.FromKnownGood((global::System.TimeSpan)x), []), + new(42, "base-equality=override-Equals;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=char", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O42.V42), [typeof(O42.B42_1)], x => O42.V42.From((char)x), []), + new(43, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=int", "sealed-record-class", "nested-in-struct", "base-depth-1", typeof(O43.V43), [typeof(O43.B43_1)], x => O43.V43.From((int)x), []), + new(44, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Uri", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O44.V44), [typeof(O44.B44_1)], x => O44.V44.From((global::System.Uri)x), []), + new(45, "base-equality=none;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=enum", "validated-class", "nested-in-class", "base-depth-1", typeof(O45.V45), [typeof(O45.B45_1)], x => O45.V45.FromKnownGood((global::Probes.Shapes.PEnum)x), []), + new(46, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=long", "validated-class", "nested-in-class", "explicit-interface-member", typeof(O46.V46), [typeof(O46.B46_1)], x => O46.V46.FromKnownGood((long)x), []), + new(47, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=NodaTime.Instant", "sealed-record-class", "top-level", "base-depth-3", typeof(V47), [typeof(B47_1), typeof(B47_2), typeof(B47_3)], x => V47.From((global::NodaTime.Instant)x), []), + new(48, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=string", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O48.V48), [typeof(O48.B48_1), typeof(O48.B48_2), typeof(O48.B48_3)], x => O48.V48.From((string)x), []), + new(49, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=own-record-class", "sealed-record-class", "top-level", "explicit-interface-member", typeof(V49), [typeof(B49_1)], x => V49.From((global::Probes.Shapes.PRec)x), [("shape.kind", "sealed-record-class")]), + new(50, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateOnly", "sealed-record-class", "nested-two-levels", "base-depth-3", typeof(O50.P50.V50), [typeof(O50.P50.B50_1), typeof(O50.P50.B50_2), typeof(O50.P50.B50_3)], x => O50.P50.V50.From((global::System.DateOnly)x), []), + new(51, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=bool", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V51), [typeof(B51_1)], x => V51.FromKnownGood((bool)x), []), + new(52, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=DateTimeOffset", "sealed-record-class", "top-level", "base-depth-2", typeof(V52), [typeof(B52_1), typeof(B52_2)], x => V52.From((global::System.DateTimeOffset)x), []), + new(53, "base-equality=none;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Guid", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O53.V53), [typeof(O53.B53_1)], x => O53.V53.From((global::System.Guid)x), []), + new(54, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=DateTime", "validated-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O54.V54), [typeof(O54.B54_1)], x => O54.V54.FromKnownGood((global::System.DateTime)x), []), + new(55, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=byte", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O55.V55), [typeof(O55.B55_1), typeof(O55.B55_2), typeof(O55.B55_3)], x => O55.V55.From((byte)x), []), + new(56, "base-equality=none;collection=Distinct;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=decimal", "validated-class", "top-level", "explicit-interface-member", typeof(V56), [typeof(B56_1)], x => V56.FromKnownGood((decimal)x), [("shape.kind", "validated-class")]), + new(57, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=double", "validated-class", "nested-in-class", "base-depth-1", typeof(O57.V57), [typeof(O57.B57_1)], x => O57.V57.FromKnownGood((double)x), []), + new(58, "base-equality=override-Equals;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=TimeOnly", "validated-class", "top-level", "explicit-interface-member", typeof(V58), [typeof(B58_1)], x => V58.FromKnownGood((global::System.TimeOnly)x), [("shape.kind", "validated-class")]), + new(59, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateOnly", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O59.V59), [typeof(O59.B59_1)], x => O59.V59.From((global::System.DateOnly)x), [("shape.kind", "sealed-record-class")]), + new(60, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=enum", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V60), [typeof(B60_1)], x => V60.FromKnownGood((global::Probes.Shapes.PEnum)x), []), + new(61, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=ulong", "sealed-record-class", "top-level", "base-depth-1", typeof(V61), [typeof(B61_1)], x => V61.From((ulong)x), []), + new(62, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=closed-set;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=own-record-class", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O62.V62), [typeof(O62.B62_1), typeof(O62.B62_2)], x => O62.V62.From((global::Probes.Shapes.PRec)x), [("shape.kind", "sealed-record-class")]), + new(63, "base-equality=override-Equals;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=long", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O63.P63.V63), [typeof(O63.P63.B63_1)], x => O63.P63.V63.FromKnownGood((long)x), []), + new(64, "base-equality=none;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=int", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O64.P64.V64), [typeof(O64.P64.B64_1)], x => O64.P64.V64.FromKnownGood((int)x), []), + new(65, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=string", "sealed-record-class", "nested-in-class", "base-depth-1", typeof(O65.V65), [typeof(O65.B65_1)], x => O65.V65.From((string)x), []), + new(66, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeSpan", "validated-class", "nested-two-levels", "base-depth-1", typeof(O66.P66.V66), [typeof(O66.P66.B66_1)], x => O66.P66.V66.FromKnownGood((global::System.TimeSpan)x), []), + new(67, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTime", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O67.P67.V67), [typeof(O67.P67.B67_1)], x => O67.P67.V67.From((global::System.DateTime)x), []), + new(68, "base-equality=override-Equals;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=ulong", "validated-class", "nested-in-class", "base-depth-1", typeof(O68.V68), [typeof(O68.B68_1)], x => O68.V68.FromKnownGood((ulong)x), [("shape.kind", "validated-class")]), + new(69, "base-equality=override-Equals;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=bool", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O69.P69.V69), [typeof(O69.P69.B69_1), typeof(O69.P69.B69_2)], x => O69.P69.V69.From((bool)x), []), + new(70, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=Uri", "validated-class", "top-level", "base-depth-2", typeof(V70), [typeof(B70_1), typeof(B70_2)], x => V70.FromKnownGood((global::System.Uri)x), []), + new(71, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=flags-enum", "validated-class", "top-level", "explicit-interface-member", typeof(V71), [typeof(B71_1)], x => V71.FromKnownGood((global::Probes.Shapes.PFlags)x), []), + new(72, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=byte", "sealed-record-class", "nested-in-class", "base-depth-1", typeof(O72.V72), [typeof(O72.B72_1)], x => O72.V72.From((byte)x), []), + new(73, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Guid", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O73.P73.V73), [typeof(O73.P73.B73_1)], x => O73.P73.V73.From((global::System.Guid)x), []), + new(74, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=double", "validated-class", "nested-two-levels", "base-depth-2", typeof(O74.P74.V74), [typeof(O74.P74.B74_1), typeof(O74.P74.B74_2)], x => O74.P74.V74.FromKnownGood((double)x), []), + new(75, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=NodaTime.Instant", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O75.V75), [typeof(O75.B75_1)], x => O75.V75.From((global::NodaTime.Instant)x), []), + new(76, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTimeOffset", "validated-class", "nested-in-class", "explicit-interface-member", typeof(O76.V76), [typeof(O76.B76_1)], x => O76.V76.FromKnownGood((global::System.DateTimeOffset)x), []), + new(77, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=char", "validated-class", "nested-two-levels", "base-depth-2", typeof(O77.P77.V77), [typeof(O77.P77.B77_1), typeof(O77.P77.B77_2)], x => O77.P77.V77.FromKnownGood((char)x), []), + new(78, "base-equality=override-GetHashCode;collection=HashSet;shape.inheritance=base-depth-1;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=decimal", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O78.P78.V78), [typeof(O78.P78.B78_1)], x => O78.P78.V78.From((decimal)x), [("shape.kind", "sealed-record-class")]), + new(79, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeOnly", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O79.V79), [typeof(O79.B79_1), typeof(O79.B79_2)], x => O79.V79.From((global::System.TimeOnly)x), []), + new(80, "base-equality=override-Equals;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeOnly", "validated-class", "nested-two-levels", "base-depth-3", typeof(O80.P80.V80), [typeof(O80.P80.B80_1), typeof(O80.P80.B80_2), typeof(O80.P80.B80_3)], x => O80.P80.V80.FromKnownGood((global::System.TimeOnly)x), []), + new(81, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=ulong", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O81.V81), [typeof(O81.B81_1)], x => O81.V81.From((ulong)x), []), + new(82, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateOnly", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O82.V82), [typeof(O82.B82_1)], x => O82.V82.From((global::System.DateOnly)x), []), + new(83, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=ulong", "sealed-record-class", "top-level", "base-explicit-IEquatable", typeof(V83), [typeof(B83_1)], x => V83.From((ulong)x), []), + new(84, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=char", "validated-class", "top-level", "base-depth-1", typeof(V84), [typeof(B84_1)], x => V84.FromKnownGood((char)x), []), + new(85, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeSpan", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O85.V85), [typeof(O85.B85_1)], x => O85.V85.From((global::System.TimeSpan)x), []), + new(86, "base-equality=override-GetHashCode;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=decimal", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O86.V86), [typeof(O86.B86_1), typeof(O86.B86_2)], x => O86.V86.From((decimal)x), []), + new(87, "base-equality=none;collection=HashSet;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset", "validated-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O87.V87), [typeof(O87.B87_1)], x => O87.V87.FromKnownGood((global::System.DateTimeOffset)x), []), + new(88, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=Guid", "validated-class", "nested-in-class", "base-depth-2", typeof(O88.V88), [typeof(O88.B88_1), typeof(O88.B88_2)], x => O88.V88.FromKnownGood((global::System.Guid)x), []), + new(89, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=byte", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O89.V89), [typeof(O89.B89_1)], x => O89.V89.From((byte)x), [("shape.site", "nested-in-class")]), + new(90, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=long", "validated-class", "top-level", "base-depth-3", typeof(V90), [typeof(B90_1), typeof(B90_2), typeof(B90_3)], x => V90.FromKnownGood((long)x), []), + new(91, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=string", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O91.P91.V91), [typeof(O91.P91.B91_1), typeof(O91.P91.B91_2)], x => O91.P91.V91.From((string)x), []), + new(92, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=bool", "validated-class", "top-level", "base-depth-3", typeof(V92), [typeof(B92_1), typeof(B92_2), typeof(B92_3)], x => V92.FromKnownGood((bool)x), [("shape.kind", "validated-class")]), + new(93, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=NodaTime.Instant", "validated-class", "top-level", "base-depth-1", typeof(V93), [typeof(B93_1)], x => V93.FromKnownGood((global::NodaTime.Instant)x), []), + new(94, "base-equality=explicit-IEquatable;collection=Distinct;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=int", "sealed-record-class", "nested-two-levels", "explicit-interface-member", typeof(O94.P94.V94), [typeof(O94.P94.B94_1)], x => O94.P94.V94.From((int)x), []), + new(95, "base-equality=none;collection=Distinct;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=flags-enum", "validated-class", "nested-in-class", "base-explicit-IEquatable", typeof(O95.V95), [typeof(O95.B95_1)], x => O95.V95.FromKnownGood((global::Probes.Shapes.PFlags)x), []), + new(96, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=enum", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O96.V96), [typeof(O96.B96_1)], x => O96.V96.From((global::Probes.Shapes.PEnum)x), []), + new(97, "base-equality=none;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=own-record-class", "sealed-record-class", "nested-two-levels", "base-depth-3", typeof(O97.P97.V97), [typeof(O97.P97.B97_1), typeof(O97.P97.B97_2), typeof(O97.P97.B97_3)], x => O97.P97.V97.From((global::Probes.Shapes.PRec)x), []), + new(98, "base-equality=explicit-IEquatable;collection=HashSet;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=DateTime", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O98.V98), [typeof(O98.B98_1)], x => O98.V98.From((global::System.DateTime)x), []), + new(99, "base-equality=override-GetHashCode;collection=Dictionary-key;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=in-generic-type;shape.wrapped=double", "validated-class", "nested-in-class", "explicit-interface-member", typeof(O99.V99), [typeof(O99.B99_1)], x => O99.V99.FromKnownGood((double)x), [("shape.site", "nested-in-class")]), + new(100, "base-equality=explicit-IEquatable;collection=Dictionary-key;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=Uri", "validated-class", "nested-in-class", "base-depth-3", typeof(O100.V100), [typeof(O100.B100_1), typeof(O100.B100_2), typeof(O100.B100_3)], x => O100.V100.FromKnownGood((global::System.Uri)x), [("shape.site", "nested-in-class")]), + ]; +} diff --git a/tools/probe/corpus/harness/C11Shapes.g.cs b/tools/probe/corpus/harness/C11Shapes.g.cs new file mode 100644 index 00000000..992834fa --- /dev/null +++ b/tools/probe/corpus/harness/C11Shapes.g.cs @@ -0,0 +1,2596 @@ +// <auto-generated/> by tools/probe/generators/decls.cs from rows/C11ParseShapesBuildProbe.json.gz: one value object per row. Do not edit; regenerate with the rows. +#nullable enable +// CMTK1023: the bases' own equality is the point (C09) or beside it (C11); CS8851: a base's Equals without GetHashCode, on purpose. +#pragma warning disable CMTK1023, CS8851, CS1591 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +namespace Probes.Build.C11; + +// row 0 +public static partial class O0 +{ +public interface IMark0 { int Mark(); } +public sealed partial record V0 : IValue<double>, IMark0 { int IMark0.Mark() => 1; } +} + +// row 1 +public sealed partial record V1 : IValidatedValue<V1, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V1, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V1(value); +} + +// row 2 +public partial struct O2 +{ +public abstract record B2_2 { } +public abstract record B2_1 : B2_2 { } +public sealed partial record V2 : B2_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC2_2 { } +public abstract record BC2_1 : BC2_2 { } +public sealed partial record VC2 : BC2_1, IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 3 +public static partial class O3 +{ +public sealed partial class P3 +{ +public readonly partial record struct V3 : IValidatedValue<V3, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault> +{ + public static Result<V3, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V3(value); +} +public readonly partial record struct VC3 : IValidatedValue<VC3, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC3, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC3(value); +} +} +} + +// row 4 +public interface IMark4 { int Mark(); } +public readonly partial record struct V4 : IValue<global::Probes.Build.C11ParsesByRef>, IMark4 { int IMark4.Mark() => 1; } +public readonly partial record struct VC4 : IValue<global::Probes.Build.C11ParsesFromIn>, IMark4 { int IMark4.Mark() => 1; } + +// row 5 +public partial struct O5 +{ +public abstract record B5_1 { } +public sealed partial record V5 : B5_1, IValidatedValue<V5, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault> +{ + public static Result<V5, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V5(value); +} +public abstract record BC5_1 { } +public sealed partial record VC5 : BC5_1, IValidatedValue<VC5, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC5, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC5(value); +} +} + +// row 6 +public static partial class O6 +{ +public readonly partial record struct V6 : IValue<global::Probes.Build.C11ParsesToInt> { } +public readonly partial record struct VC6 : IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 7 +public static partial class O7 +{ +public sealed partial class P7 +{ +public abstract record B7_1 { } +public sealed partial record V7 : B7_1, IValue<global::Probes.Build.C11ConstructedByRef> { } +public abstract record BC7_1 { } +public sealed partial record VC7 : BC7_1, IValue<global::Probes.Build.C11ParsesFromIn> { } +} +} + +// row 8 +public static partial class O8 +{ +public sealed partial class P8 +{ +public abstract record B8_1 : System.IEquatable<B8_1> { bool System.IEquatable<B8_1>.Equals(B8_1? other) => ReferenceEquals(this, other); } +public sealed partial record V8 : B8_1, IValue<long> { } +} +} + +// row 9 +public partial struct O9 +{ +public abstract record B9_3 { } +public abstract record B9_2 : B9_3 { } +public abstract record B9_1 : B9_2 { } +public sealed partial record V9 : B9_1, IValidatedValue<V9, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V9, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V9(value); +} +} + +// row 10 +public abstract record B10_1 : System.IEquatable<B10_1> { bool System.IEquatable<B10_1>.Equals(B10_1? other) => ReferenceEquals(this, other); } +public sealed partial record V10 : B10_1, IValidatedValue<V10, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V10, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V10(value); +} +public abstract record BC10_1 : System.IEquatable<BC10_1> { bool System.IEquatable<BC10_1>.Equals(BC10_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC10 : BC10_1, IValidatedValue<VC10, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC10, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC10(value); +} + +// row 11 +public static partial class O11 +{ +public interface IMark11 { int Mark(); } +public readonly partial record struct V11 : IValidatedValue<V11, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault>, IMark11 +{ + public static Result<V11, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V11(value); int IMark11.Mark() => 1; +} +public readonly partial record struct VC11 : IValidatedValue<VC11, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault>, IMark11 +{ + public static Result<VC11, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC11(value); int IMark11.Mark() => 1; +} +} + +// row 12 +public abstract record B12_2 { } +public abstract record B12_1 : B12_2 { } +public sealed partial record V12 : B12_1, IValue<bool> { } + +// row 13 +public static partial class O13 +{ +public abstract record B13_3 { } +public abstract record B13_2 : B13_3 { } +public abstract record B13_1 : B13_2 { } +public sealed partial record V13 : B13_1, IValidatedValue<V13, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault> +{ + public static Result<V13, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V13(value); +} +public abstract record BC13_3 { } +public abstract record BC13_2 : BC13_3 { } +public abstract record BC13_1 : BC13_2 { } +public sealed partial record VC13 : BC13_1, IValidatedValue<VC13, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC13, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC13(value); +} +} + +// row 14 +public partial struct O14 +{ +public readonly partial record struct V14 : IValidatedValue<V14, global::System.DateOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V14, global::Probes.Shapes.ShapeFault> Create(global::System.DateOnly value) => new V14(value); +} +} + +// row 15 +public partial struct O15 +{ +public readonly partial record struct V15 : IValue<global::Probes.Build.C11ConstructedByRef> { } +public readonly partial record struct VC15 : IValue<global::Probes.Build.C11ParsesFromIn> { } +} + +// row 16 +public static partial class O16 +{ +public sealed partial class P16 +{ +public interface IMark16 { int Mark(); } +public readonly partial record struct V16 : IValue<global::Probes.Build.C11AbstractText>, IMark16 { int IMark16.Mark() => 1; } +public readonly partial record struct VC16 : IValue<global::Probes.Build.C11ConstructedConcrete>, IMark16 { int IMark16.Mark() => 1; } +} +} + +// row 17 +public readonly partial record struct V17 : IValue<global::System.Uri> { } + +// row 18 +public static partial class O18 +{ +public sealed partial class P18 +{ +public abstract record B18_2 { } +public abstract record B18_1 : B18_2 { } +public sealed partial record V18 : B18_1, IValidatedValue<V18, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V18, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V18(value); +} +public abstract record BC18_2 { } +public abstract record BC18_1 : BC18_2 { } +public sealed partial record VC18 : BC18_1, IValidatedValue<VC18, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC18, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC18(value); +} +} +} + +// row 19 +public static partial class O19 +{ +public abstract record B19_1 : System.IEquatable<B19_1> { bool System.IEquatable<B19_1>.Equals(B19_1? other) => ReferenceEquals(this, other); } +public sealed partial record V19 : B19_1, IValue<global::Probes.Build.C11AbstractText> { } +public abstract record BC19_1 : System.IEquatable<BC19_1> { bool System.IEquatable<BC19_1>.Equals(BC19_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC19 : BC19_1, IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 20 +public partial struct O20 +{ +public interface IMark20 { int Mark(); } +public readonly partial record struct V20 : IValidatedValue<V20, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault>, IMark20 +{ + public static Result<V20, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V20(value); int IMark20.Mark() => 1; +} +public readonly partial record struct VC20 : IValidatedValue<VC20, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault>, IMark20 +{ + public static Result<VC20, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC20(value); int IMark20.Mark() => 1; +} +} + +// row 21 +public static partial class O21 +{ +public readonly partial record struct V21 : IValidatedValue<V21, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V21, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V21(value); +} +} + +// row 22 +public static partial class O22 +{ +public sealed partial class P22 +{ +public interface IMark22 { int Mark(); } +public readonly partial record struct V22 : IValue<global::Probes.Build.C11ParsesToInt>, IMark22 { int IMark22.Mark() => 1; } +public readonly partial record struct VC22 : IValue<global::Probes.Build.C11ParsedShape>, IMark22 { int IMark22.Mark() => 1; } +} +} + +// row 23 +public static partial class O23 +{ +public sealed partial class P23 +{ +public abstract record B23_3 { } +public abstract record B23_2 : B23_3 { } +public abstract record B23_1 : B23_2 { } +public sealed partial record V23 : B23_1, IValue<byte> { } +} +} + +// row 24 +public static partial class O24 +{ +public readonly partial record struct V24 : IValidatedValue<V24, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V24, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V24(value); +} +public readonly partial record struct VC24 : IValidatedValue<VC24, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC24, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC24(value); +} +} + +// row 25 +public interface IMark25 { int Mark(); } +public readonly partial record struct V25 : IValidatedValue<V25, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault>, IMark25 +{ + public static Result<V25, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V25(value); int IMark25.Mark() => 1; +} +public readonly partial record struct VC25 : IValidatedValue<VC25, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault>, IMark25 +{ + public static Result<VC25, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC25(value); int IMark25.Mark() => 1; +} + +// row 26 +public partial struct O26 +{ +public abstract record B26_1 : System.IEquatable<B26_1> { bool System.IEquatable<B26_1>.Equals(B26_1? other) => ReferenceEquals(this, other); } +public sealed partial record V26 : B26_1, IValidatedValue<V26, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V26, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V26(value); +} +public abstract record BC26_1 : System.IEquatable<BC26_1> { bool System.IEquatable<BC26_1>.Equals(BC26_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC26 : BC26_1, IValidatedValue<VC26, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC26, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC26(value); +} +} + +// row 27 +public static partial class O27 +{ +public readonly partial record struct V27 : IValidatedValue<V27, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V27, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V27(value); +} +public readonly partial record struct VC27 : IValidatedValue<VC27, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC27, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC27(value); +} +} + +// row 28 +public readonly partial record struct V28 : IValidatedValue<V28, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V28, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V28(value); +} +public readonly partial record struct VC28 : IValidatedValue<VC28, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC28, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC28(value); +} + +// row 29 +public partial struct O29 +{ +public interface IMark29 { int Mark(); } +public sealed partial record V29 : IValidatedValue<V29, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault>, IMark29 +{ + public static Result<V29, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V29(value); int IMark29.Mark() => 1; +} +public sealed partial record VC29 : IValidatedValue<VC29, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault>, IMark29 +{ + public static Result<VC29, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC29(value); int IMark29.Mark() => 1; +} +} + +// row 30 +public abstract record B30_2 { } +public abstract record B30_1 : B30_2 { } +public sealed partial record V30 : B30_1, IValue<global::Probes.Build.C11ParsesByRef> { } +public abstract record BC30_2 { } +public abstract record BC30_1 : BC30_2 { } +public sealed partial record VC30 : BC30_1, IValue<global::Probes.Build.C11ParsesFromIn> { } + +// row 31 +public static partial class O31 +{ +public readonly partial record struct V31 : IValidatedValue<V31, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V31, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V31(value); +} +public readonly partial record struct VC31 : IValidatedValue<VC31, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC31, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC31(value); +} +} + +// row 32 +public partial struct O32 +{ +public sealed partial record V32 : IValue<global::Probes.Build.C11AbstractText> { } +public sealed partial record VC32 : IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 33 +public static partial class O33 +{ +public abstract record B33_1 { } +public sealed partial record V33 : B33_1, IValidatedValue<V33, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V33, global::Probes.Shapes.ShapeFault> Create(long value) => new V33(value); +} +} + +// row 34 +public abstract record B34_3 { } +public abstract record B34_2 : B34_3 { } +public abstract record B34_1 : B34_2 { } +public sealed partial record V34 : B34_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC34_3 { } +public abstract record BC34_2 : BC34_3 { } +public abstract record BC34_1 : BC34_2 { } +public sealed partial record VC34 : BC34_1, IValue<global::Probes.Build.C11ParsedShape> { } + +// row 35 +public abstract record B35_2 { } +public abstract record B35_1 : B35_2 { } +public sealed partial record V35 : B35_1, IValue<int> { } + +// row 36 +public partial struct O36 +{ +public abstract record B36_2 { } +public abstract record B36_1 : B36_2 { } +public sealed partial record V36 : B36_1, IValue<global::Probes.Build.C11AbstractText> { } +public abstract record BC36_2 { } +public abstract record BC36_1 : BC36_2 { } +public sealed partial record VC36 : BC36_1, IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 37 +public static partial class O37 +{ +public sealed partial class P37 +{ +public sealed partial record V37 : IValue<char> { } +} +} + +// row 38 +public partial struct O38 +{ +public abstract record B38_2 { } +public abstract record B38_1 : B38_2 { } +public sealed partial record V38 : B38_1, IValidatedValue<V38, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V38, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V38(value); +} +public abstract record BC38_2 { } +public abstract record BC38_1 : BC38_2 { } +public sealed partial record VC38 : BC38_1, IValidatedValue<VC38, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC38, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC38(value); +} +} + +// row 39 +public partial struct O39 +{ +public abstract record B39_3 { } +public abstract record B39_2 : B39_3 { } +public abstract record B39_1 : B39_2 { } +public sealed partial record V39 : B39_1, IValue<global::Probes.Shapes.PEnum> { } +} + +// row 40 +public partial struct O40 +{ +public readonly partial record struct V40 : IValidatedValue<V40, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V40, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V40(value); +} +public readonly partial record struct VC40 : IValidatedValue<VC40, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC40, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC40(value); +} +} + +// row 41 +public partial struct O41 +{ +public abstract record B41_1 : System.IEquatable<B41_1> { bool System.IEquatable<B41_1>.Equals(B41_1? other) => ReferenceEquals(this, other); } +public sealed partial record V41 : B41_1, IValue<global::Probes.Shapes.PRec> { } +} + +// row 42 +public static partial class O42 +{ +public sealed partial class P42 +{ +public abstract record B42_1 : System.IEquatable<B42_1> { bool System.IEquatable<B42_1>.Equals(B42_1? other) => ReferenceEquals(this, other); } +public sealed partial record V42 : B42_1, IValue<global::System.Guid> { } +} +} + +// row 43 +public partial struct O43 +{ +public readonly partial record struct V43 : IValue<global::Probes.Build.C11AbstractText> { } +public readonly partial record struct VC43 : IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 44 +public static partial class O44 +{ +public sealed partial class P44 +{ +public readonly partial record struct V44 : IValue<global::Probes.Build.C11ParsesByRef> { } +public readonly partial record struct VC44 : IValue<global::Probes.Build.C11ParsesFromIn> { } +} +} + +// row 45 +public readonly partial record struct V45 : IValidatedValue<V45, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V45, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V45(value); +} +public readonly partial record struct VC45 : IValidatedValue<VC45, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC45, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC45(value); +} + +// row 46 +public static partial class O46 +{ +public readonly partial record struct V46 : IValidatedValue<V46, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V46, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V46(value); +} +} + +// row 47 +public static partial class O47 +{ +public readonly partial record struct V47 : IValidatedValue<V47, int, global::Probes.Shapes.ShapeFault> +{ + public static Result<V47, global::Probes.Shapes.ShapeFault> Create(int value) => new V47(value); +} +} + +// row 48 +public static partial class O48 +{ +public readonly partial record struct V48 : IValue<global::Probes.Build.C11AbstractText> { } +public readonly partial record struct VC48 : IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 49 +public static partial class O49 +{ +public abstract record B49_1 : System.IEquatable<B49_1> { bool System.IEquatable<B49_1>.Equals(B49_1? other) => ReferenceEquals(this, other); } +public sealed partial record V49 : B49_1, IValidatedValue<V49, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V49, global::Probes.Shapes.ShapeFault> Create(bool value) => new V49(value); +} +} + +// row 50 +public abstract record B50_1 : System.IEquatable<B50_1> { bool System.IEquatable<B50_1>.Equals(B50_1? other) => ReferenceEquals(this, other); } +public sealed partial record V50 : B50_1, IValue<global::Probes.Build.C11ParsesByRef> { } +public abstract record BC50_1 : System.IEquatable<BC50_1> { bool System.IEquatable<BC50_1>.Equals(BC50_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC50 : BC50_1, IValue<global::Probes.Build.C11ParsesFromIn> { } + +// row 51 +public static partial class O51 +{ +public abstract record B51_3 { } +public abstract record B51_2 : B51_3 { } +public abstract record B51_1 : B51_2 { } +public sealed partial record V51 : B51_1, IValidatedValue<V51, global::System.DateOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V51, global::Probes.Shapes.ShapeFault> Create(global::System.DateOnly value) => new V51(value); +} +} + +// row 52 +public partial struct O52 +{ +public sealed partial record V52 : IValue<ulong> { } +} + +// row 53 +public static partial class O53 +{ +public sealed partial class P53 +{ +public abstract record B53_3 { } +public abstract record B53_2 : B53_3 { } +public abstract record B53_1 : B53_2 { } +public sealed partial record V53 : B53_1, IValidatedValue<V53, string, global::Probes.Shapes.ShapeFault> +{ + public static Result<V53, global::Probes.Shapes.ShapeFault> Create(string value) => new V53(value); +} +} +} + +// row 54 +public static partial class O54 +{ +public sealed partial class P54 +{ +public abstract record B54_3 { } +public abstract record B54_2 : B54_3 { } +public abstract record B54_1 : B54_2 { } +public sealed partial record V54 : B54_1, IValidatedValue<V54, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault> +{ + public static Result<V54, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V54(value); +} +public abstract record BC54_3 { } +public abstract record BC54_2 : BC54_3 { } +public abstract record BC54_1 : BC54_2 { } +public sealed partial record VC54 : BC54_1, IValidatedValue<VC54, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC54, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC54(value); +} +} +} + +// row 55 +public static partial class O55 +{ +public sealed partial class P55 +{ +public abstract record B55_1 { } +public sealed partial record V55 : B55_1, IValue<global::System.TimeSpan> { } +} +} + +// row 56 +public static partial class O56 +{ +public sealed partial class P56 +{ +public abstract record B56_1 { } +public sealed partial record V56 : B56_1, IValidatedValue<V56, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V56, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V56(value); +} +} +} + +// row 57 +public abstract record B57_1 { } +public sealed partial record V57 : B57_1, IValidatedValue<V57, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V57, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V57(value); +} + +// row 58 +public readonly partial record struct V58 : IValue<byte> { } + +// row 59 +public static partial class O59 +{ +public sealed partial class P59 +{ +public abstract record B59_3 { } +public abstract record B59_2 : B59_3 { } +public abstract record B59_1 : B59_2 { } +public sealed partial record V59 : B59_1, IValidatedValue<V59, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V59, global::Probes.Shapes.ShapeFault> Create(double value) => new V59(value); +} +} +} + +// row 60 +public static partial class O60 +{ +public interface IMark60 { int Mark(); } +public sealed partial record V60 : IValue<global::System.TimeOnly>, IMark60 { int IMark60.Mark() => 1; } +} + +// row 61 +public partial struct O61 +{ +public abstract record B61_1 { } +public sealed partial record V61 : B61_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC61_1 { } +public sealed partial record VC61 : BC61_1, IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 62 +public static partial class O62 +{ +public sealed partial class P62 +{ +public readonly partial record struct V62 : IValidatedValue<V62, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V62, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V62(value); +} +} +} + +// row 63 +public interface IMark63 { int Mark(); } +public readonly partial record struct V63 : IValue<global::NodaTime.Instant>, IMark63 { int IMark63.Mark() => 1; } + +// row 64 +public static partial class O64 +{ +public readonly partial record struct V64 : IValidatedValue<V64, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V64, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V64(value); +} +} + +// row 65 +public abstract record B65_2 { } +public abstract record B65_1 : B65_2 { } +public sealed partial record V65 : B65_1, IValidatedValue<V65, global::System.Guid, global::Probes.Shapes.ShapeFault> +{ + public static Result<V65, global::Probes.Shapes.ShapeFault> Create(global::System.Guid value) => new V65(value); +} + +// row 66 +public abstract record B66_1 : System.IEquatable<B66_1> { bool System.IEquatable<B66_1>.Equals(B66_1? other) => ReferenceEquals(this, other); } +public sealed partial record V66 : B66_1, IValidatedValue<V66, global::Probes.Shapes.PRec, global::Probes.Shapes.ShapeFault> +{ + public static Result<V66, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PRec value) => new V66(value); +} + +// row 67 +public static partial class O67 +{ +public sealed partial class P67 +{ +public readonly partial record struct V67 : IValidatedValue<V67, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault> +{ + public static Result<V67, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V67(value); +} +public readonly partial record struct VC67 : IValidatedValue<VC67, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC67, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC67(value); +} +} +} + +// row 68 +public static partial class O68 +{ +public sealed partial class P68 +{ +public readonly partial record struct V68 : IValue<global::System.DateOnly> { } +} +} + +// row 69 +public partial struct O69 +{ +public interface IMark69 { int Mark(); } +public readonly partial record struct V69 : IValidatedValue<V69, long, global::Probes.Shapes.ShapeFault>, IMark69 +{ + public static Result<V69, global::Probes.Shapes.ShapeFault> Create(long value) => new V69(value); int IMark69.Mark() => 1; +} +} + +// row 70 +public static partial class O70 +{ +public sealed partial class P70 +{ +public interface IMark70 { int Mark(); } +public readonly partial record struct V70 : IValue<global::System.DateTimeOffset>, IMark70 { int IMark70.Mark() => 1; } +} +} + +// row 71 +public static partial class O71 +{ +public sealed partial class P71 +{ +public readonly partial record struct V71 : IValue<global::Probes.Shapes.PFlags> { } +} +} + +// row 72 +public partial struct O72 +{ +public readonly partial record struct V72 : IValue<double> { } +} + +// row 73 +public static partial class O73 +{ +public interface IMark73 { int Mark(); } +public readonly partial record struct V73 : IValidatedValue<V73, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault>, IMark73 +{ + public static Result<V73, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V73(value); int IMark73.Mark() => 1; +} +public readonly partial record struct VC73 : IValidatedValue<VC73, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault>, IMark73 +{ + public static Result<VC73, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC73(value); int IMark73.Mark() => 1; +} +} + +// row 74 +public static partial class O74 +{ +public readonly partial record struct V74 : IValue<char> { } +} + +// row 75 +public partial struct O75 +{ +public abstract record B75_1 { } +public sealed partial record V75 : B75_1, IValidatedValue<V75, int, global::Probes.Shapes.ShapeFault> +{ + public static Result<V75, global::Probes.Shapes.ShapeFault> Create(int value) => new V75(value); +} +} + +// row 76 +public static partial class O76 +{ +public interface IMark76 { int Mark(); } +public sealed partial record V76 : IValue<global::System.Uri>, IMark76 { int IMark76.Mark() => 1; } +} + +// row 77 +public readonly partial record struct V77 : IValue<global::Probes.Build.C11AbstractText> { } +public readonly partial record struct VC77 : IValue<global::Probes.Build.C11ConstructedConcrete> { } + +// row 78 +public static partial class O78 +{ +public sealed partial class P78 +{ +public readonly partial record struct V78 : IValue<global::Probes.Build.C11ParsesToInt> { } +public readonly partial record struct VC78 : IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 79 +public abstract record B79_1 : System.IEquatable<B79_1> { bool System.IEquatable<B79_1>.Equals(B79_1? other) => ReferenceEquals(this, other); } +public sealed partial record V79 : B79_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC79_1 : System.IEquatable<BC79_1> { bool System.IEquatable<BC79_1>.Equals(BC79_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC79 : BC79_1, IValue<global::Probes.Build.C11ParsedShape> { } + +// row 80 +public static partial class O80 +{ +public readonly partial record struct V80 : IValidatedValue<V80, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V80, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V80(value); +} +} + +// row 81 +public static partial class O81 +{ +public abstract record B81_1 : System.IEquatable<B81_1> { bool System.IEquatable<B81_1>.Equals(B81_1? other) => ReferenceEquals(this, other); } +public sealed partial record V81 : B81_1, IValue<global::Probes.Build.C11AbstractText> { } +public abstract record BC81_1 : System.IEquatable<BC81_1> { bool System.IEquatable<BC81_1>.Equals(BC81_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC81 : BC81_1, IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 82 +public static partial class O82 +{ +public sealed partial class P82 +{ +public interface IMark82 { int Mark(); } +public readonly partial record struct V82 : IValue<global::System.DateTime>, IMark82 { int IMark82.Mark() => 1; } +} +} + +// row 83 +public abstract record B83_2 { } +public abstract record B83_1 : B83_2 { } +public sealed partial record V83 : B83_1, IValidatedValue<V83, string, global::Probes.Shapes.ShapeFault> +{ + public static Result<V83, global::Probes.Shapes.ShapeFault> Create(string value) => new V83(value); +} + +// row 84 +public static partial class O84 +{ +public abstract record B84_2 { } +public abstract record B84_1 : B84_2 { } +public sealed partial record V84 : B84_1, IValidatedValue<V84, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V84, global::Probes.Shapes.ShapeFault> Create(long value) => new V84(value); +} +} + +// row 85 +public partial struct O85 +{ +public abstract record B85_1 : System.IEquatable<B85_1> { bool System.IEquatable<B85_1>.Equals(B85_1? other) => ReferenceEquals(this, other); } +public sealed partial record V85 : B85_1, IValue<global::System.TimeOnly> { } +} + +// row 86 +public static partial class O86 +{ +public sealed partial class P86 +{ +public abstract record B86_2 { } +public abstract record B86_1 : B86_2 { } +public sealed partial record V86 : B86_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC86_2 { } +public abstract record BC86_1 : BC86_2 { } +public sealed partial record VC86 : BC86_1, IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 87 +public static partial class O87 +{ +public sealed partial class P87 +{ +public sealed partial record V87 : IValue<int> { } +} +} + +// row 88 +public sealed partial record V88 : IValue<global::Probes.Build.C11AbstractText> { } +public sealed partial record VC88 : IValue<global::Probes.Build.C11ConstructedConcrete> { } + +// row 89 +public abstract record B89_2 { } +public abstract record B89_1 : B89_2 { } +public sealed partial record V89 : B89_1, IValidatedValue<V89, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V89, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V89(value); +} + +// row 90 +public readonly partial record struct V90 : IValue<global::NodaTime.Instant> { } + +// row 91 +public static partial class O91 +{ +public sealed partial class P91 +{ +public abstract record B91_1 { } +public sealed partial record V91 : B91_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC91_1 { } +public sealed partial record VC91 : BC91_1, IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 92 +public static partial class O92 +{ +public abstract record B92_2 { } +public abstract record B92_1 : B92_2 { } +public sealed partial record V92 : B92_1, IValidatedValue<V92, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V92, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V92(value); +} +} + +// row 93 +public abstract record B93_3 { } +public abstract record B93_2 : B93_3 { } +public abstract record B93_1 : B93_2 { } +public sealed partial record V93 : B93_1, IValue<decimal> { } + +// row 94 +public partial struct O94 +{ +public abstract record B94_2 { } +public abstract record B94_1 : B94_2 { } +public sealed partial record V94 : B94_1, IValue<global::System.Uri> { } +} + +// row 95 +public abstract record B95_3 { } +public abstract record B95_2 : B95_3 { } +public abstract record B95_1 : B95_2 { } +public sealed partial record V95 : B95_1, IValidatedValue<V95, char, global::Probes.Shapes.ShapeFault> +{ + public static Result<V95, global::Probes.Shapes.ShapeFault> Create(char value) => new V95(value); +} + +// row 96 +public partial struct O96 +{ +public abstract record B96_3 { } +public abstract record B96_2 : B96_3 { } +public abstract record B96_1 : B96_2 { } +public sealed partial record V96 : B96_1, IValidatedValue<V96, global::Probes.Shapes.PRec, global::Probes.Shapes.ShapeFault> +{ + public static Result<V96, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PRec value) => new V96(value); +} +} + +// row 97 +public abstract record B97_1 { } +public sealed partial record V97 : B97_1, IValidatedValue<V97, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V97, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V97(value); +} + +// row 98 +public abstract record B98_2 { } +public abstract record B98_1 : B98_2 { } +public sealed partial record V98 : B98_1, IValue<global::Probes.Shapes.PEnum> { } + +// row 99 +public interface IMark99 { int Mark(); } +public readonly partial record struct V99 : IValidatedValue<V99, string, global::Probes.Shapes.ShapeFault>, IMark99 +{ + public static Result<V99, global::Probes.Shapes.ShapeFault> Create(string value) => new V99(value); int IMark99.Mark() => 1; +} + +// row 100 +public abstract record B100_1 { } +public sealed partial record V100 : B100_1, IValidatedValue<V100, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V100, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V100(value); +} +public abstract record BC100_1 { } +public sealed partial record VC100 : BC100_1, IValidatedValue<VC100, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC100, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC100(value); +} + +// row 101 +public static partial class O101 +{ +public interface IMark101 { int Mark(); } +public readonly partial record struct V101 : IValidatedValue<V101, global::System.DateOnly, global::Probes.Shapes.ShapeFault>, IMark101 +{ + public static Result<V101, global::Probes.Shapes.ShapeFault> Create(global::System.DateOnly value) => new V101(value); int IMark101.Mark() => 1; +} +} + +// row 102 +public static partial class O102 +{ +public sealed partial class P102 +{ +public interface IMark102 { int Mark(); } +public sealed partial record V102 : IValue<bool>, IMark102 { int IMark102.Mark() => 1; } +} +} + +// row 103 +public readonly partial record struct V103 : IValidatedValue<V103, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V103, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V103(value); +} +public readonly partial record struct VC103 : IValidatedValue<VC103, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC103, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC103(value); +} + +// row 104 +public static partial class O104 +{ +public sealed partial class P104 +{ +public readonly partial record struct V104 : IValue<global::System.TimeSpan> { } +} +} + +// row 105 +public interface IMark105 { int Mark(); } +public readonly partial record struct V105 : IValue<global::Probes.Shapes.PRec>, IMark105 { int IMark105.Mark() => 1; } + +// row 106 +public static partial class O106 +{ +public sealed partial class P106 +{ +public abstract record B106_1 : System.IEquatable<B106_1> { bool System.IEquatable<B106_1>.Equals(B106_1? other) => ReferenceEquals(this, other); } +public sealed partial record V106 : B106_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC106_1 : System.IEquatable<BC106_1> { bool System.IEquatable<BC106_1>.Equals(BC106_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC106 : BC106_1, IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 107 +public static partial class O107 +{ +public sealed partial class P107 +{ +public readonly partial record struct V107 : IValue<global::Probes.Build.C11ParsesToInt> { } +public readonly partial record struct VC107 : IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 108 +public static partial class O108 +{ +public sealed partial class P108 +{ +public readonly partial record struct V108 : IValidatedValue<V108, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V108, global::Probes.Shapes.ShapeFault> Create(bool value) => new V108(value); +} +} +} + +// row 109 +public static partial class O109 +{ +public abstract record B109_2 { } +public abstract record B109_1 : B109_2 { } +public sealed partial record V109 : B109_1, IValidatedValue<V109, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V109, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V109(value); +} +public abstract record BC109_2 { } +public abstract record BC109_1 : BC109_2 { } +public sealed partial record VC109 : BC109_1, IValidatedValue<VC109, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC109, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC109(value); +} +} + +// row 110 +public static partial class O110 +{ +public sealed partial class P110 +{ +public abstract record B110_1 : System.IEquatable<B110_1> { bool System.IEquatable<B110_1>.Equals(B110_1? other) => ReferenceEquals(this, other); } +public sealed partial record V110 : B110_1, IValue<global::System.Uri> { } +} +} + +// row 111 +public static partial class O111 +{ +public readonly partial record struct V111 : IValue<global::Probes.Build.C11ParsesToInt> { } +public readonly partial record struct VC111 : IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 112 +public partial struct O112 +{ +public interface IMark112 { int Mark(); } +public readonly partial record struct V112 : IValidatedValue<V112, int, global::Probes.Shapes.ShapeFault>, IMark112 +{ + public static Result<V112, global::Probes.Shapes.ShapeFault> Create(int value) => new V112(value); int IMark112.Mark() => 1; +} +} + +// row 113 +public partial struct O113 +{ +public abstract record B113_3 { } +public abstract record B113_2 : B113_3 { } +public abstract record B113_1 : B113_2 { } +public sealed partial record V113 : B113_1, IValue<ulong> { } +} + +// row 114 +public partial struct O114 +{ +public abstract record B114_2 { } +public abstract record B114_1 : B114_2 { } +public sealed partial record V114 : B114_1, IValidatedValue<V114, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V114, global::Probes.Shapes.ShapeFault> Create(double value) => new V114(value); +} +} + +// row 115 +public static partial class O115 +{ +public readonly partial record struct V115 : IValidatedValue<V115, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V115, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V115(value); +} +} + +// row 116 +public static partial class O116 +{ +public abstract record B116_2 { } +public abstract record B116_1 : B116_2 { } +public sealed partial record V116 : B116_1, IValue<char> { } +} + +// row 117 +public static partial class O117 +{ +public abstract record B117_1 { } +public sealed partial record V117 : B117_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC117_1 { } +public sealed partial record VC117 : BC117_1, IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 118 +public partial struct O118 +{ +public abstract record B118_1 : System.IEquatable<B118_1> { bool System.IEquatable<B118_1>.Equals(B118_1? other) => ReferenceEquals(this, other); } +public sealed partial record V118 : B118_1, IValue<char> { } +} + +// row 119 +public static partial class O119 +{ +public sealed partial class P119 +{ +public abstract record B119_1 { } +public sealed partial record V119 : B119_1, IValue<global::System.Guid> { } +} +} + +// row 120 +public partial struct O120 +{ +public abstract record B120_2 { } +public abstract record B120_1 : B120_2 { } +public sealed partial record V120 : B120_1, IValidatedValue<V120, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V120, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V120(value); +} +} + +// row 121 +public abstract record B121_3 { } +public abstract record B121_2 : B121_3 { } +public abstract record B121_1 : B121_2 { } +public sealed partial record V121 : B121_1, IValue<global::Probes.Build.C11AbstractText> { } +public abstract record BC121_3 { } +public abstract record BC121_2 : BC121_3 { } +public abstract record BC121_1 : BC121_2 { } +public sealed partial record VC121 : BC121_1, IValue<global::Probes.Build.C11ConstructedConcrete> { } + +// row 122 +public static partial class O122 +{ +public readonly partial record struct V122 : IValue<long> { } +} + +// row 123 +public partial struct O123 +{ +public interface IMark123 { int Mark(); } +public sealed partial record V123 : IValue<global::Probes.Build.C11ConstructedByRef>, IMark123 { int IMark123.Mark() => 1; } +public sealed partial record VC123 : IValue<global::Probes.Build.C11ParsesFromIn>, IMark123 { int IMark123.Mark() => 1; } +} + +// row 124 +public partial struct O124 +{ +public readonly partial record struct V124 : IValidatedValue<V124, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V124, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V124(value); +} +public readonly partial record struct VC124 : IValidatedValue<VC124, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC124, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC124(value); +} +} + +// row 125 +public static partial class O125 +{ +public interface IMark125 { int Mark(); } +public readonly partial record struct V125 : IValidatedValue<V125, global::System.Uri, global::Probes.Shapes.ShapeFault>, IMark125 +{ + public static Result<V125, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V125(value); int IMark125.Mark() => 1; +} +} + +// row 126 +public static partial class O126 +{ +public sealed partial class P126 +{ +public abstract record B126_1 : System.IEquatable<B126_1> { bool System.IEquatable<B126_1>.Equals(B126_1? other) => ReferenceEquals(this, other); } +public sealed partial record V126 : B126_1, IValidatedValue<V126, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V126, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V126(value); +} +} +} + +// row 127 +public static partial class O127 +{ +public abstract record B127_1 : System.IEquatable<B127_1> { bool System.IEquatable<B127_1>.Equals(B127_1? other) => ReferenceEquals(this, other); } +public sealed partial record V127 : B127_1, IValue<global::NodaTime.Instant> { } +} + +// row 128 +public static partial class O128 +{ +public abstract record B128_1 : System.IEquatable<B128_1> { bool System.IEquatable<B128_1>.Equals(B128_1? other) => ReferenceEquals(this, other); } +public sealed partial record V128 : B128_1, IValidatedValue<V128, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V128, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V128(value); +} +} + +// row 129 +public readonly partial record struct V129 : IValidatedValue<V129, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V129, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V129(value); +} +public readonly partial record struct VC129 : IValidatedValue<VC129, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC129, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC129(value); +} + +// row 130 +public static partial class O130 +{ +public interface IMark130 { int Mark(); } +public readonly partial record struct V130 : IValue<char>, IMark130 { int IMark130.Mark() => 1; } +} + +// row 131 +public partial struct O131 +{ +public interface IMark131 { int Mark(); } +public readonly partial record struct V131 : IValue<long>, IMark131 { int IMark131.Mark() => 1; } +} + +// row 132 +public interface IMark132 { int Mark(); } +public readonly partial record struct V132 : IValidatedValue<V132, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault>, IMark132 +{ + public static Result<V132, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V132(value); int IMark132.Mark() => 1; +} +public readonly partial record struct VC132 : IValidatedValue<VC132, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault>, IMark132 +{ + public static Result<VC132, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC132(value); int IMark132.Mark() => 1; +} + +// row 133 +public static partial class O133 +{ +public abstract record B133_2 { } +public abstract record B133_1 : B133_2 { } +public sealed partial record V133 : B133_1, IValue<global::Probes.Shapes.PRec> { } +} + +// row 134 +public abstract record B134_2 { } +public abstract record B134_1 : B134_2 { } +public sealed partial record V134 : B134_1, IValidatedValue<V134, global::Probes.Build.C11ParsesByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V134, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesByRef value) => new V134(value); +} +public abstract record BC134_2 { } +public abstract record BC134_1 : BC134_2 { } +public sealed partial record VC134 : BC134_1, IValidatedValue<VC134, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC134, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC134(value); +} + +// row 135 +public partial struct O135 +{ +public abstract record B135_3 { } +public abstract record B135_2 : B135_3 { } +public abstract record B135_1 : B135_2 { } +public sealed partial record V135 : B135_1, IValue<global::System.TimeSpan> { } +} + +// row 136 +public partial struct O136 +{ +public interface IMark136 { int Mark(); } +public readonly partial record struct V136 : IValue<global::System.TimeOnly>, IMark136 { int IMark136.Mark() => 1; } +} + +// row 137 +public static partial class O137 +{ +public abstract record B137_1 { } +public sealed partial record V137 : B137_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC137_1 { } +public sealed partial record VC137 : BC137_1, IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 138 +public static partial class O138 +{ +public readonly partial record struct V138 : IValue<global::Probes.Build.C11AbstractText> { } +public readonly partial record struct VC138 : IValue<global::Probes.Build.C11ConstructedConcrete> { } +} + +// row 139 +public static partial class O139 +{ +public sealed partial class P139 +{ +public abstract record B139_1 : System.IEquatable<B139_1> { bool System.IEquatable<B139_1>.Equals(B139_1? other) => ReferenceEquals(this, other); } +public sealed partial record V139 : B139_1, IValue<global::Probes.Shapes.PFlags> { } +} +} + +// row 140 +public static partial class O140 +{ +public abstract record B140_2 { } +public abstract record B140_1 : B140_2 { } +public sealed partial record V140 : B140_1, IValue<bool> { } +} + +// row 141 +public static partial class O141 +{ +public interface IMark141 { int Mark(); } +public readonly partial record struct V141 : IValue<global::Probes.Build.C11AbstractText>, IMark141 { int IMark141.Mark() => 1; } +public readonly partial record struct VC141 : IValue<global::Probes.Build.C11ConstructedConcrete>, IMark141 { int IMark141.Mark() => 1; } +} + +// row 142 +public static partial class O142 +{ +public readonly partial record struct V142 : IValidatedValue<V142, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V142, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V142(value); +} +public readonly partial record struct VC142 : IValidatedValue<VC142, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC142, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC142(value); +} +} + +// row 143 +public sealed partial record V143 : IValue<global::Probes.Build.C11ConstructedByRef> { } +public sealed partial record VC143 : IValue<global::Probes.Build.C11ParsesFromIn> { } + +// row 144 +public static partial class O144 +{ +public sealed partial class P144 +{ +public interface IMark144 { int Mark(); } +public readonly partial record struct V144 : IValue<global::Probes.Build.C11AbstractText>, IMark144 { int IMark144.Mark() => 1; } +public readonly partial record struct VC144 : IValue<global::Probes.Build.C11ConstructedConcrete>, IMark144 { int IMark144.Mark() => 1; } +} +} + +// row 145 +public static partial class O145 +{ +public sealed partial class P145 +{ +public readonly partial record struct V145 : IValidatedValue<V145, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V145, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V145(value); +} +} +} + +// row 146 +public static partial class O146 +{ +public readonly partial record struct V146 : IValidatedValue<V146, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V146, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V146(value); +} +public readonly partial record struct VC146 : IValidatedValue<VC146, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC146, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC146(value); +} +} + +// row 147 +public static partial class O147 +{ +public sealed partial class P147 +{ +public interface IMark147 { int Mark(); } +public readonly partial record struct V147 : IValidatedValue<V147, global::Probes.Build.C11AbstractText, global::Probes.Shapes.ShapeFault>, IMark147 +{ + public static Result<V147, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11AbstractText value) => new V147(value); int IMark147.Mark() => 1; +} +public readonly partial record struct VC147 : IValidatedValue<VC147, global::Probes.Build.C11ConstructedConcrete, global::Probes.Shapes.ShapeFault>, IMark147 +{ + public static Result<VC147, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedConcrete value) => new VC147(value); int IMark147.Mark() => 1; +} +} +} + +// row 148 +public static partial class O148 +{ +public sealed partial record V148 : IValidatedValue<V148, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault> +{ + public static Result<V148, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V148(value); +} +} + +// row 149 +public partial struct O149 +{ +public interface IMark149 { int Mark(); } +public sealed partial record V149 : IValue<global::Probes.Build.C11ParsesToInt>, IMark149 { int IMark149.Mark() => 1; } +public sealed partial record VC149 : IValue<global::Probes.Build.C11ParsedShape>, IMark149 { int IMark149.Mark() => 1; } +} + +// row 150 +public static partial class O150 +{ +public sealed partial class P150 +{ +public abstract record B150_3 { } +public abstract record B150_2 : B150_3 { } +public abstract record B150_1 : B150_2 { } +public sealed partial record V150 : B150_1, IValidatedValue<V150, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V150, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V150(value); +} +} +} + +// row 151 +public static partial class O151 +{ +public sealed partial record V151 : IValidatedValue<V151, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V151, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V151(value); +} +} + +// row 152 +public static partial class O152 +{ +public interface IMark152 { int Mark(); } +public sealed partial record V152 : IValue<global::Probes.Build.C11AbstractText>, IMark152 { int IMark152.Mark() => 1; } +public sealed partial record VC152 : IValue<global::Probes.Build.C11ConstructedConcrete>, IMark152 { int IMark152.Mark() => 1; } +} + +// row 153 +public static partial class O153 +{ +public sealed partial record V153 : IValue<global::Probes.Build.C11ParsesToInt> { } +public sealed partial record VC153 : IValue<global::Probes.Build.C11ParsedShape> { } +} + +// row 154 +public static partial class O154 +{ +public sealed partial class P154 +{ +public abstract record B154_1 : System.IEquatable<B154_1> { bool System.IEquatable<B154_1>.Equals(B154_1? other) => ReferenceEquals(this, other); } +public sealed partial record V154 : B154_1, IValue<global::Probes.Build.C11ParsesToInt> { } +public abstract record BC154_1 : System.IEquatable<BC154_1> { bool System.IEquatable<BC154_1>.Equals(BC154_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC154 : BC154_1, IValue<global::Probes.Build.C11ParsedShape> { } +} +} + +// row 155 +public sealed partial record V155 : IValue<global::Probes.Build.C11ConstructedByRef> { } +public sealed partial record VC155 : IValue<global::Probes.Build.C11ParsesFromIn> { } + +// row 156 +public interface IMark156 { int Mark(); } +public readonly partial record struct V156 : IValidatedValue<V156, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault>, IMark156 +{ + public static Result<V156, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V156(value); int IMark156.Mark() => 1; +} + +// row 157 +public static partial class O157 +{ +public sealed partial class P157 +{ +public abstract record B157_1 : System.IEquatable<B157_1> { bool System.IEquatable<B157_1>.Equals(B157_1? other) => ReferenceEquals(this, other); } +public sealed partial record V157 : B157_1, IValidatedValue<V157, global::Probes.Build.C11ParsesToInt, global::Probes.Shapes.ShapeFault> +{ + public static Result<V157, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesToInt value) => new V157(value); +} +public abstract record BC157_1 : System.IEquatable<BC157_1> { bool System.IEquatable<BC157_1>.Equals(BC157_1? other) => ReferenceEquals(this, other); } +public sealed partial record VC157 : BC157_1, IValidatedValue<VC157, global::Probes.Build.C11ParsedShape, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC157, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsedShape value) => new VC157(value); +} +} +} + +// row 158 +public static partial class O158 +{ +public sealed partial class P158 +{ +public abstract record B158_3 { } +public abstract record B158_2 : B158_3 { } +public abstract record B158_1 : B158_2 { } +public sealed partial record V158 : B158_1, IValidatedValue<V158, string, global::Probes.Shapes.ShapeFault> +{ + public static Result<V158, global::Probes.Shapes.ShapeFault> Create(string value) => new V158(value); +} +} +} + +// row 159 +public static partial class O159 +{ +public sealed partial class P159 +{ +public abstract record B159_2 { } +public abstract record B159_1 : B159_2 { } +public sealed partial record V159 : B159_1, IValue<global::Probes.Shapes.PFlags> { } +} +} + +// row 160 +public partial struct O160 +{ +public interface IMark160 { int Mark(); } +public sealed partial record V160 : IValue<decimal>, IMark160 { int IMark160.Mark() => 1; } +} + +// row 161 +public static partial class O161 +{ +public readonly partial record struct V161 : IValue<global::System.Guid> { } +} + +// row 162 +public partial struct O162 +{ +public abstract record B162_3 { } +public abstract record B162_2 : B162_3 { } +public abstract record B162_1 : B162_2 { } +public sealed partial record V162 : B162_1, IValue<global::System.DateTime> { } +} + +// row 163 +public partial struct O163 +{ +public abstract record B163_1 : System.IEquatable<B163_1> { bool System.IEquatable<B163_1>.Equals(B163_1? other) => ReferenceEquals(this, other); } +public sealed partial record V163 : B163_1, IValue<global::System.DateTimeOffset> { } +} + +// row 164 +public partial struct O164 +{ +public interface IMark164 { int Mark(); } +public readonly partial record struct V164 : IValidatedValue<V164, byte, global::Probes.Shapes.ShapeFault>, IMark164 +{ + public static Result<V164, global::Probes.Shapes.ShapeFault> Create(byte value) => new V164(value); int IMark164.Mark() => 1; +} +} + +// row 165 +public abstract record B165_1 : System.IEquatable<B165_1> { bool System.IEquatable<B165_1>.Equals(B165_1? other) => ReferenceEquals(this, other); } +public sealed partial record V165 : B165_1, IValue<global::System.DateOnly> { } + +// row 166 +public static partial class O166 +{ +public readonly partial record struct V166 : IValue<decimal> { } +} + +// row 167 +public static partial class O167 +{ +public readonly partial record struct V167 : IValue<string> { } +} + +// row 168 +public static partial class O168 +{ +public readonly partial record struct V168 : IValue<ulong> { } +} + +// row 169 +public partial struct O169 +{ +public interface IMark169 { int Mark(); } +public readonly partial record struct V169 : IValue<bool>, IMark169 { int IMark169.Mark() => 1; } +} + +// row 170 +public static partial class O170 +{ +public sealed partial class P170 +{ +public readonly partial record struct V170 : IValidatedValue<V170, global::Probes.Shapes.PRec, global::Probes.Shapes.ShapeFault> +{ + public static Result<V170, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PRec value) => new V170(value); +} +} +} + +// row 171 +public static partial class O171 +{ +public sealed partial record V171 : IValidatedValue<V171, byte, global::Probes.Shapes.ShapeFault> +{ + public static Result<V171, global::Probes.Shapes.ShapeFault> Create(byte value) => new V171(value); +} +} + +// row 172 +public static partial class O172 +{ +public sealed partial class P172 +{ +public abstract record B172_2 { } +public abstract record B172_1 : B172_2 { } +public sealed partial record V172 : B172_1, IValue<global::System.TimeOnly> { } +} +} + +// row 173 +public abstract record B173_3 { } +public abstract record B173_2 : B173_3 { } +public abstract record B173_1 : B173_2 { } +public sealed partial record V173 : B173_1, IValidatedValue<V173, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V173, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V173(value); +} + +// row 174 +public static partial class O174 +{ +public abstract record B174_3 { } +public abstract record B174_2 : B174_3 { } +public abstract record B174_1 : B174_2 { } +public sealed partial record V174 : B174_1, IValidatedValue<V174, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V174, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V174(value); +} +} + +// row 175 +public partial struct O175 +{ +public readonly partial record struct V175 : IValidatedValue<V175, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V175, global::Probes.Shapes.ShapeFault> Create(double value) => new V175(value); +} +} + +// row 176 +public partial struct O176 +{ +public abstract record B176_1 : System.IEquatable<B176_1> { bool System.IEquatable<B176_1>.Equals(B176_1? other) => ReferenceEquals(this, other); } +public sealed partial record V176 : B176_1, IValue<string> { } +} + +// row 177 +public static partial class O177 +{ +public abstract record B177_3 { } +public abstract record B177_2 : B177_3 { } +public abstract record B177_1 : B177_2 { } +public sealed partial record V177 : B177_1, IValidatedValue<V177, global::Probes.Build.C11ConstructedByRef, global::Probes.Shapes.ShapeFault> +{ + public static Result<V177, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ConstructedByRef value) => new V177(value); +} +public abstract record BC177_3 { } +public abstract record BC177_2 : BC177_3 { } +public abstract record BC177_1 : BC177_2 { } +public sealed partial record VC177 : BC177_1, IValidatedValue<VC177, global::Probes.Build.C11ParsesFromIn, global::Probes.Shapes.ShapeFault> +{ + public static Result<VC177, global::Probes.Shapes.ShapeFault> Create(global::Probes.Build.C11ParsesFromIn value) => new VC177(value); +} +} + +// row 178 +public partial struct O178 +{ +public interface IMark178 { int Mark(); } +public readonly partial record struct V178 : IValidatedValue<V178, global::System.Guid, global::Probes.Shapes.ShapeFault>, IMark178 +{ + public static Result<V178, global::Probes.Shapes.ShapeFault> Create(global::System.Guid value) => new V178(value); int IMark178.Mark() => 1; +} +} + +// row 179 +public static partial class O179 +{ +public readonly partial record struct V179 : IValidatedValue<V179, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V179, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V179(value); +} +} + +// row 180 +public static partial class O180 +{ +public sealed partial class P180 +{ +public interface IMark180 { int Mark(); } +public readonly partial record struct V180 : IValue<global::Probes.Shapes.PEnum>, IMark180 { int IMark180.Mark() => 1; } +} +} + +// row 181 +public interface IMark181 { int Mark(); } +public readonly partial record struct V181 : IValidatedValue<V181, global::System.TimeSpan, global::Probes.Shapes.ShapeFault>, IMark181 +{ + public static Result<V181, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V181(value); int IMark181.Mark() => 1; +} + +// row 182 +public static partial class O182 +{ +public readonly partial record struct V182 : IValidatedValue<V182, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V182, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V182(value); +} +} + +// row 183 +public partial struct O183 +{ +public abstract record B183_3 { } +public abstract record B183_2 : B183_3 { } +public abstract record B183_1 : B183_2 { } +public sealed partial record V183 : B183_1, IValidatedValue<V183, global::Probes.Shapes.PFlags, global::Probes.Shapes.ShapeFault> +{ + public static Result<V183, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PFlags value) => new V183(value); +} +} + +// row 184 +public abstract record B184_1 { } +public sealed partial record V184 : B184_1, IValidatedValue<V184, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V184, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V184(value); +} + +// row 185 +public static partial class O185 +{ +public sealed partial class P185 +{ +public readonly partial record struct V185 : IValue<bool> { } +} +} + +// row 186 +public abstract record B186_3 { } +public abstract record B186_2 : B186_3 { } +public abstract record B186_1 : B186_2 { } +public sealed partial record V186 : B186_1, IValue<long> { } + +// row 187 +public static partial class O187 +{ +public abstract record B187_1 { } +public sealed partial record V187 : B187_1, IValidatedValue<V187, global::System.DateOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V187, global::Probes.Shapes.ShapeFault> Create(global::System.DateOnly value) => new V187(value); +} +} + +// row 188 +public static partial class O188 +{ +public sealed partial class P188 +{ +public interface IMark188 { int Mark(); } +public readonly partial record struct V188 : IValue<int>, IMark188 { int IMark188.Mark() => 1; } +} +} + +// row 189 +public static partial class O189 +{ +public sealed partial class P189 +{ +public interface IMark189 { int Mark(); } +public sealed partial record V189 : IValidatedValue<V189, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault>, IMark189 +{ + public static Result<V189, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V189(value); int IMark189.Mark() => 1; +} +} +} + +// row 190 +public partial struct O190 +{ +public abstract record B190_3 { } +public abstract record B190_2 : B190_3 { } +public abstract record B190_1 : B190_2 { } +public sealed partial record V190 : B190_1, IValue<global::System.Guid> { } +} + +// row 191 +public static partial class O191 +{ +public sealed partial class P191 +{ +public interface IMark191 { int Mark(); } +public sealed partial record V191 : IValue<string>, IMark191 { int IMark191.Mark() => 1; } +} +} + +// row 192 +public abstract record B192_1 : System.IEquatable<B192_1> { bool System.IEquatable<B192_1>.Equals(B192_1? other) => ReferenceEquals(this, other); } +public sealed partial record V192 : B192_1, IValidatedValue<V192, ulong, global::Probes.Shapes.ShapeFault> +{ + public static Result<V192, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V192(value); +} + +// row 193 +public abstract record B193_2 { } +public abstract record B193_1 : B193_2 { } +public sealed partial record V193 : B193_1, IValue<global::System.DateOnly> { } + +// row 194 +public static partial class O194 +{ +public abstract record B194_2 { } +public abstract record B194_1 : B194_2 { } +public sealed partial record V194 : B194_1, IValidatedValue<V194, byte, global::Probes.Shapes.ShapeFault> +{ + public static Result<V194, global::Probes.Shapes.ShapeFault> Create(byte value) => new V194(value); +} +} + +// row 195 +public interface IMark195 { int Mark(); } +public sealed partial record V195 : IValue<global::Probes.Shapes.PFlags>, IMark195 { int IMark195.Mark() => 1; } + +// row 196 +public abstract record B196_1 : System.IEquatable<B196_1> { bool System.IEquatable<B196_1>.Equals(B196_1? other) => ReferenceEquals(this, other); } +public sealed partial record V196 : B196_1, IValidatedValue<V196, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V196, global::Probes.Shapes.ShapeFault> Create(double value) => new V196(value); +} + +// row 197 +public static partial class O197 +{ +public abstract record B197_2 { } +public abstract record B197_1 : B197_2 { } +public sealed partial record V197 : B197_1, IValue<global::System.DateTime> { } +} + +// row 198 +public partial struct O198 +{ +public readonly partial record struct V198 : IValidatedValue<V198, byte, global::Probes.Shapes.ShapeFault> +{ + public static Result<V198, global::Probes.Shapes.ShapeFault> Create(byte value) => new V198(value); +} +} + +// row 199 +public static partial class O199 +{ +public sealed partial class P199 +{ +public interface IMark199 { int Mark(); } +public readonly partial record struct V199 : IValidatedValue<V199, char, global::Probes.Shapes.ShapeFault>, IMark199 +{ + public static Result<V199, global::Probes.Shapes.ShapeFault> Create(char value) => new V199(value); int IMark199.Mark() => 1; +} +} +} + +// row 200 +public partial struct O200 +{ +public abstract record B200_1 : System.IEquatable<B200_1> { bool System.IEquatable<B200_1>.Equals(B200_1? other) => ReferenceEquals(this, other); } +public sealed partial record V200 : B200_1, IValue<global::Probes.Shapes.PEnum> { } +} + +// row 201 +public partial struct O201 +{ +public abstract record B201_3 { } +public abstract record B201_2 : B201_3 { } +public abstract record B201_1 : B201_2 { } +public sealed partial record V201 : B201_1, IValue<global::System.Guid> { } +} + +// row 202 +public partial struct O202 +{ +public abstract record B202_2 { } +public abstract record B202_1 : B202_2 { } +public sealed partial record V202 : B202_1, IValue<global::System.DateTimeOffset> { } +} + +// row 203 +public static partial class O203 +{ +public sealed partial class P203 +{ +public abstract record B203_1 { } +public sealed partial record V203 : B203_1, IValue<char> { } +} +} + +// row 204 +public partial struct O204 +{ +public abstract record B204_1 { } +public sealed partial record V204 : B204_1, IValidatedValue<V204, global::System.TimeOnly, global::Probes.Shapes.ShapeFault> +{ + public static Result<V204, global::Probes.Shapes.ShapeFault> Create(global::System.TimeOnly value) => new V204(value); +} +} + +// row 205 +public partial struct O205 +{ +public abstract record B205_1 { } +public sealed partial record V205 : B205_1, IValue<string> { } +} + +// row 206 +public static partial class O206 +{ +public sealed partial class P206 +{ +public abstract record B206_1 { } +public sealed partial record V206 : B206_1, IValidatedValue<V206, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V206, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V206(value); +} +} +} + +// row 207 +public abstract record B207_1 { } +public sealed partial record V207 : B207_1, IValue<global::System.DateTimeOffset> { } + +// row 208 +public abstract record B208_2 { } +public abstract record B208_1 : B208_2 { } +public sealed partial record V208 : B208_1, IValidatedValue<V208, long, global::Probes.Shapes.ShapeFault> +{ + public static Result<V208, global::Probes.Shapes.ShapeFault> Create(long value) => new V208(value); +} + +// row 209 +public static partial class O209 +{ +public sealed partial class P209 +{ +public interface IMark209 { int Mark(); } +public sealed partial record V209 : IValidatedValue<V209, ulong, global::Probes.Shapes.ShapeFault>, IMark209 +{ + public static Result<V209, global::Probes.Shapes.ShapeFault> Create(ulong value) => new V209(value); int IMark209.Mark() => 1; +} +} +} + +// row 210 +public static partial class O210 +{ +public abstract record B210_1 { } +public sealed partial record V210 : B210_1, IValidatedValue<V210, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V210, global::Probes.Shapes.ShapeFault> Create(bool value) => new V210(value); +} +} + +// row 211 +public abstract record B211_1 : System.IEquatable<B211_1> { bool System.IEquatable<B211_1>.Equals(B211_1? other) => ReferenceEquals(this, other); } +public sealed partial record V211 : B211_1, IValidatedValue<V211, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V211, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V211(value); +} + +// row 212 +public static partial class O212 +{ +public abstract record B212_3 { } +public abstract record B212_2 : B212_3 { } +public abstract record B212_1 : B212_2 { } +public sealed partial record V212 : B212_1, IValidatedValue<V212, int, global::Probes.Shapes.ShapeFault> +{ + public static Result<V212, global::Probes.Shapes.ShapeFault> Create(int value) => new V212(value); +} +} + +// row 213 +public static partial class O213 +{ +public abstract record B213_1 : System.IEquatable<B213_1> { bool System.IEquatable<B213_1>.Equals(B213_1? other) => ReferenceEquals(this, other); } +public sealed partial record V213 : B213_1, IValue<int> { } +} + +// row 214 +public abstract record B214_1 { } +public sealed partial record V214 : B214_1, IValidatedValue<V214, decimal, global::Probes.Shapes.ShapeFault> +{ + public static Result<V214, global::Probes.Shapes.ShapeFault> Create(decimal value) => new V214(value); +} + +// row 215 +public static partial class O215 +{ +public sealed partial class P215 +{ +public abstract record B215_1 : System.IEquatable<B215_1> { bool System.IEquatable<B215_1>.Equals(B215_1? other) => ReferenceEquals(this, other); } +public sealed partial record V215 : B215_1, IValidatedValue<V215, byte, global::Probes.Shapes.ShapeFault> +{ + public static Result<V215, global::Probes.Shapes.ShapeFault> Create(byte value) => new V215(value); +} +} +} + +// row 216 +public static partial class O216 +{ +public sealed partial class P216 +{ +public abstract record B216_1 { } +public sealed partial record V216 : B216_1, IValidatedValue<V216, global::Probes.Shapes.PRec, global::Probes.Shapes.ShapeFault> +{ + public static Result<V216, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PRec value) => new V216(value); +} +} +} + +// row 217 +public partial struct O217 +{ +public readonly partial record struct V217 : IValidatedValue<V217, int, global::Probes.Shapes.ShapeFault> +{ + public static Result<V217, global::Probes.Shapes.ShapeFault> Create(int value) => new V217(value); +} +} + +// row 218 +public static partial class O218 +{ +public abstract record B218_1 : System.IEquatable<B218_1> { bool System.IEquatable<B218_1>.Equals(B218_1? other) => ReferenceEquals(this, other); } +public sealed partial record V218 : B218_1, IValue<string> { } +} + +// row 219 +public static partial class O219 +{ +public sealed partial class P219 +{ +public abstract record B219_2 { } +public abstract record B219_1 : B219_2 { } +public sealed partial record V219 : B219_1, IValidatedValue<V219, global::System.Guid, global::Probes.Shapes.ShapeFault> +{ + public static Result<V219, global::Probes.Shapes.ShapeFault> Create(global::System.Guid value) => new V219(value); +} +} +} + +// row 220 +public static partial class O220 +{ +public sealed partial class P220 +{ +public abstract record B220_1 { } +public sealed partial record V220 : B220_1, IValue<global::Probes.Shapes.PFlags> { } +} +} + +// row 221 +public partial struct O221 +{ +public sealed partial record V221 : IValue<byte> { } +} + +// row 222 +public partial struct O222 +{ +public abstract record B222_1 { } +public sealed partial record V222 : B222_1, IValue<global::System.DateTime> { } +} + +// row 223 +public sealed partial record V223 : IValue<global::Probes.Shapes.PRec> { } + +// row 224 +public partial struct O224 +{ +public abstract record B224_1 { } +public sealed partial record V224 : B224_1, IValidatedValue<V224, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V224, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V224(value); +} +} + +// row 225 +public static partial class O225 +{ +public sealed partial class P225 +{ +public readonly partial record struct V225 : IValue<global::System.DateOnly> { } +} +} + +// row 226 +public static partial class O226 +{ +public sealed partial class P226 +{ +public readonly partial record struct V226 : IValidatedValue<V226, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V226, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V226(value); +} +} +} + +// row 227 +public static partial class O227 +{ +public abstract record B227_3 { } +public abstract record B227_2 : B227_3 { } +public abstract record B227_1 : B227_2 { } +public sealed partial record V227 : B227_1, IValue<global::System.TimeOnly> { } +} + +// row 228 +public static partial class O228 +{ +public readonly partial record struct V228 : IValidatedValue<V228, global::System.TimeSpan, global::Probes.Shapes.ShapeFault> +{ + public static Result<V228, global::Probes.Shapes.ShapeFault> Create(global::System.TimeSpan value) => new V228(value); +} +} + +// row 229 +public static partial class O229 +{ +public sealed partial class P229 +{ +public abstract record B229_1 : System.IEquatable<B229_1> { bool System.IEquatable<B229_1>.Equals(B229_1? other) => ReferenceEquals(this, other); } +public sealed partial record V229 : B229_1, IValidatedValue<V229, global::System.DateTime, global::Probes.Shapes.ShapeFault> +{ + public static Result<V229, global::Probes.Shapes.ShapeFault> Create(global::System.DateTime value) => new V229(value); +} +} +} + +// row 230 +public partial struct O230 +{ +public abstract record B230_1 : System.IEquatable<B230_1> { bool System.IEquatable<B230_1>.Equals(B230_1? other) => ReferenceEquals(this, other); } +public sealed partial record V230 : B230_1, IValidatedValue<V230, double, global::Probes.Shapes.ShapeFault> +{ + public static Result<V230, global::Probes.Shapes.ShapeFault> Create(double value) => new V230(value); +} +} + +// row 231 +public static partial class O231 +{ +public interface IMark231 { int Mark(); } +public readonly partial record struct V231 : IValidatedValue<V231, global::System.Uri, global::Probes.Shapes.ShapeFault>, IMark231 +{ + public static Result<V231, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V231(value); int IMark231.Mark() => 1; +} +} + +// row 232 +public partial struct O232 +{ +public interface IMark232 { int Mark(); } +public readonly partial record struct V232 : IValue<double>, IMark232 { int IMark232.Mark() => 1; } +} + +// row 233 +public static partial class O233 +{ +public sealed partial class P233 +{ +public readonly partial record struct V233 : IValue<global::System.DateOnly> { } +} +} + +// row 234 +public readonly partial record struct V234 : IValue<global::Probes.Shapes.PEnum> { } + +// row 235 +public abstract record B235_3 { } +public abstract record B235_2 : B235_3 { } +public abstract record B235_1 : B235_2 { } +public sealed partial record V235 : B235_1, IValidatedValue<V235, global::System.DateTimeOffset, global::Probes.Shapes.ShapeFault> +{ + public static Result<V235, global::Probes.Shapes.ShapeFault> Create(global::System.DateTimeOffset value) => new V235(value); +} + +// row 236 +public abstract record B236_3 { } +public abstract record B236_2 : B236_3 { } +public abstract record B236_1 : B236_2 { } +public sealed partial record V236 : B236_1, IValidatedValue<V236, bool, global::Probes.Shapes.ShapeFault> +{ + public static Result<V236, global::Probes.Shapes.ShapeFault> Create(bool value) => new V236(value); +} + +// row 237 +public static partial class O237 +{ +public abstract record B237_2 { } +public abstract record B237_1 : B237_2 { } +public sealed partial record V237 : B237_1, IValue<decimal> { } +} + +// row 238 +public static partial class O238 +{ +public sealed partial class P238 +{ +public abstract record B238_1 { } +public sealed partial record V238 : B238_1, IValue<ulong> { } +} +} + +// row 239 +public abstract record B239_1 { } +public sealed partial record V239 : B239_1, IValue<double> { } + +// row 240 +public abstract record B240_1 { } +public sealed partial record V240 : B240_1, IValue<byte> { } + +// row 241 +public static partial class O241 +{ +public sealed partial class P241 +{ +public interface IMark241 { int Mark(); } +public sealed partial record V241 : IValidatedValue<V241, global::Probes.Shapes.PEnum, global::Probes.Shapes.ShapeFault>, IMark241 +{ + public static Result<V241, global::Probes.Shapes.ShapeFault> Create(global::Probes.Shapes.PEnum value) => new V241(value); int IMark241.Mark() => 1; +} +} +} + +// row 242 +public partial struct O242 +{ +public abstract record B242_3 { } +public abstract record B242_2 : B242_3 { } +public abstract record B242_1 : B242_2 { } +public sealed partial record V242 : B242_1, IValue<global::Probes.Shapes.PFlags> { } +} + +// row 243 +public readonly partial record struct V243 : IValue<global::System.Uri> { } + +// row 244 +public static partial class O244 +{ +public sealed partial class P244 +{ +public readonly partial record struct V244 : IValidatedValue<V244, global::NodaTime.Instant, global::Probes.Shapes.ShapeFault> +{ + public static Result<V244, global::Probes.Shapes.ShapeFault> Create(global::NodaTime.Instant value) => new V244(value); +} +} +} + +// row 245 +public partial struct O245 +{ +public interface IMark245 { int Mark(); } +public readonly partial record struct V245 : IValue<global::System.Uri>, IMark245 { int IMark245.Mark() => 1; } +} + +// row 246 +public static partial class O246 +{ +public sealed partial class P246 +{ +public abstract record B246_3 { } +public abstract record B246_2 : B246_3 { } +public abstract record B246_1 : B246_2 { } +public sealed partial record V246 : B246_1, IValidatedValue<V246, global::System.Uri, global::Probes.Shapes.ShapeFault> +{ + public static Result<V246, global::Probes.Shapes.ShapeFault> Create(global::System.Uri value) => new V246(value); +} +} +} + +public static partial class C11Table +{ + public static readonly C11Case[] Cases = + [ + new(0, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=double;text.culture=invariant;text.entry=TryFrom", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O0.V0), typeof(double), null, null, []), + new(1, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=TimeSpan;text.culture=de-DE;text.entry=ISpanParsable", "validated-class", "top-level", "none", typeof(V1), typeof(global::System.TimeSpan), null, null, []), + new(2, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=decimal;text.culture=tr-TR;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O2.V2), typeof(global::Probes.Build.C11ParsesToInt), typeof(O2.VC2), typeof(global::Probes.Build.C11ParsedShape), []), + new(3, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeOnly;text.culture=tr-TR;text.entry=Parse", "validated-struct", "nested-two-levels", "none", typeof(O3.P3.V3), typeof(global::Probes.Build.C11AbstractText), typeof(O3.P3.VC3), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(4, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=decimal;text.culture=ar-SA;text.entry=ToString-format", "record-struct", "top-level", "explicit-interface-member", typeof(V4), typeof(global::Probes.Build.C11ParsesByRef), typeof(VC4), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(5, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=char;text.culture=globalization-invariant;text.entry=IParsable", "validated-class", "nested-in-struct", "base-depth-1", typeof(O5.V5), typeof(global::Probes.Build.C11AbstractText), typeof(O5.VC5), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(6, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=string;text.culture=globalization-invariant;text.entry=TryParse", "record-struct", "nested-in-class", "none", typeof(O6.V6), typeof(global::Probes.Build.C11ParsesToInt), typeof(O6.VC6), typeof(global::Probes.Build.C11ParsedShape), []), + new(7, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=NodaTime.Instant;text.culture=invariant;text.entry=type-converter", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O7.P7.V7), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O7.P7.VC7), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(8, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=long;text.culture=ar-SA;text.entry=IParsable", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O8.P8.V8), typeof(long), null, null, []), + new(9, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=NodaTime.Instant;text.culture=de-DE;text.entry=TryParse", "validated-class", "nested-in-struct", "base-depth-3", typeof(O9.V9), typeof(global::NodaTime.Instant), null, null, []), + new(10, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=ulong;text.culture=invariant;text.entry=TryFormat-short-span", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V10), typeof(global::Probes.Build.C11ParsesToInt), typeof(VC10), typeof(global::Probes.Build.C11ParsedShape), []), + new(11, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=Guid;text.culture=de-DE;text.entry=Parse", "validated-struct", "nested-in-class", "explicit-interface-member", typeof(O11.V11), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O11.VC11), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(12, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=bool;text.culture=globalization-invariant;text.entry=type-converter", "sealed-record-class", "top-level", "base-depth-2", typeof(V12), typeof(bool), null, null, []), + new(13, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTime;text.culture=tr-TR;text.entry=ToString-format", "validated-class", "nested-in-class", "base-depth-3", typeof(O13.V13), typeof(global::Probes.Build.C11AbstractText), typeof(O13.VC13), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(14, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateOnly;text.culture=ar-SA;text.entry=ISpanParsable", "validated-struct", "nested-in-struct", "none", typeof(O14.V14), typeof(global::System.DateOnly), null, null, [("shape.inheritance", "none")]), + new(15, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=int;text.culture=invariant;text.entry=TryFrom", "record-struct", "nested-in-struct", "none", typeof(O15.V15), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O15.VC15), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(16, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum;text.culture=globalization-invariant;text.entry=TryFormat-short-span", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O16.P16.V16), typeof(global::Probes.Build.C11AbstractText), typeof(O16.P16.VC16), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(17, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=Uri;text.culture=tr-TR;text.entry=Parse", "record-struct", "top-level", "none", typeof(V17), typeof(global::System.Uri), null, null, [("shape.inheritance", "none")]), + new(18, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=TimeOnly;text.culture=ar-SA;text.entry=TryFrom", "validated-class", "nested-two-levels", "base-depth-2", typeof(O18.P18.V18), typeof(global::Probes.Build.C11ParsesToInt), typeof(O18.P18.VC18), typeof(global::Probes.Build.C11ParsedShape), []), + new(19, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=string;text.culture=de-DE;text.entry=ISpanParsable", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O19.V19), typeof(global::Probes.Build.C11AbstractText), typeof(O19.VC19), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(20, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeSpan;text.culture=tr-TR;text.entry=IParsable", "validated-struct", "nested-in-struct", "explicit-interface-member", typeof(O20.V20), typeof(global::Probes.Build.C11ParsesByRef), typeof(O20.VC20), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(21, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=DateTimeOffset;text.culture=ar-SA;text.entry=TryFormat-short-span", "validated-struct", "nested-in-class", "none", typeof(O21.V21), typeof(global::System.DateTimeOffset), null, null, []), + new(22, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=ulong;text.culture=de-DE;text.entry=ISpanParsable", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O22.P22.V22), typeof(global::Probes.Build.C11ParsesToInt), typeof(O22.P22.VC22), typeof(global::Probes.Build.C11ParsedShape), []), + new(23, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=byte;text.culture=invariant;text.entry=ToString-format", "sealed-record-class", "nested-two-levels", "base-depth-3", typeof(O23.P23.V23), typeof(byte), null, null, []), + new(24, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=own-record-class;text.culture=de-DE;text.entry=ToString-format", "validated-struct", "nested-in-class", "none", typeof(O24.V24), typeof(global::Probes.Build.C11ParsesToInt), typeof(O24.VC24), typeof(global::Probes.Build.C11ParsedShape), [("shape.inheritance", "none")]), + new(25, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=char;text.culture=ar-SA;text.entry=TryParse", "validated-struct", "top-level", "explicit-interface-member", typeof(V25), typeof(global::Probes.Build.C11AbstractText), typeof(VC25), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(26, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=byte;text.culture=globalization-invariant;text.entry=Parse", "validated-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O26.V26), typeof(global::Probes.Build.C11ParsesByRef), typeof(O26.VC26), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(27, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=ulong;text.culture=ar-SA;text.entry=type-converter", "validated-struct", "nested-in-class", "none", typeof(O27.V27), typeof(global::Probes.Build.C11ParsesToInt), typeof(O27.VC27), typeof(global::Probes.Build.C11ParsedShape), [("shape.inheritance", "none")]), + new(28, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=TimeOnly;text.culture=globalization-invariant;text.entry=TryFrom", "validated-struct", "top-level", "none", typeof(V28), typeof(global::Probes.Build.C11ParsesByRef), typeof(VC28), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.inheritance", "none")]), + new(29, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=flags-enum;text.culture=de-DE;text.entry=type-converter", "validated-class", "nested-in-struct", "explicit-interface-member", typeof(O29.V29), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O29.VC29), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(30, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=DateOnly;text.culture=invariant;text.entry=TryParse", "sealed-record-class", "top-level", "base-depth-2", typeof(V30), typeof(global::Probes.Build.C11ParsesByRef), typeof(VC30), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(31, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=decimal;text.culture=invariant;text.entry=IParsable", "validated-struct", "nested-in-class", "none", typeof(O31.V31), typeof(global::Probes.Build.C11ParsesToInt), typeof(O31.VC31), typeof(global::Probes.Build.C11ParsedShape), []), + new(32, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Guid;text.culture=globalization-invariant;text.entry=ToString-format", "sealed-record-class", "nested-in-struct", "none", typeof(O32.V32), typeof(global::Probes.Build.C11AbstractText), typeof(O32.VC32), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(33, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=long;text.culture=de-DE;text.entry=TryFrom", "validated-class", "nested-in-class", "base-depth-1", typeof(O33.V33), typeof(long), null, null, []), + new(34, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=flags-enum;text.culture=globalization-invariant;text.entry=Parse", "sealed-record-class", "top-level", "base-depth-3", typeof(V34), typeof(global::Probes.Build.C11ParsesToInt), typeof(VC34), typeof(global::Probes.Build.C11ParsedShape), []), + new(35, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=int;text.culture=globalization-invariant;text.entry=IParsable", "sealed-record-class", "top-level", "base-depth-2", typeof(V35), typeof(int), null, null, []), + new(36, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTime;text.culture=invariant;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O36.V36), typeof(global::Probes.Build.C11AbstractText), typeof(O36.VC36), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(37, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=char;text.culture=tr-TR;text.entry=type-converter", "sealed-record-class", "nested-two-levels", "none", typeof(O37.P37.V37), typeof(char), null, null, []), + new(38, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset;text.culture=globalization-invariant;text.entry=ISpanParsable", "validated-class", "nested-in-struct", "base-depth-2", typeof(O38.V38), typeof(global::Probes.Build.C11ParsesByRef), typeof(O38.VC38), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(39, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=enum;text.culture=ar-SA;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O39.V39), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(40, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Uri;text.culture=invariant;text.entry=ISpanParsable", "validated-struct", "nested-in-struct", "none", typeof(O40.V40), typeof(global::Probes.Build.C11ParsesToInt), typeof(O40.VC40), typeof(global::Probes.Build.C11ParsedShape), [("shape.inheritance", "none")]), + new(41, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=own-record-class;text.culture=tr-TR;text.entry=ISpanParsable", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O41.V41), typeof(global::Probes.Shapes.PRec), null, null, []), + new(42, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Guid;text.culture=tr-TR;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O42.P42.V42), typeof(global::System.Guid), null, null, []), + new(43, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=bool;text.culture=de-DE;text.entry=IParsable", "record-struct", "nested-in-struct", "none", typeof(O43.V43), typeof(global::Probes.Build.C11AbstractText), typeof(O43.VC43), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.inheritance", "none")]), + new(44, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeOnly;text.culture=de-DE;text.entry=TryFormat-short-span", "record-struct", "nested-two-levels", "none", typeof(O44.P44.V44), typeof(global::Probes.Build.C11ParsesByRef), typeof(O44.P44.VC44), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.inheritance", "none")]), + new(45, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=double;text.culture=ar-SA;text.entry=TryFormat-short-span", "validated-struct", "top-level", "none", typeof(V45), typeof(global::Probes.Build.C11ConstructedByRef), typeof(VC45), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(46, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=flags-enum;text.culture=invariant;text.entry=ToString-format", "validated-struct", "nested-in-class", "none", typeof(O46.V46), typeof(global::Probes.Shapes.PFlags), null, null, [("shape.inheritance", "none")]), + new(47, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=int;text.culture=de-DE;text.entry=type-converter", "validated-struct", "nested-in-class", "none", typeof(O47.V47), typeof(int), null, null, [("shape.inheritance", "none")]), + new(48, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=Guid;text.culture=invariant;text.entry=type-converter", "record-struct", "nested-in-class", "none", typeof(O48.V48), typeof(global::Probes.Build.C11AbstractText), typeof(O48.VC48), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.site", "nested-in-class"), ("shape.inheritance", "none")]), + new(49, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=bool;text.culture=tr-TR;text.entry=TryFrom", "validated-class", "nested-in-class", "base-explicit-IEquatable", typeof(O49.V49), typeof(bool), null, null, []), + new(50, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=DateTimeOffset;text.culture=invariant;text.entry=type-converter", "sealed-record-class", "top-level", "base-explicit-IEquatable", typeof(V50), typeof(global::Probes.Build.C11ParsesByRef), typeof(VC50), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(51, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateOnly;text.culture=globalization-invariant;text.entry=IParsable", "validated-class", "nested-in-class", "base-depth-3", typeof(O51.V51), typeof(global::System.DateOnly), null, null, []), + new(52, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=ulong;text.culture=tr-TR;text.entry=TryFrom", "sealed-record-class", "nested-in-struct", "none", typeof(O52.V52), typeof(ulong), null, null, []), + new(53, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=string;text.culture=invariant;text.entry=TryFormat-short-span", "validated-class", "nested-two-levels", "base-depth-3", typeof(O53.P53.V53), typeof(string), null, null, []), + new(54, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri;text.culture=ar-SA;text.entry=TryFrom", "validated-class", "nested-two-levels", "base-depth-3", typeof(O54.P54.V54), typeof(global::Probes.Build.C11AbstractText), typeof(O54.P54.VC54), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(55, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=TimeSpan;text.culture=ar-SA;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O55.P55.V55), typeof(global::System.TimeSpan), null, null, []), + new(56, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=decimal;text.culture=globalization-invariant;text.entry=TryParse", "validated-class", "nested-two-levels", "base-depth-1", typeof(O56.P56.V56), typeof(decimal), null, null, []), + new(57, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=enum;text.culture=de-DE;text.entry=ToString-format", "validated-class", "top-level", "base-depth-1", typeof(V57), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(58, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=byte;text.culture=tr-TR;text.entry=TryParse", "record-struct", "top-level", "none", typeof(V58), typeof(byte), null, null, []), + new(59, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=double;text.culture=tr-TR;text.entry=TryParse", "validated-class", "nested-two-levels", "base-depth-3", typeof(O59.P59.V59), typeof(double), null, null, []), + new(60, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeOnly;text.culture=invariant;text.entry=ToString-format", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O60.V60), typeof(global::System.TimeOnly), null, null, []), + new(61, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=string;text.culture=tr-TR;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-depth-1", typeof(O61.V61), typeof(global::Probes.Build.C11ParsesToInt), typeof(O61.VC61), typeof(global::Probes.Build.C11ParsedShape), []), + new(62, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTime;text.culture=ar-SA;text.entry=ISpanParsable", "validated-struct", "nested-two-levels", "none", typeof(O62.P62.V62), typeof(global::System.DateTime), null, null, []), + new(63, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=NodaTime.Instant;text.culture=tr-TR;text.entry=TryFrom", "record-struct", "top-level", "explicit-interface-member", typeof(V63), typeof(global::NodaTime.Instant), null, null, []), + new(64, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=enum;text.culture=tr-TR;text.entry=type-converter", "validated-struct", "nested-in-class", "none", typeof(O64.V64), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(65, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=Guid;text.culture=ar-SA;text.entry=TryFormat-short-span", "validated-class", "top-level", "base-depth-2", typeof(V65), typeof(global::System.Guid), null, null, []), + new(66, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=own-record-class;text.culture=ar-SA;text.entry=IParsable", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V66), typeof(global::Probes.Shapes.PRec), null, null, []), + new(67, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=bool;text.culture=ar-SA;text.entry=ISpanParsable", "validated-struct", "nested-two-levels", "none", typeof(O67.P67.V67), typeof(global::Probes.Build.C11AbstractText), typeof(O67.P67.VC67), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.inheritance", "none")]), + new(68, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=DateOnly;text.culture=tr-TR;text.entry=ToString-format", "record-struct", "nested-two-levels", "none", typeof(O68.P68.V68), typeof(global::System.DateOnly), null, null, [("shape.inheritance", "none")]), + new(69, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=long;text.culture=invariant;text.entry=TryParse", "validated-struct", "nested-in-struct", "explicit-interface-member", typeof(O69.V69), typeof(long), null, null, []), + new(70, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTimeOffset;text.culture=tr-TR;text.entry=Parse", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O70.P70.V70), typeof(global::System.DateTimeOffset), null, null, []), + new(71, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=flags-enum;text.culture=ar-SA;text.entry=TryFormat-short-span", "record-struct", "nested-two-levels", "none", typeof(O71.P71.V71), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(72, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=double;text.culture=globalization-invariant;text.entry=ToString-format", "record-struct", "nested-in-struct", "none", typeof(O72.V72), typeof(double), null, null, [("shape.inheritance", "none")]), + new(73, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=byte;text.culture=de-DE;text.entry=ISpanParsable", "validated-struct", "nested-in-class", "explicit-interface-member", typeof(O73.V73), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O73.VC73), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(74, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=char;text.culture=de-DE;text.entry=TryFormat-short-span", "record-struct", "nested-in-class", "none", typeof(O74.V74), typeof(char), null, null, []), + new(75, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=int;text.culture=ar-SA;text.entry=TryFormat-short-span", "validated-class", "nested-in-struct", "base-depth-1", typeof(O75.V75), typeof(int), null, null, []), + new(76, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=Uri;text.culture=de-DE;text.entry=TryParse", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O76.V76), typeof(global::System.Uri), null, null, []), + new(77, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=long;text.culture=globalization-invariant;text.entry=ToString-format", "record-struct", "top-level", "none", typeof(V77), typeof(global::Probes.Build.C11AbstractText), typeof(VC77), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(78, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=own-record-class;text.culture=invariant;text.entry=TryParse", "record-struct", "nested-two-levels", "none", typeof(O78.P78.V78), typeof(global::Probes.Build.C11ParsesToInt), typeof(O78.P78.VC78), typeof(global::Probes.Build.C11ParsedShape), []), + new(79, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=DateTime;text.culture=globalization-invariant;text.entry=TryFrom", "sealed-record-class", "top-level", "base-explicit-IEquatable", typeof(V79), typeof(global::Probes.Build.C11ParsesToInt), typeof(VC79), typeof(global::Probes.Build.C11ParsedShape), []), + new(80, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=NodaTime.Instant;text.culture=globalization-invariant;text.entry=IParsable", "validated-struct", "nested-in-class", "none", typeof(O80.V80), typeof(global::NodaTime.Instant), null, null, [("shape.inheritance", "none")]), + new(81, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=closed-set;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeSpan;text.culture=ar-SA;text.entry=Parse", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O81.V81), typeof(global::Probes.Build.C11AbstractText), typeof(O81.VC81), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.kind", "sealed-record-class")]), + new(82, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTime;text.culture=de-DE;text.entry=IParsable", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O82.P82.V82), typeof(global::System.DateTime), null, null, []), + new(83, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=string;text.culture=ar-SA;text.entry=ToString-format", "validated-class", "top-level", "base-depth-2", typeof(V83), typeof(string), null, null, []), + new(84, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=long;text.culture=tr-TR;text.entry=ISpanParsable", "validated-class", "nested-in-class", "base-depth-2", typeof(O84.V84), typeof(long), null, null, []), + new(85, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeOnly;text.culture=invariant;text.entry=type-converter", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O85.V85), typeof(global::System.TimeOnly), null, null, []), + new(86, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=byte;text.culture=ar-SA;text.entry=TryFrom", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O86.P86.V86), typeof(global::Probes.Build.C11ParsesToInt), typeof(O86.P86.VC86), typeof(global::Probes.Build.C11ParsedShape), []), + new(87, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=int;text.culture=tr-TR;text.entry=ToString-format", "sealed-record-class", "nested-two-levels", "none", typeof(O87.P87.V87), typeof(int), null, null, []), + new(88, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=DateOnly;text.culture=de-DE;text.entry=type-converter", "sealed-record-class", "top-level", "none", typeof(V88), typeof(global::Probes.Build.C11AbstractText), typeof(VC88), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(89, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=ulong;text.culture=globalization-invariant;text.entry=TryParse", "validated-class", "top-level", "base-depth-2", typeof(V89), typeof(ulong), null, null, []), + new(90, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=NodaTime.Instant;text.culture=ar-SA;text.entry=TryFormat-short-span", "record-struct", "top-level", "none", typeof(V90), typeof(global::NodaTime.Instant), null, null, []), + new(91, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTimeOffset;text.culture=de-DE;text.entry=ToString-format", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O91.P91.V91), typeof(global::Probes.Build.C11ParsesToInt), typeof(O91.P91.VC91), typeof(global::Probes.Build.C11ParsedShape), [("shape.kind", "sealed-record-class")]), + new(92, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=TimeSpan;text.culture=invariant;text.entry=type-converter", "validated-class", "nested-in-class", "base-depth-2", typeof(O92.V92), typeof(global::System.TimeSpan), null, null, [("shape.site", "nested-in-class")]), + new(93, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=decimal;text.culture=de-DE;text.entry=ISpanParsable", "sealed-record-class", "top-level", "base-depth-3", typeof(V93), typeof(decimal), null, null, []), + new(94, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Uri;text.culture=globalization-invariant;text.entry=IParsable", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O94.V94), typeof(global::System.Uri), null, null, []), + new(95, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=char;text.culture=invariant;text.entry=TryFrom", "validated-class", "top-level", "base-depth-3", typeof(V95), typeof(char), null, null, []), + new(96, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=own-record-class;text.culture=globalization-invariant;text.entry=type-converter", "validated-class", "nested-in-struct", "base-depth-3", typeof(O96.V96), typeof(global::Probes.Shapes.PRec), null, null, []), + new(97, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=flags-enum;text.culture=tr-TR;text.entry=TryFrom", "validated-class", "top-level", "base-depth-1", typeof(V97), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(98, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=enum;text.culture=invariant;text.entry=TryParse", "sealed-record-class", "top-level", "base-depth-2", typeof(V98), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(99, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=string;text.culture=invariant;text.entry=TryFrom", "validated-struct", "top-level", "explicit-interface-member", typeof(V99), typeof(string), null, null, []), + new(100, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=double;text.culture=de-DE;text.entry=ISpanParsable", "validated-class", "top-level", "base-depth-1", typeof(V100), typeof(global::Probes.Build.C11ParsesToInt), typeof(VC100), typeof(global::Probes.Build.C11ParsedShape), []), + new(101, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=in-generic-type;shape.wrapped=DateOnly;text.culture=tr-TR;text.entry=Parse", "validated-struct", "nested-in-class", "explicit-interface-member", typeof(O101.V101), typeof(global::System.DateOnly), null, null, [("shape.site", "nested-in-class")]), + new(102, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=bool;text.culture=invariant;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "explicit-interface-member", typeof(O102.P102.V102), typeof(bool), null, null, []), + new(103, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=bool;text.culture=ar-SA;text.entry=ToString-format", "validated-struct", "top-level", "none", typeof(V103), typeof(global::Probes.Build.C11ParsesToInt), typeof(VC103), typeof(global::Probes.Build.C11ParsedShape), [("shape.inheritance", "none")]), + new(104, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeSpan;text.culture=globalization-invariant;text.entry=TryFormat-short-span", "record-struct", "nested-two-levels", "none", typeof(O104.P104.V104), typeof(global::System.TimeSpan), null, null, []), + new(105, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=own-record-class;text.culture=de-DE;text.entry=TryFormat-short-span", "record-struct", "top-level", "explicit-interface-member", typeof(V105), typeof(global::Probes.Shapes.PRec), null, null, []), + new(106, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum;text.culture=globalization-invariant;text.entry=ISpanParsable", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O106.P106.V106), typeof(global::Probes.Build.C11ParsesToInt), typeof(O106.P106.VC106), typeof(global::Probes.Build.C11ParsedShape), []), + new(107, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri;text.culture=tr-TR;text.entry=ToString-format", "record-struct", "nested-two-levels", "none", typeof(O107.P107.V107), typeof(global::Probes.Build.C11ParsesToInt), typeof(O107.P107.VC107), typeof(global::Probes.Build.C11ParsedShape), []), + new(108, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=bool;text.culture=tr-TR;text.entry=TryFormat-short-span", "validated-struct", "nested-two-levels", "none", typeof(O108.P108.V108), typeof(bool), null, null, []), + new(109, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=flags-enum;text.culture=ar-SA;text.entry=ISpanParsable", "validated-class", "nested-in-class", "base-depth-2", typeof(O109.V109), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O109.VC109), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(110, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri;text.culture=ar-SA;text.entry=type-converter", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O110.P110.V110), typeof(global::System.Uri), null, null, []), + new(111, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=int;text.culture=de-DE;text.entry=ISpanParsable", "record-struct", "nested-in-class", "none", typeof(O111.V111), typeof(global::Probes.Build.C11ParsesToInt), typeof(O111.VC111), typeof(global::Probes.Build.C11ParsedShape), [("shape.inheritance", "none")]), + new(112, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=int;text.culture=ar-SA;text.entry=TryParse", "validated-struct", "nested-in-struct", "explicit-interface-member", typeof(O112.V112), typeof(int), null, null, []), + new(113, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=ulong;text.culture=globalization-invariant;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O113.V113), typeof(ulong), null, null, []), + new(114, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=double;text.culture=de-DE;text.entry=IParsable", "validated-class", "nested-in-struct", "base-depth-2", typeof(O114.V114), typeof(double), null, null, []), + new(115, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTime;text.culture=tr-TR;text.entry=TryParse", "validated-struct", "nested-in-class", "none", typeof(O115.V115), typeof(global::System.DateTime), null, null, [("shape.inheritance", "none")]), + new(116, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=closed-set;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=in-generic-type;shape.wrapped=char;text.culture=globalization-invariant;text.entry=ISpanParsable", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O116.V116), typeof(char), null, null, [("shape.kind", "sealed-record-class"), ("shape.site", "nested-in-class")]), + new(117, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=byte;text.culture=tr-TR;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-class", "base-depth-1", typeof(O117.V117), typeof(global::Probes.Build.C11ParsesToInt), typeof(O117.VC117), typeof(global::Probes.Build.C11ParsedShape), []), + new(118, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=char;text.culture=ar-SA;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O118.V118), typeof(char), null, null, []), + new(119, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Guid;text.culture=tr-TR;text.entry=IParsable", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O119.P119.V119), typeof(global::System.Guid), null, null, []), + new(120, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=NodaTime.Instant;text.culture=invariant;text.entry=ToString-format", "validated-class", "nested-in-struct", "base-depth-2", typeof(O120.V120), typeof(global::NodaTime.Instant), null, null, []), + new(121, "parse-shape=abstract-string-ctor;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=closed-set;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=TimeSpan;text.culture=invariant;text.entry=TryFrom", "sealed-record-class", "top-level", "base-depth-3", typeof(V121), typeof(global::Probes.Build.C11AbstractText), typeof(VC121), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.kind", "sealed-record-class")]), + new(122, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=in-generic-type;shape.wrapped=long;text.culture=globalization-invariant;text.entry=type-converter", "record-struct", "nested-in-class", "none", typeof(O122.V122), typeof(long), null, null, [("shape.site", "nested-in-class"), ("shape.inheritance", "none")]), + new(123, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateOnly;text.culture=de-DE;text.entry=TryFrom", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O123.V123), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O123.VC123), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.kind", "sealed-record-class")]), + new(124, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset;text.culture=tr-TR;text.entry=TryFrom", "validated-struct", "nested-in-struct", "none", typeof(O124.V124), typeof(global::Probes.Build.C11ParsesByRef), typeof(O124.VC124), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.inheritance", "none")]), + new(125, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=Uri;text.culture=tr-TR;text.entry=TryFormat-short-span", "validated-struct", "nested-in-class", "explicit-interface-member", typeof(O125.V125), typeof(global::System.Uri), null, null, []), + new(126, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=decimal;text.culture=de-DE;text.entry=Parse", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O126.P126.V126), typeof(decimal), null, null, []), + new(127, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=closed-set;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=NodaTime.Instant;text.culture=ar-SA;text.entry=Parse", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O127.V127), typeof(global::NodaTime.Instant), null, null, [("shape.kind", "sealed-record-class")]), + new(128, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=DateTime;text.culture=de-DE;text.entry=type-converter", "validated-class", "nested-in-class", "base-explicit-IEquatable", typeof(O128.V128), typeof(global::System.DateTime), null, null, []), + new(129, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=own-record-class;text.culture=ar-SA;text.entry=TryFrom", "validated-struct", "top-level", "none", typeof(V129), typeof(global::Probes.Build.C11ConstructedByRef), typeof(VC129), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.inheritance", "none")]), + new(130, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=char;text.culture=globalization-invariant;text.entry=ToString-format", "record-struct", "nested-in-class", "explicit-interface-member", typeof(O130.V130), typeof(char), null, null, []), + new(131, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=long;text.culture=globalization-invariant;text.entry=TryFormat-short-span", "record-struct", "nested-in-struct", "explicit-interface-member", typeof(O131.V131), typeof(long), null, null, []), + new(132, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=double;text.culture=invariant;text.entry=Parse", "validated-struct", "top-level", "explicit-interface-member", typeof(V132), typeof(global::Probes.Build.C11AbstractText), typeof(VC132), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(133, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=own-record-class;text.culture=de-DE;text.entry=Parse", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O133.V133), typeof(global::Probes.Shapes.PRec), null, null, []), + new(134, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=NodaTime.Instant;text.culture=invariant;text.entry=ISpanParsable", "validated-class", "top-level", "base-depth-2", typeof(V134), typeof(global::Probes.Build.C11ParsesByRef), typeof(VC134), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(135, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeSpan;text.culture=globalization-invariant;text.entry=ToString-format", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O135.V135), typeof(global::System.TimeSpan), null, null, [("shape.kind", "sealed-record-class")]), + new(136, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=TimeOnly;text.culture=de-DE;text.entry=TryParse", "record-struct", "nested-in-struct", "explicit-interface-member", typeof(O136.V136), typeof(global::System.TimeOnly), null, null, []), + new(137, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=byte;text.culture=ar-SA;text.entry=IParsable", "sealed-record-class", "nested-in-class", "base-depth-1", typeof(O137.V137), typeof(global::Probes.Build.C11ParsesToInt), typeof(O137.VC137), typeof(global::Probes.Build.C11ParsedShape), []), + new(138, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeOnly;text.culture=invariant;text.entry=ISpanParsable", "record-struct", "nested-in-class", "none", typeof(O138.V138), typeof(global::Probes.Build.C11AbstractText), typeof(O138.VC138), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(139, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=flags-enum;text.culture=de-DE;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O139.P139.V139), typeof(global::Probes.Shapes.PFlags), null, null, [("shape.kind", "sealed-record-class")]), + new(140, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=bool;text.culture=globalization-invariant;text.entry=Parse", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O140.V140), typeof(bool), null, null, []), + new(141, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=DateTimeOffset;text.culture=de-DE;text.entry=IParsable", "record-struct", "nested-in-class", "explicit-interface-member", typeof(O141.V141), typeof(global::Probes.Build.C11AbstractText), typeof(O141.VC141), typeof(global::Probes.Build.C11ConstructedConcrete), [("shape.site", "nested-in-class")]), + new(142, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=in-generic-type;shape.wrapped=TimeOnly;text.culture=invariant;text.entry=IParsable", "validated-struct", "nested-in-class", "none", typeof(O142.V142), typeof(global::Probes.Build.C11ParsesToInt), typeof(O142.VC142), typeof(global::Probes.Build.C11ParsedShape), [("shape.site", "nested-in-class")]), + new(143, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=none;shape.kind=closed-set;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=byte;text.culture=tr-TR;text.entry=type-converter", "sealed-record-class", "top-level", "none", typeof(V143), typeof(global::Probes.Build.C11ConstructedByRef), typeof(VC143), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.kind", "sealed-record-class")]), + new(144, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum;text.culture=de-DE;text.entry=TryFrom", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O144.P144.V144), typeof(global::Probes.Build.C11AbstractText), typeof(O144.P144.VC144), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(145, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=ulong;text.culture=de-DE;text.entry=ToString-format", "validated-struct", "nested-two-levels", "none", typeof(O145.P145.V145), typeof(ulong), null, null, []), + new(146, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=int;text.culture=de-DE;text.entry=Parse", "validated-struct", "nested-in-class", "none", typeof(O146.V146), typeof(global::Probes.Build.C11ParsesToInt), typeof(O146.VC146), typeof(global::Probes.Build.C11ParsedShape), []), + new(147, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=Guid;text.culture=ar-SA;text.entry=ISpanParsable", "validated-struct", "nested-two-levels", "explicit-interface-member", typeof(O147.P147.V147), typeof(global::Probes.Build.C11AbstractText), typeof(O147.P147.VC147), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(148, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=in-generic-type;shape.wrapped=enum;text.culture=globalization-invariant;text.entry=IParsable", "validated-class", "nested-in-class", "none", typeof(O148.V148), typeof(global::Probes.Shapes.PEnum), null, null, [("shape.site", "nested-in-class")]), + new(149, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTime;text.culture=invariant;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O149.V149), typeof(global::Probes.Build.C11ParsesToInt), typeof(O149.VC149), typeof(global::Probes.Build.C11ParsedShape), []), + new(150, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=decimal;text.culture=de-DE;text.entry=TryFrom", "validated-class", "nested-two-levels", "base-depth-3", typeof(O150.P150.V150), typeof(decimal), null, null, []), + new(151, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=ulong;text.culture=invariant;text.entry=IParsable", "validated-class", "nested-in-class", "none", typeof(O151.V151), typeof(ulong), null, null, []), + new(152, "parse-shape=abstract-string-ctor;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=double;text.culture=ar-SA;text.entry=type-converter", "sealed-record-class", "nested-in-class", "explicit-interface-member", typeof(O152.V152), typeof(global::Probes.Build.C11AbstractText), typeof(O152.VC152), typeof(global::Probes.Build.C11ConstructedConcrete), []), + new(153, "parse-shape=implicit-result;process.converter-lookup=none;shape.inheritance=none;shape.kind=closed-set;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=in-generic-type;shape.wrapped=Guid;text.culture=ar-SA;text.entry=TryFrom", "sealed-record-class", "nested-in-class", "none", typeof(O153.V153), typeof(global::Probes.Build.C11ParsesToInt), typeof(O153.VC153), typeof(global::Probes.Build.C11ParsedShape), [("shape.kind", "sealed-record-class"), ("shape.site", "nested-in-class")]), + new(154, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=long;text.culture=invariant;text.entry=Parse", "sealed-record-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O154.P154.V154), typeof(global::Probes.Build.C11ParsesToInt), typeof(O154.P154.VC154), typeof(global::Probes.Build.C11ParsedShape), []), + new(155, "parse-shape=ref-text;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=closed-set;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=string;text.culture=globalization-invariant;text.entry=type-converter", "sealed-record-class", "top-level", "none", typeof(V155), typeof(global::Probes.Build.C11ConstructedByRef), typeof(VC155), typeof(global::Probes.Build.C11ParsesFromIn), [("shape.kind", "sealed-record-class")]), + new(156, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=DateTimeOffset;text.culture=de-DE;text.entry=TryParse", "validated-struct", "top-level", "explicit-interface-member", typeof(V156), typeof(global::System.DateTimeOffset), null, null, []), + new(157, "parse-shape=implicit-result;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateOnly;text.culture=globalization-invariant;text.entry=TryFormat-short-span", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O157.P157.V157), typeof(global::Probes.Build.C11ParsesToInt), typeof(O157.P157.VC157), typeof(global::Probes.Build.C11ParsedShape), []), + new(158, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=string;text.culture=invariant;text.entry=IParsable", "validated-class", "nested-two-levels", "base-depth-3", typeof(O158.P158.V158), typeof(string), null, null, []), + new(159, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=flags-enum;text.culture=globalization-invariant;text.entry=IParsable", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O159.P159.V159), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(160, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=decimal;text.culture=invariant;text.entry=type-converter", "sealed-record-class", "nested-in-struct", "explicit-interface-member", typeof(O160.V160), typeof(decimal), null, null, []), + new(161, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=Guid;text.culture=invariant;text.entry=type-converter", "record-struct", "nested-in-class", "none", typeof(O161.V161), typeof(global::System.Guid), null, null, []), + new(162, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTime;text.culture=invariant;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O162.V162), typeof(global::System.DateTime), null, null, []), + new(163, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset;text.culture=invariant;text.entry=ToString-format", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O163.V163), typeof(global::System.DateTimeOffset), null, null, []), + new(164, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=byte;text.culture=ar-SA;text.entry=IParsable", "validated-struct", "nested-in-struct", "explicit-interface-member", typeof(O164.V164), typeof(byte), null, null, []), + new(165, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=DateOnly;text.culture=de-DE;text.entry=ToString-format", "sealed-record-class", "top-level", "base-explicit-IEquatable", typeof(V165), typeof(global::System.DateOnly), null, null, []), + new(166, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=decimal;text.culture=ar-SA;text.entry=ToString-format", "record-struct", "nested-in-class", "none", typeof(O166.V166), typeof(decimal), null, null, []), + new(167, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=string;text.culture=de-DE;text.entry=Parse", "record-struct", "nested-in-class", "none", typeof(O167.V167), typeof(string), null, null, []), + new(168, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=ulong;text.culture=ar-SA;text.entry=TryFormat-short-span", "record-struct", "nested-in-class", "none", typeof(O168.V168), typeof(ulong), null, null, [("shape.inheritance", "none")]), + new(169, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=bool;text.culture=de-DE;text.entry=ISpanParsable", "record-struct", "nested-in-struct", "explicit-interface-member", typeof(O169.V169), typeof(bool), null, null, []), + new(170, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=own-record-class;text.culture=invariant;text.entry=ToString-format", "validated-struct", "nested-two-levels", "none", typeof(O170.P170.V170), typeof(global::Probes.Shapes.PRec), null, null, []), + new(171, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=byte;text.culture=globalization-invariant;text.entry=ISpanParsable", "validated-class", "nested-in-class", "none", typeof(O171.V171), typeof(byte), null, null, []), + new(172, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=TimeOnly;text.culture=ar-SA;text.entry=Parse", "sealed-record-class", "nested-two-levels", "base-depth-2", typeof(O172.P172.V172), typeof(global::System.TimeOnly), null, null, []), + new(173, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=TimeOnly;text.culture=tr-TR;text.entry=TryFormat-short-span", "validated-class", "top-level", "base-depth-3", typeof(V173), typeof(global::System.TimeOnly), null, null, []), + new(174, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=Uri;text.culture=invariant;text.entry=ISpanParsable", "validated-class", "nested-in-class", "base-depth-3", typeof(O174.V174), typeof(global::System.Uri), null, null, []), + new(175, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=double;text.culture=ar-SA;text.entry=ISpanParsable", "validated-struct", "nested-in-struct", "none", typeof(O175.V175), typeof(double), null, null, [("shape.inheritance", "none")]), + new(176, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=string;text.culture=tr-TR;text.entry=ToString-format", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O176.V176), typeof(string), null, null, []), + new(177, "parse-shape=ref-text;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTimeOffset;text.culture=globalization-invariant;text.entry=TryFrom", "validated-class", "nested-in-class", "base-depth-3", typeof(O177.V177), typeof(global::Probes.Build.C11ConstructedByRef), typeof(O177.VC177), typeof(global::Probes.Build.C11ParsesFromIn), []), + new(178, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=Guid;text.culture=tr-TR;text.entry=ToString-format", "validated-struct", "nested-in-struct", "explicit-interface-member", typeof(O178.V178), typeof(global::System.Guid), null, null, []), + new(179, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeOnly;text.culture=globalization-invariant;text.entry=TryFrom", "validated-struct", "nested-in-class", "none", typeof(O179.V179), typeof(global::System.TimeOnly), null, null, []), + new(180, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum;text.culture=globalization-invariant;text.entry=ISpanParsable", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O180.P180.V180), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(181, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=TimeSpan;text.culture=tr-TR;text.entry=IParsable", "validated-struct", "top-level", "explicit-interface-member", typeof(V181), typeof(global::System.TimeSpan), null, null, []), + new(182, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=decimal;text.culture=tr-TR;text.entry=TryFormat-short-span", "validated-struct", "nested-in-class", "none", typeof(O182.V182), typeof(decimal), null, null, [("shape.inheritance", "none")]), + new(183, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=flags-enum;text.culture=invariant;text.entry=ISpanParsable", "validated-class", "nested-in-struct", "base-depth-3", typeof(O183.V183), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(184, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=DateTime;text.culture=globalization-invariant;text.entry=type-converter", "validated-class", "top-level", "base-depth-1", typeof(V184), typeof(global::System.DateTime), null, null, []), + new(185, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=bool;text.culture=ar-SA;text.entry=IParsable", "record-struct", "nested-two-levels", "none", typeof(O185.P185.V185), typeof(bool), null, null, [("shape.inheritance", "none")]), + new(186, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=long;text.culture=globalization-invariant;text.entry=ToString-format", "sealed-record-class", "top-level", "base-depth-3", typeof(V186), typeof(long), null, null, []), + new(187, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=in-generic-type;shape.wrapped=DateOnly;text.culture=invariant;text.entry=TryFrom", "validated-class", "nested-in-class", "base-depth-1", typeof(O187.V187), typeof(global::System.DateOnly), null, null, [("shape.site", "nested-in-class")]), + new(188, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=int;text.culture=invariant;text.entry=Parse", "record-struct", "nested-two-levels", "explicit-interface-member", typeof(O188.P188.V188), typeof(int), null, null, []), + new(189, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=NodaTime.Instant;text.culture=invariant;text.entry=ISpanParsable", "validated-class", "nested-two-levels", "explicit-interface-member", typeof(O189.P189.V189), typeof(global::NodaTime.Instant), null, null, []), + new(190, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Guid;text.culture=de-DE;text.entry=TryFrom", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O190.V190), typeof(global::System.Guid), null, null, []), + new(191, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=string;text.culture=globalization-invariant;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "explicit-interface-member", typeof(O191.P191.V191), typeof(string), null, null, []), + new(192, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=ulong;text.culture=globalization-invariant;text.entry=type-converter", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V192), typeof(ulong), null, null, []), + new(193, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=DateOnly;text.culture=globalization-invariant;text.entry=TryParse", "sealed-record-class", "top-level", "base-depth-2", typeof(V193), typeof(global::System.DateOnly), null, null, []), + new(194, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=byte;text.culture=de-DE;text.entry=TryFrom", "validated-class", "nested-in-class", "base-depth-2", typeof(O194.V194), typeof(byte), null, null, []), + new(195, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=flags-enum;text.culture=globalization-invariant;text.entry=Parse", "sealed-record-class", "top-level", "explicit-interface-member", typeof(V195), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(196, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=double;text.culture=ar-SA;text.entry=type-converter", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V196), typeof(double), null, null, []), + new(197, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=DateTime;text.culture=invariant;text.entry=TryFrom", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O197.V197), typeof(global::System.DateTime), null, null, []), + new(198, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=byte;text.culture=invariant;text.entry=type-converter", "validated-struct", "nested-in-struct", "none", typeof(O198.V198), typeof(byte), null, null, [("shape.inheritance", "none")]), + new(199, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=char;text.culture=invariant;text.entry=IParsable", "validated-struct", "nested-two-levels", "explicit-interface-member", typeof(O199.P199.V199), typeof(char), null, null, []), + new(200, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=enum;text.culture=ar-SA;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O200.V200), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(201, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Guid;text.culture=globalization-invariant;text.entry=ISpanParsable", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O201.V201), typeof(global::System.Guid), null, null, []), + new(202, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTimeOffset;text.culture=de-DE;text.entry=IParsable", "sealed-record-class", "nested-in-struct", "base-depth-2", typeof(O202.V202), typeof(global::System.DateTimeOffset), null, null, []), + new(203, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=char;text.culture=invariant;text.entry=TryParse", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O203.P203.V203), typeof(char), null, null, []), + new(204, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=TimeOnly;text.culture=invariant;text.entry=ISpanParsable", "validated-class", "nested-in-struct", "base-depth-1", typeof(O204.V204), typeof(global::System.TimeOnly), null, null, []), + new(205, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=string;text.culture=tr-TR;text.entry=ISpanParsable", "sealed-record-class", "nested-in-struct", "base-depth-1", typeof(O205.V205), typeof(string), null, null, []), + new(206, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri;text.culture=tr-TR;text.entry=TryFrom", "validated-class", "nested-two-levels", "base-depth-1", typeof(O206.P206.V206), typeof(global::System.Uri), null, null, []), + new(207, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=DateTimeOffset;text.culture=ar-SA;text.entry=ISpanParsable", "sealed-record-class", "top-level", "base-depth-1", typeof(V207), typeof(global::System.DateTimeOffset), null, null, []), + new(208, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=long;text.culture=ar-SA;text.entry=Parse", "validated-class", "top-level", "base-depth-2", typeof(V208), typeof(long), null, null, []), + new(209, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=ulong;text.culture=de-DE;text.entry=ISpanParsable", "validated-class", "nested-two-levels", "explicit-interface-member", typeof(O209.P209.V209), typeof(ulong), null, null, []), + new(210, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=bool;text.culture=invariant;text.entry=ToString-format", "validated-class", "nested-in-class", "base-depth-1", typeof(O210.V210), typeof(bool), null, null, []), + new(211, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=top-level;shape.wrapped=TimeSpan;text.culture=tr-TR;text.entry=Parse", "validated-class", "top-level", "base-explicit-IEquatable", typeof(V211), typeof(global::System.TimeSpan), null, null, []), + new(212, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=int;text.culture=globalization-invariant;text.entry=TryFrom", "validated-class", "nested-in-class", "base-depth-3", typeof(O212.V212), typeof(int), null, null, []), + new(213, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=in-generic-type;shape.wrapped=int;text.culture=invariant;text.entry=ToString-format", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O213.V213), typeof(int), null, null, [("shape.site", "nested-in-class")]), + new(214, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=top-level;shape.wrapped=decimal;text.culture=tr-TR;text.entry=IParsable", "validated-class", "top-level", "base-depth-1", typeof(V214), typeof(decimal), null, null, []), + new(215, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=byte;text.culture=de-DE;text.entry=TryFormat-short-span", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O215.P215.V215), typeof(byte), null, null, []), + new(216, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=own-record-class;text.culture=ar-SA;text.entry=TryParse", "validated-class", "nested-two-levels", "base-depth-1", typeof(O216.P216.V216), typeof(global::Probes.Shapes.PRec), null, null, []), + new(217, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=int;text.culture=invariant;text.entry=ISpanParsable", "validated-struct", "nested-in-struct", "none", typeof(O217.V217), typeof(int), null, null, []), + new(218, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=in-generic-type;shape.wrapped=string;text.culture=globalization-invariant;text.entry=type-converter", "sealed-record-class", "nested-in-class", "base-explicit-IEquatable", typeof(O218.V218), typeof(string), null, null, [("shape.site", "nested-in-class")]), + new(219, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=Guid;text.culture=invariant;text.entry=Parse", "validated-class", "nested-two-levels", "base-depth-2", typeof(O219.P219.V219), typeof(global::System.Guid), null, null, []), + new(220, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-two-levels;shape.wrapped=flags-enum;text.culture=globalization-invariant;text.entry=type-converter", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O220.P220.V220), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(221, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=byte;text.culture=invariant;text.entry=Parse", "sealed-record-class", "nested-in-struct", "none", typeof(O221.V221), typeof(byte), null, null, []), + new(222, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=DateTime;text.culture=tr-TR;text.entry=Parse", "sealed-record-class", "nested-in-struct", "base-depth-1", typeof(O222.V222), typeof(global::System.DateTime), null, null, []), + new(223, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=none;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=own-record-class;text.culture=tr-TR;text.entry=TryFrom", "sealed-record-class", "top-level", "none", typeof(V223), typeof(global::Probes.Shapes.PRec), null, null, []), + new(224, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-struct;shape.wrapped=NodaTime.Instant;text.culture=tr-TR;text.entry=type-converter", "validated-class", "nested-in-struct", "base-depth-1", typeof(O224.V224), typeof(global::NodaTime.Instant), null, null, []), + new(225, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-2;shape.kind=record-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateOnly;text.culture=de-DE;text.entry=type-converter", "record-struct", "nested-two-levels", "none", typeof(O225.P225.V225), typeof(global::System.DateOnly), null, null, [("shape.inheritance", "none")]), + new(226, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-two-levels;shape.wrapped=DateTimeOffset;text.culture=invariant;text.entry=type-converter", "validated-struct", "nested-two-levels", "none", typeof(O226.P226.V226), typeof(global::System.DateTimeOffset), null, null, [("shape.inheritance", "none")]), + new(227, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeOnly;text.culture=ar-SA;text.entry=IParsable", "sealed-record-class", "nested-in-class", "base-depth-3", typeof(O227.V227), typeof(global::System.TimeOnly), null, null, []), + new(228, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-struct;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-class;shape.wrapped=TimeSpan;text.culture=globalization-invariant;text.entry=TryFrom", "validated-struct", "nested-in-class", "none", typeof(O228.V228), typeof(global::System.TimeSpan), null, null, [("shape.inheritance", "none")]), + new(229, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=DateTime;text.culture=tr-TR;text.entry=ToString-format", "validated-class", "nested-two-levels", "base-explicit-IEquatable", typeof(O229.P229.V229), typeof(global::System.DateTime), null, null, []), + new(230, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-explicit-IEquatable;shape.kind=validated-class;shape.refused-kind=generic-wrapped (CMTK1005);shape.site=nested-in-struct;shape.wrapped=double;text.culture=globalization-invariant;text.entry=TryFormat-short-span", "validated-class", "nested-in-struct", "base-explicit-IEquatable", typeof(O230.V230), typeof(double), null, null, []), + new(231, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-struct;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-class;shape.wrapped=Uri;text.culture=ar-SA;text.entry=ToString-format", "validated-struct", "nested-in-class", "explicit-interface-member", typeof(O231.V231), typeof(global::System.Uri), null, null, []), + new(232, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=over-value-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=double;text.culture=ar-SA;text.entry=Parse", "record-struct", "nested-in-struct", "explicit-interface-member", typeof(O232.V232), typeof(double), null, null, []), + new(233, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-two-levels;shape.wrapped=DateOnly;text.culture=tr-TR;text.entry=TryFormat-short-span", "record-struct", "nested-two-levels", "none", typeof(O233.P233.V233), typeof(global::System.DateOnly), null, null, [("shape.inheritance", "none")]), + new(234, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=record-struct;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=enum;text.culture=invariant;text.entry=TryFrom", "record-struct", "top-level", "none", typeof(V234), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(235, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=top-level;shape.wrapped=DateTimeOffset;text.culture=globalization-invariant;text.entry=TryFrom", "validated-class", "top-level", "base-depth-3", typeof(V235), typeof(global::System.DateTimeOffset), null, null, []), + new(236, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=bool;text.culture=de-DE;text.entry=Parse", "validated-class", "top-level", "base-depth-3", typeof(V236), typeof(bool), null, null, []), + new(237, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-2;shape.kind=sealed-record-class;shape.refused-kind=unsealed-record-class (CMTK1006);shape.site=nested-in-class;shape.wrapped=decimal;text.culture=tr-TR;text.entry=TryFormat-short-span", "sealed-record-class", "nested-in-class", "base-depth-2", typeof(O237.V237), typeof(decimal), null, null, []), + new(238, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=ulong;text.culture=de-DE;text.entry=TryFormat-short-span", "sealed-record-class", "nested-two-levels", "base-depth-1", typeof(O238.P238.V238), typeof(ulong), null, null, []), + new(239, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=double;text.culture=de-DE;text.entry=IParsable", "sealed-record-class", "top-level", "base-depth-1", typeof(V239), typeof(double), null, null, []), + new(240, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-1;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=top-level;shape.wrapped=byte;text.culture=ar-SA;text.entry=TryFrom", "sealed-record-class", "top-level", "base-depth-1", typeof(V240), typeof(byte), null, null, []), + new(241, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=validated-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-two-levels;shape.wrapped=enum;text.culture=de-DE;text.entry=IParsable", "validated-class", "nested-two-levels", "explicit-interface-member", typeof(O241.P241.V241), typeof(global::Probes.Shapes.PEnum), null, null, []), + new(242, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=sealed-record-class;shape.refused-kind=abstract (CMTK1005);shape.site=nested-in-struct;shape.wrapped=flags-enum;text.culture=ar-SA;text.entry=TryFrom", "sealed-record-class", "nested-in-struct", "base-depth-3", typeof(O242.V242), typeof(global::Probes.Shapes.PFlags), null, null, []), + new(243, "parse-shape=ordinary;process.converter-lookup=wrapped-type-converter-looked-up-first;shape.inheritance=base-depth-3;shape.kind=record-struct;shape.refused-kind=closed-set-named-as-generated-member (CMTK1021);shape.site=top-level;shape.wrapped=Uri;text.culture=ar-SA;text.entry=IParsable", "record-struct", "top-level", "none", typeof(V243), typeof(global::System.Uri), null, null, [("shape.inheritance", "none")]), + new(244, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=none;shape.kind=validated-struct;shape.refused-kind=equality-operator (CMTK1011);shape.site=nested-two-levels;shape.wrapped=NodaTime.Instant;text.culture=ar-SA;text.entry=TryFormat-short-span", "validated-struct", "nested-two-levels", "none", typeof(O244.P244.V244), typeof(global::NodaTime.Instant), null, null, []), + new(245, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=explicit-interface-member;shape.kind=record-struct;shape.refused-kind=over-object (CMTK1005);shape.site=nested-in-struct;shape.wrapped=Uri;text.culture=invariant;text.entry=TryFormat-short-span", "record-struct", "nested-in-struct", "explicit-interface-member", typeof(O245.V245), typeof(global::System.Uri), null, null, []), + new(246, "parse-shape=ordinary;process.converter-lookup=none;shape.inheritance=base-depth-3;shape.kind=validated-class;shape.refused-kind=over-System.Enum (CMTK1005);shape.site=nested-two-levels;shape.wrapped=Uri;text.culture=ar-SA;text.entry=TryParse", "validated-class", "nested-two-levels", "base-depth-3", typeof(O246.P246.V246), typeof(global::System.Uri), null, null, []), + ]; +} diff --git a/tools/probe/corpus/harness/Cmp.cs b/tools/probe/corpus/harness/Cmp.cs index 7339983e..a435030b 100644 --- a/tools/probe/corpus/harness/Cmp.cs +++ b/tools/probe/corpus/harness/Cmp.cs @@ -85,6 +85,30 @@ public static SortedSet<int> DocStatuses(int arity, string variant) return s; } + static readonly Dictionary<string, string> Kinds = new(StringComparer.Ordinal) + { + ["HFault.Missing"] = "NotFound", ["HFault.Clash"] = "Conflict", ["HFault.Bad"] = "Invalid", ["HFault.Denied"] = "Forbidden", + ["HFault.Down"] = "Unavailable", ["HFault.Stale"] = "PreconditionFailed", ["HFault.Garbled"] = "Malformed", + ["GFault.Gone"] = "NotFound", ["GFault.Raced"] = "Conflict", ["GFault.Old"] = "PreconditionFailed", + ["KFault.Clash"] = "Conflict", ["KFault.Lost"] = "NotFound", + }; + /// <summary>The kind of a fault-set member, or null for a member of the enum SFault, which has none.</summary> + public static string? KindOf(string type, string member) => Kinds.GetValueOrDefault(type + "." + member); + /// <summary>The problem types the operation's response for <paramref name="status"/> names: every member answered with it, of every set.</summary> + public static HashSet<string> TypesAt(int arity, string variant, int status) + { + var members = new List<(string Type, string Member, int Status)> + { + ("HFault", "Missing", 404), ("HFault", "Clash", variant == "group" ? 422 : 409), ("HFault", "Denied", 403), ("HFault", "Down", 503), + ("HFault", "Stale", 412), ("HFault", "Garbled", 400), + }; + if (variant != "wo") members.Add(("HFault", "Bad", 422)); + if (variant == "dup") members.AddRange([("KFault", "Clash", 409), ("KFault", "Lost", 404)]); + else members.AddRange([("GFault", "Gone", 404), ("GFault", "Raced", 409), ("GFault", "Old", 412)]); + // arity 3 adds SFault, an enum mapped to 409 and 410 with no type + return [.. members.Where(m => m.Status == status).Select(m => ComposedClaims.TypeOf(m.Type, KindOf(m.Type, m.Member)!))]; + } + public static RouteHandlerBuilder Decorate(RouteHandlerBuilder rb, string variant) => variant switch { "wo" => rb.WithoutFaults(HFault.Bad), diff --git a/tools/probe/corpus/harness/ComposedProbes.cs b/tools/probe/corpus/harness/ComposedProbes.cs index 33949f25..56532242 100644 --- a/tools/probe/corpus/harness/ComposedProbes.cs +++ b/tools/probe/corpus/harness/ComposedProbes.cs @@ -1,14 +1,17 @@ // Claims C03-composed-faults (AspNetCore README#faults-of-several-types) and C05-with-rule (README#the-rule-that-refused). -// C03: a Fault<T1,T2[,T3]> is "answered as its own set maps it" (status, `fault`, detail, rule intact); "its operation lists the statuses of both -// sets, in ascending order"; "the host doesn't start while either set is unmapped". +// C03: a Fault<T1,T2[,T3]> is "answered as its own set maps it" (status, `fault`, problem type, detail, rule intact); "its operation lists the +// statuses of both sets, in ascending order, with the problem types of both where they share a status"; "the host doesn't start while either +// set is unmapped". The host maps every fault set with a problem type of its set and kind (TypeOf), so two sets sharing a status name two types. // C05: WithRule(member) is written as `rule`, "qualified with its type's name", a nested type after its nesting chain "whatever QualifiedFaultNames // says"; "The status, the mapping and the document stay the error's"; a value no member names "throws where it's given"; the document describes `rule`. -// Oracle: claim (README text) + differential wire/document. Expected: see Cmp.Expect / Cmp.DocStatuses. +// Oracle: claim (README text) + differential wire/document. Expected: see Cmp.Expect / Cmp.DocStatuses. Each obligation is a check of its +// own (ComposedChecks.C03, C05), every check on one host. using System.Text.Json.Nodes; using CodoMetis.TypeKit; using CodoMetis.TypeKit.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Probes.Faults; using Probes.Shapes; namespace Probes; @@ -25,61 +28,104 @@ public static class ComposedClaims { FaultControllers = true, Controllers = Hx.Plain(r) ? [typeof(RulePlainController)] : [typeof(RuleApiController)], Minimal = Cmp.MapMinimal, Qualified = r.Is("qualified", "QualifiedFaultNames"), + Mapping = "own-per-set-type", + Services = s => s.AddFaultResponses(f => FaultResponse.For(f.Kind).WithType(new Uri(TypeOf(f.GetType().Name, f.Kind.ToString())))), }; - /// <summary>The wire and the document of one endpoint family under the row. e: the error code of Cmp.</summary> - public static async Task<string?> WireAndDoc(Row r, Entry en, int arity, int e, string rule) + /// <summary>The problem type the host maps a fault-set member to: its set's and its kind's, so two sets sharing a status name two types.</summary> + public static string TypeOf(string set, string kind) => $"https://p.example/{set}/{kind}"; + + /// <summary>The problem's rule, an aspect of its own beside <see cref="Aspect"/>'s.</summary> + public const string Rule = "rule"; + + /// <summary> + /// The wire and the document of one endpoint family under the row, one check (Row.Check) per obligation <paramref name="of"/> names an + /// aspect for (<see cref="Aspect"/>'s and <see cref="Rule"/>), against <paramref name="against"/>; every check runs on the one host, with + /// the answers fetched once. e: the error code of Cmp. + /// </summary> + public static async Task<string?> WireAndDoc(Row r, Entry en, int arity, int e, string rule, Func<string, string[]> of, Baseline against) { var variant = Variant(r); - WebApplication app; + var obligations = Aspect.Answer.Append(Rule).Concat(Aspect.Doc).SelectMany(of).Distinct().ToList(); + WebApplication? app = null; Exception? down = null; try { app = await Hx.Start(r, en, Cfg(r)); } - catch (Exception ex) { return "host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } + catch (Exception ex) when (ex is not Sides.InputsNotEquivalent) { down = ex; } await using var _ = app; + string? failed = null; + if (down is not null) + { + foreach (var o in obligations) { var f = r.Check(o, against, _ => "host did not start: " + down.GetType().Name + ": " + Cut(down.Message)); failed ??= f; } + return failed; + } var qualified = r.Is("qualified", "QualifiedFaultNames"); var sides = new List<string>(); if (Hx.HasMinimal(r)) sides.Add(Hx.M(r, $"/m/{arity}/{variant}")); if (Hx.HasMvc(r)) sides.Add(Hx.C(r, $"/c/{arity}/{variant}")); - JsonNode? doc = null; string? docFail = null; - try { doc = await Hx.Doc(app); } - catch (Exception ex) { docFail = "DOC " + ex.GetType().Name + ": " + Cut(ex.Message) + " @ " + string.Join(" < ", (ex.StackTrace ?? "").Split('\n').Take(4).Select(x => x.Trim().Split('(')[0])); } - foreach (var path in sides) + JsonNode? doc = null; Exception? docFailed = null; + try { doc = await Hx.Doc(app!); } + catch (Exception ex) { docFailed = ex; } + var wires = new Dictionary<string, Hx.Wire>(); + foreach (var path in sides) wires[path] = await Hx.Get(app!, $"{path}?e={e}"); + var (type, member, status) = Cmp.Expect(arity, variant, e); + + foreach (var o in obligations) { - var w = await Hx.Get(app, $"{path}?e={e}"); - var (type, member, status) = Cmp.Expect(arity, variant, e); - if (w.Thrown is not null) return $"{path} e={e}: request threw {w.Thrown}"; - if ((int)w.Status != status) return $"{path} e={e}: status {(int)w.Status}, expected {status}: {Cut(w.Body)}"; - JsonNode? body; try { body = JsonNode.Parse(w.Body); } catch { return $"{path} e={e}: body is not JSON: {Cut(w.Body)}"; } - var fault = Un(r, body?["fault"]?.GetValue<string>()); - var wantFault = qualified ? $"{type}.{member}" : member; - if (fault != wantFault) return $"{path} e={e}: fault {fault}, expected {wantFault}"; - if (e == 3 && Un(r, body?["detail"]?.GetValue<string>()) != Cmp.Detail) return $"{path} e={e}: detail {body?["detail"]}, expected the occurrence's"; - var gotRule = Un(r, body?["rule"]?.GetValue<string>()); - if (gotRule != (rule == "" ? null : rule)) return $"{path} e={e}: rule {gotRule ?? "(none)"}, expected {(rule == "" ? "(none)" : rule)}"; - if (int.Parse(body?["status"]?.ToString() is { } st ? Un(r, st)!.Trim('"') : "0") != status) return $"{path} e={e}: problem status field {body?["status"]}"; - // the document - if (docFail is not null) - { - if (r.Is("json.ReferenceHandler", "Preserve") && docFail.Contains("ReferenceHandler.Preserve")) { r.Note("doc:preserve-refused-by-stj"); continue; } - if (r.Is("json.MaxDepth", "3") && docFail.Contains("maximum allowed depth")) { r.Note("doc:stj-maxdepth-also-for-plain-ProducesProblem"); continue; } // plain ASP.NET fails alike (verified) - if (docFail.Contains("converted to OpenApiJsonSchema") && en.Wrapped == "char" && r.Is("json.NumberHandling", "WriteAsString")) { r.Note("doc:aspnet-refuses-char-WriteAsString-for-the-plain-type-too"); continue; } // verified with the baseline (doc-refused-for-wrapped-too) - if (docFail.Contains("ClosedSetSchemaTransformer.Describe") && en.Wrapped == "own-record-class") { r.Note("doc:F2"); continue; } // open finding F2 - return docFail; - } - if (!Hx.InDocument(r) && path.Contains("/c/")) { r.Note("doc:plain-controller-not-in-api-explorer"); continue; } - var ops = doc?["paths"]?[path]?["get"]; if (ops is null) return $"document has no GET {path}"; - var keys = ops["responses"]!.AsObject().Select(kv => kv.Key).Where(k => int.TryParse(k, out var n) && n >= 400).Select(int.Parse).ToList(); - var want = Cmp.DocStatuses(arity, variant); - // A status the endpoint declares itself (the 429) takes its place among the sets' statuses (fixed in 8d1ebd8). - if (!keys.SequenceEqual(want)) return $"{path}: document lists {string.Join(",", keys)}, expected {string.Join(",", want)} (ascending)"; - foreach (var k in keys) + var f = await r.Check(o, against, async c => { - if (variant == "declared" && k == 429) continue; // a declared problem is ASP.NET's, not an answered fault - var txt = Hx.Deep(doc!, ops["responses"]![k.ToString()])?.ToJsonString() ?? ""; - if (!txt.Contains("\"fault\"")) return $"{path} {k}: response schema does not describe fault: {Cut(txt)}"; - if (!txt.Contains("\"rule\"")) return $"{path} {k}: response schema does not describe rule: {Cut(txt)}"; - } + bool want(string aspect) => of(aspect).Contains(o); + if (Aspect.Answer.Any(want) || want(Rule)) + foreach (var path in sides) + { + var w = wires[path]; + if (w.Thrown is not null) return $"{path} e={e}: request threw {w.Thrown}"; + if (want(Aspect.Status) && (int)w.Status != status) return $"{path} e={e}: status {(int)w.Status}, expected {status}: {Cut(w.Body)}"; + JsonNode? body; try { body = JsonNode.Parse(w.Body); } catch { return $"{path} e={e}: body is not JSON: {Cut(w.Body)}"; } + var fault = Un(r, body?["fault"]?.GetValue<string>()); + var wantFault = qualified ? $"{type}.{member}" : member; + if (want(Aspect.Fault) && fault != wantFault) return $"{path} e={e}: fault {fault}, expected {wantFault}"; + if (want(Aspect.Detail) && e == 3 && Un(r, body?["detail"]?.GetValue<string>()) != Cmp.Detail) return $"{path} e={e}: detail {body?["detail"]}, expected the occurrence's"; + var gotRule = Un(r, body?["rule"]?.GetValue<string>()); + if (want(Rule) && gotRule != (rule == "" ? null : rule)) return $"{path} e={e}: rule {gotRule ?? "(none)"}, expected {(rule == "" ? "(none)" : rule)}"; + // an enum (SFault) is mapped to a status alone, and ASP.NET writes its own default type there + if (want(Aspect.Type) && Cmp.KindOf(type, member) is { } kind && Un(r, body?["type"]?.GetValue<string>()) is var gotType && gotType != TypeOf(type, kind)) + return $"{path} e={e}: type {gotType ?? "(none)"}, expected {TypeOf(type, kind)}"; + if (want(Aspect.Status) && int.Parse(body?["status"]?.ToString() is { } st ? Un(r, st)!.Trim('"') : "0") != status) return $"{path} e={e}: problem status field {body?["status"]}"; + } + if (!Aspect.Doc.Any(want)) return null; + // the document: one ASP.NET alone refuses the same way has nothing to check (noted there) + if (docFailed is not null) + return await Hx.DocRefusedByAspNetAlone(r, en, Cfg(r), docFailed, (Aspect.Answer.Any(want) || want(Rule)) && sides.Count > 0) is { } alone + ? alone + " @ " + string.Join(" < ", (docFailed.StackTrace ?? "").Split('\n').Take(4).Select(x => x.Trim().Split('(')[0])) + : null; + foreach (var path in sides) + { + var ops = doc!["paths"]?[path]?["get"]; + if (ops is null && !Hx.InDocument(r) && path.Contains("/c/")) { c.Note(NoteKeys.DocControllerNotInApiExplorer); continue; } + if (ops is null) return $"document has no GET {path}"; + var keys = ops["responses"]!.AsObject().Select(kv => kv.Key).Where(k => int.TryParse(k, out var n) && n >= 400).Select(int.Parse).ToList(); + var listed = Cmp.DocStatuses(arity, variant); + // A status the endpoint declares itself (the 429) takes its place among the sets' statuses (fixed in 8d1ebd8). + if ((want(Aspect.DocListed) || want(Aspect.DocOrder)) && !keys.SequenceEqual(listed)) return $"{path}: document lists {string.Join(",", keys)}, expected {string.Join(",", listed)} (ascending)"; + foreach (var k in keys) + { + if (variant == "declared" && k == 429) continue; // a declared problem is ASP.NET's, not an answered fault + var txt = Hx.Deep(doc!, ops["responses"]![k.ToString()])?.ToJsonString() ?? ""; + if (want(Aspect.DocDescribes) && !txt.Contains("\"fault\"")) return $"{path} {k}: response schema does not describe fault: {Cut(txt)}"; + if (want(Aspect.DocDescribes) && !txt.Contains("\"rule\"")) return $"{path} {k}: response schema does not describe rule: {Cut(txt)}"; + var desc = ops["responses"]![k.ToString()]?["description"]?.GetValue<string>() ?? ""; + const string Lead = "Problem types: "; + var at = desc.IndexOf(Lead, StringComparison.Ordinal); + var types = at < 0 ? [] : desc[(at + Lead.Length)..].Split(", ").ToHashSet(); + var wantTypes = Cmp.TypesAt(arity, variant, k); + if (want(Aspect.DocTypes) && !types.SetEquals(wantTypes)) + return $"{path} {k}: description \"{desc}\" names problem types {{{string.Join(", ", types)}}}, expected {{{string.Join(", ", wantTypes)}}}"; + } + } + return null; + }); + failed ??= f; } - return null; + return failed; } /// <summary>"The host doesn't start while either set is unmapped."</summary> @@ -98,7 +144,7 @@ public static class ComposedClaims }; WebApplication? app = null; try { app = await Hx.Start(r, en, cfg); } - catch (Exception ex) + catch (Exception ex) when (ex is not Sides.InputsNotEquivalent) { return ex.ToString().Contains(unmapped) ? null : $"host refused to start but not naming {unmapped}: {ex.GetType().Name}: {Cut(ex.Message)}"; } @@ -110,26 +156,57 @@ public static class ComposedClaims public static class ComposedChecks { static void Process(Row r, Entry en) { if (r.Is("process.converter-lookup", "wrapped-type-converter-looked-up-first")) _ = System.ComponentModel.TypeDescriptor.GetConverter(Make.Held(en.Plain)); } + /// <summary> + /// C03, a check per obligation: each-error-own-set (status, fault, problem type, and the rule where the error names none), + /// detail-and-rule-intact (error with-detail or with-rule: the detail and the rule), statuses-ascending (the statuses each operation + /// lists, in order, and their problem schemas), shared-status-types (the problem types each status's description names), and + /// unmapped-set-stops-start (a host with one set unmapped, against the refusal) in every row. + /// </summary> public static async Task<string?> C03(Row r) { var en = Registry.Of(r); Process(r, en); var arity = int.Parse(r["arity"]); var e = r["error"] switch { "first-set" => 1, "last-set" => 2, "with-detail" => 3, _ => 4 }; - return await ComposedClaims.WireAndDoc(r, en, arity, e, e == 4 ? "PRule.A" : "") ?? await ComposedClaims.UnmappedDoesNotStart(r, en, arity, e); + var intact = r["error"] is "with-detail" or "with-rule"; + string[] Of(string aspect) => aspect switch + { + Aspect.Detail or ComposedClaims.Rule when intact => ["detail-and-rule-intact"], + Aspect.DocListed or Aspect.DocOrder or Aspect.DocDescribes => ["statuses-ascending"], + Aspect.DocTypes => ["shared-status-types"], + _ => ["each-error-own-set"], + }; + var answered = await ComposedClaims.WireAndDoc(r, en, arity, e, e == 4 ? "PRule.A" : "", Of, + Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types")); + var unmapped = await r.Check("unmapped-set-stops-start", Baseline.Refusal, _ => ComposedClaims.UnmappedDoesNotStart(r, en, arity, e)); + return answered ?? unmapped; } + /// <summary> + /// C05, a check per obligation: unnamed-value-throws (ruletype flags-combination or undefined-cast, against the refusal); + /// rule-qualified (enum, closed-set) or nested-chain (nested-enum), and no-namespace, for the rule on the wire; stays-the-errors for + /// the status, fault, problem type and the document, and in the rows of an unnamed value for the error answered without a rule. + /// </summary> public static async Task<string?> C05(Row r) { var en = Registry.Of(r); Process(r, en); + var ruletype = r["ruletype"]; // a value no member names throws where it is given (never a rule on the wire) - switch (r["ruletype"]) + var thrown = ruletype switch { - case "flags-combination": - try { _ = Fault.Of(HFault.Clash).WithRule((FRule)3); return "WithRule(flags combination) did not throw"; } catch (ArgumentException) { break; } - case "undefined-cast": - try { _ = Fault.Of(HFault.Clash).WithRule((PRule)9); return "WithRule(undefined cast) did not throw"; } catch (ArgumentException) { break; } - } - var (e, rule) = r["ruletype"] switch { "enum" => (4, "PRule.A"), "closed-set" => (5, "RuleSet.X"), "nested-enum" => (6, "Holder.NestedRule.N"), _ => (1, "") }; - return await ComposedClaims.WireAndDoc(r, en, 3, e, rule); + "flags-combination" => r.Check("unnamed-value-throws", Baseline.Refusal, c => + { + try { _ = Fault.Of(HFault.Clash).WithRule((FRule)3); return "WithRule(flags combination) did not throw"; } catch (ArgumentException) { return null; } + }), + "undefined-cast" => r.Check("unnamed-value-throws", Baseline.Refusal, c => + { + try { _ = Fault.Of(HFault.Clash).WithRule((PRule)9); return "WithRule(undefined cast) did not throw"; } catch (ArgumentException) { return null; } + }), + _ => null, + }; + var (e, rule) = ruletype switch { "enum" => (4, "PRule.A"), "closed-set" => (5, "RuleSet.X"), "nested-enum" => (6, "Holder.NestedRule.N"), _ => (1, "") }; + var named = ruletype switch { "enum" or "closed-set" => "rule-qualified", "nested-enum" => "nested-chain", _ => null }; + string[] Of(string aspect) => aspect == ComposedClaims.Rule && named is not null ? [named, "no-namespace"] : ["stays-the-errors"]; + var answered = await ComposedClaims.WireAndDoc(r, en, 3, e, rule, Of, Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#the-rule-that-refused")); + return thrown ?? answered; } } public static class ComposedShrinkInit @@ -139,13 +216,13 @@ public static class ComposedShrinkInit } public sealed class C03ComposedProbe { - public static TheoryData<string, int> Data() => Rows.Data("C03-composed-faults"); + public static TheoryData<string, int> Data() => Rows.Data("C03ComposedProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run("C03", idx, ComposedChecks.C03, claim); + public Task Row(string probe, int idx) => Rec.Run("C03", idx, ComposedChecks.C03, probe); } public sealed class C05RuleProbe { - public static TheoryData<string, int> Data() => Rows.Data("C05-with-rule"); + public static TheoryData<string, int> Data() => Rows.Data("C05RuleProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run("C05", idx, ComposedChecks.C05, claim); + public Task Row(string probe, int idx) => Rec.Run("C05", idx, ComposedChecks.C05, probe); } diff --git a/tools/probe/corpus/harness/Containers.g.cs b/tools/probe/corpus/harness/Containers.g.cs index cff4defd..d79627ed 100644 --- a/tools/probe/corpus/harness/Containers.g.cs +++ b/tools/probe/corpus/harness/Containers.g.cs @@ -1,4 +1,4 @@ -// <auto-generated/> by gen_containers.py +// <auto-generated/> by tools/probe/generators/containers.cs #nullable disable #pragma warning disable CS8618, CS1591, CS0649 using System.Text.Json.Serialization; diff --git a/tools/probe/corpus/harness/DecimalSpelling.cs b/tools/probe/corpus/harness/DecimalSpelling.cs new file mode 100644 index 00000000..11a7c617 --- /dev/null +++ b/tools/probe/corpus/harness/DecimalSpelling.cs @@ -0,0 +1,230 @@ +// Claim C01-decimal-spelling (docs/json.md#decimals-in-one-spelling): DecimalNumberJsonConverter writes the smallest scale as a +// number, DecimalStringJsonConverter as a string; "Register either one once, and it covers every decimal: nullable ones, dictionary +// keys, and every value object over a decimal, since its JSON is the underlying type's." "Both read a number, or a string holding one, +// and keep the scale they read." +// Oracle: claim (the minimal spelling, computed here independently of the product) plus differential (the value object's document is the +// plain decimal's under the same options), with the options' own leaving-out shown by a baseline without the converter in the same row. +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using CodoMetis.TypeKit; +using Probes.Shapes; +namespace Probes.Json; + +public static class DecimalSpelling +{ + static readonly CultureInfo Inv = CultureInfo.InvariantCulture; + const char Bs = (char)92; + + /// <summary>The input axis as decimal text: max28digits is 7.8 at scale 28, the largest scale a decimal has.</summary> + public static string Text(string input) => input == "max28digits" ? "7.8000000000000000000000000000" : input; + + /// <summary>The minimal spelling, worked out without the product: invariant digits, trailing fractional zeros dropped, a zero written 0.</summary> + public static string Canonical(decimal d) + { + if (d == 0m) return "0"; + var s = d.ToString(Inv); + return s.Contains('.') ? s.TrimEnd('0').TrimEnd('.') : s; + } + + static bool IsDecimal(JTok t, decimal d) => t.Type switch + { + JsonTokenType.Number => decimal.TryParse(t.Raw, NumberStyles.Float, Inv, out var x) && x == d, + JsonTokenType.String or JsonTokenType.PropertyName => t.Text is { Length: > 0 } s && s.Trim() == s && decimal.TryParse(s, NumberStyles.Float, Inv, out var y) && y == d, + _ => false, + }; + + /// <summary>The decimals a read value holds, as the serializer writes them by default: with the scale they hold.</summary> + static List<decimal> Held(object? v, Type declared, decimal d) + { + if (v is null) return []; + var json = JsonSerializer.Serialize(v, declared, Jx.Plain); + return Jx.Tokens(json).Where(t => IsDecimal(t, d)).Select(t => decimal.Parse(t.Type == JsonTokenType.Number ? t.Raw : t.Text!, NumberStyles.Float, Inv)).ToList(); + } + static bool Same(decimal a, decimal b) => decimal.GetBits(a).SequenceEqual(decimal.GetBits(b)); + static string Show(List<decimal> l) => string.Join(",", l.Select(x => x.ToString(Inv))); + + /// <summary>What one form of the document read as: the decimal's and the value object's refusals and the decimals each holds.</summary> + sealed record Form(string Name, string Json, Exception? Ep, Exception? Ev, List<decimal> Cp, List<decimal> Cv, bool BothRefused); + + /// <summary> + /// C01's checks, one per obligation, over one write and one read of the row's shape: the converter's spelling of each value + /// (smallest-scale; number-is-a-number or string-is-a-string, by the row's converter) against the docs' minimal spelling worked out + /// here; every decimal of the document, a key's included, and the value object's document against the plain decimal's + /// (covers-every-decimal, the twin); each form of the input read (reads-a-number-or-a-string) with the scale it carries + /// (keeps-the-scale-it-reads). The options' own leaving-out is shown by a baseline without the converter in the same row: a + /// direction it leaves out has nothing to check. Every check runs; returns the first failure. + /// </summary> + public static string? Check(Row r) + { + var kind = r["shape.kind"]; + if (kind == "closed-set") { r.Outcome("harness-error", "C01 excludes closed-set: a closed set holds no decimal"); return null; } + var e = Registry.Get(kind, "decimal"); + var text = Text(r["input"]); + var d = decimal.Parse(text, NumberStyles.Float, Inv); + var canon = Canonical(d); + var number = r["converter"] == "number"; + JsonConverter conv = number ? new DecimalNumberJsonConverter() : new DecimalStringJsonConverter(); + var position = r["shape.position"]; + var containing = position == "root" ? "class" : r["shape.containing"]; + var (hp, hv) = Sides.Twins(position, containing, e, d, Make.Vo(e, d)); + var o = JCtx.Apply(r, e, hp.Declared, hv.Declared); + var ob = new JsonSerializerOptions(o); // the baseline: the same options, the serializer's own decimal + foreach (var c in ob.Converters.Where(c => c.CanConvert(typeof(decimal))).ToList()) ob.Converters.Remove(c); + o.Converters.Insert(0, conv); // registered first: an application converter for decimals (own-for-wrapped) comes after it + var ownVo = r.Is("json.Converters", "own-for-value-object"); + var docs = Baseline.Docs("docs/json.md#decimals-in-one-spelling"); + const string Smallest = "smallest-scale", Covers = "covers-every-decimal", Reads = "reads-a-number-or-a-string", Keeps = "keeps-the-scale-it-reads"; + var kindOf = number ? "number-is-a-number" : "string-is-a-string"; + + // Writing: one spelling, the smallest scale, as a number or a string; the value object's document is the decimal's. + var (jp, xp) = Jx.Ser(hp, o); var (jv, xv) = Jx.Ser(hv, o); var (jb, xb) = Jx.Ser(hp, ob); + if (xb is not null && xp is not null && (xv is not null || ownVo) && xp.GetType() == xb.GetType()) + { + // the options refuse the shape for the serializer's own decimal too: nothing is written or read to compare. Where the value + // object is refused alike, at the same stage with the same exception type, the decimal without TypeKit's converter is side (1) + // of the three-sided differential (Sides), and each check is the framework's refusal; otherwise none can tell. + var one = new Sides.Answer("write", null, xb); var three = new Sides.Answer("write", null, xv); + if (Sides.Alike(one, three)) + { + var framework = Sides.Measured(one, "the serializer's own decimal, without TypeKit's converter"); + foreach (var o2 in new[] { Smallest, kindOf, Covers, Reads, Keeps }) r.Check(o2, framework.Baseline, c => Sides.Refuses(c, framework, three)); + return null; + } + var why = "the options refuse the shape for the serializer's own decimal too: " + Jx.Ex(xb); + string? None(Check c) => c.Inconclusive(why); + foreach (var ob2 in new[] { Smallest, kindOf }) r.Check(ob2, docs, None); + r.Check(Covers, Baseline.Twin, None); r.Check(Reads, docs, None); r.Check(Keeps, docs, None); + return null; + } + var tp = xp is null ? Jx.Tokens(jp!).Where(t => IsDecimal(t, d)).ToList() : []; + var tb = xb is null ? Jx.Tokens(jb!).Where(t => IsDecimal(t, d)).ToList() : null; + var writeLeftOut = tb is { Count: 0 } && xp is null && tp.Count == 0; + string? WriteLeftOut(Check c) { c.Note(NoteKeys.JsonMemberLeftOutByOptions, "write: " + Jx.Cut(jb)); return c.Inconclusive("write: the options leave the member out, for the serializer's own decimal too: " + Jx.Cut(jb)); } + var want = number ? JsonTokenType.Number : JsonTokenType.String; + var f1 = r.Check(Smallest, docs, c => + { + if (xp is not null) return $"write: the decimal with {conv.GetType().Name} threw {Jx.Ex(xp)}; without it the serializer wrote {Jx.Cut(jb)}"; + if (tb is { Count: 0 } && tp.Count != 0) return $"write: the options leave the member out without the converter ({Jx.Cut(jb)}) but it wrote {Jx.Cut(jp)}"; + if (writeLeftOut) return WriteLeftOut(c); + if (tp.Count == 0) return $"write: no decimal in {Jx.Cut(jp)}; the serializer alone wrote {Jx.Cut(jb)}"; + foreach (var t in tp.Where(t => t.Type != JsonTokenType.PropertyName)) + if (t.Raw != canon) return $"write: {t.Type} {t.Raw}, expected the smallest scale {canon}, in {Jx.Cut(jp)}"; + c.Note(NoteKeys.Compared, $"write {text} -> {canon}"); + return null; + }); + var f2 = r.Check(kindOf, docs, c => + { + if (xp is not null) return c.Inconclusive("write: the decimal with the converter threw, judged by smallest-scale"); + if (writeLeftOut) return WriteLeftOut(c); + var values = tp.Where(t => t.Type != JsonTokenType.PropertyName).ToList(); + if (values.Count == 0) return c.Inconclusive($"write: no decimal value in {Jx.Cut(jp)}, only keys, which are names"); + foreach (var t in values) + if (t.Type != want) return $"write: {t.Type} {t.Raw}, expected the {want} {canon}, in {Jx.Cut(jp)}"; + c.Note(NoteKeys.Compared, $"write as a {want}"); + return null; + }); + + // Reading: a number, a string holding one, and an escaped string all read, the scale they carry kept, on both sides alike. + var full = Jx.Full(o); + var (jf, xf) = Jx.Ser(hp, full); + if (xf is not null) { r.Outcome("harness-error", "the document with every member written threw " + Jx.Ex(xf)); return f1 ?? f2; } + var at = Jx.Tokens(jf!).Where(t => IsDecimal(t, d)).ToList(); + string? readNothing = null; + var forms = new List<Form>(); + var leftOut = false; + if (at.Count == 0) + { + // Nothing to read either: the options leave the member out of every document (a field without IncludeFields). + var (jfb, _) = Jx.Ser(hp, Jx.Full(ob)); + if (jfb is null || Jx.Tokens(jfb).Any(t => IsDecimal(t, d))) { r.Outcome("harness-error", "no decimal in the document with every member written: " + jf); return f1 ?? f2; } + readNothing = "read: the options leave the member out of every document, for the serializer's own decimal too: " + Jx.Cut(jf); + } + else + { + var key = at.Any(t => t.Type == JsonTokenType.PropertyName); + var texts = new List<(string Name, string Json)>(); + if (!key) texts.Add(("number", Jx.Rewrite(jf!, at, _ => text))); + texts.Add(("string", Jx.Rewrite(jf!, at, _ => "\"" + text + "\""))); + texts.Add(("escaped string", Jx.Rewrite(jf!, at, _ => "\"" + Bs + "u00" + ((int)text[0]).ToString("x2") + text[1..] + "\""))); + // The baseline reads the first form with the serializer's own decimal: it shows whether these options read the member at all. + var (vb, eb) = Jx.De(texts[0].Json, hp.Declared, ob); + leftOut = eb is null && Held(vb, hp.Declared, d).Count == 0; + foreach (var (name, json) in texts) + { + var (vp, ep) = Jx.De(json, hp.Declared, o); + var (vv, ev) = ownVo ? (null, null) : Jx.De(json, hv.Declared, o); + var both = ep is not null && eb is not null && (ownVo || ev is not null) && ep.GetType() == eb.GetType(); + var cp = ep is null ? Held(vp, hp.Declared, d) : []; var cv = ownVo ? cp : ev is null ? Held(vv, hv.Declared, d) : []; + forms.Add(new(name, json, ep, ev, cp, cv, both)); + } + if (leftOut && forms.All(f => f.Ep is null && f.Ev is null && f.Cp.Count == 0 && f.Cv.Count == 0)) + readNothing = "read: the options read no member, for the serializer's own decimal too"; + } + string? ReadLeftOut(Check c) { c.Note(NoteKeys.JsonMemberLeftOutByOptions, "read"); return c.Inconclusive(readNothing!); } + // The forms both sides refuse alike under these options (the serializer's own decimal too) have nothing to compare. + var compared = forms.Where(f => !f.BothRefused).ToList(); + string? AllRefused(Check c) + { + foreach (var f in forms.Where(f => f.BothRefused)) c.Log.AppendLine($"read {f.Name}: both refuse: {Jx.Ex(f.Ep!)}"); + return c.Inconclusive("read: every form is refused for the serializer's own decimal too: " + Jx.Ex(forms[0].Ep!)); + } + var f4 = r.Check(Reads, docs, c => + { + if (readNothing is not null) return ReadLeftOut(c); + foreach (var f in forms) + { + if (f.BothRefused) continue; + if (f.Ep is not null) return $"read {f.Name}: the decimal with {conv.GetType().Name} threw {Jx.Ex(f.Ep)}\n text={Jx.Cut(f.Json)}"; + if (leftOut) { if (f.Cp.Count != 0) return $"read {f.Name}: the options read no member for the serializer's own decimal, yet it read {Show(f.Cp)}"; continue; } + if (f.Cp.Count == 0) return $"read {f.Name}: the decimal read nothing\n text={Jx.Cut(f.Json)}"; + } + if (compared.Count == 0) return AllRefused(c); + c.Note(NoteKeys.Compared, "read " + string.Join(", ", compared.Select(f => f.Name))); + return null; + }); + var f5 = r.Check(Keeps, docs, c => + { + if (readNothing is not null) return ReadLeftOut(c); + if (compared.Count == 0) return AllRefused(c); + var kept = 0; + foreach (var f in compared) + { + if (f.Cp.Count == 0) continue; // read nothing, or refused: reads-a-number-or-a-string judges it + if (f.Cp.Any(x => !Same(x, d))) return $"read {f.Name}: read {Show(f.Cp)}, expected {d.ToString(Inv)} with its scale\n text={Jx.Cut(f.Json)}"; + kept++; + } + if (kept == 0) return c.Inconclusive("read: no form read a decimal to hold its scale to"); + c.Note(NoteKeys.Compared, $"read with the scale of {text}"); + return null; + }); + var f3 = r.Check(Covers, ownVo ? Baseline.RoundTrip : Baseline.Twin, c => + { + if (xv is not null && !ownVo) return $"write: the value object threw {Jx.Ex(xv)} where the decimal wrote {Jx.Cut(jp)}"; + if (xp is not null) return c.Inconclusive("write: the decimal with the converter threw, judged by smallest-scale"); + if (tb is not null && tb.Count != 0 && tp.Count != tb.Count) return $"write: {tp.Count} decimals in {Jx.Cut(jp)}, {tb.Count} in {Jx.Cut(jb)}"; + foreach (var t in tp.Where(t => t.Type == JsonTokenType.PropertyName)) + if (t.Raw != canon) return $"write: key {t.Raw}, expected {canon}, in {Jx.Cut(jp)}"; + if (ownVo) + { + // "Your own converter wins" (AspNetCore README#what-the-document-says): the application's converter writes the value object, + // so the decimal's document is none of its; its own document reads back under the same options + c.Note(NoteKeys.OwnVo); + var (_, xr) = Jx.De(jv!, hv.Declared, o); + if (xr is not null) return "own converter for the value object: its document does not read back: " + Jx.Ex(xr); + return null; + } + if (jv != jp) return $"write: the value object wrote {Jx.Cut(jv)}, the decimal {Jx.Cut(jp)}"; + foreach (var f in compared) + { + if (f.Ep is not null) continue; // the decimal refused it: reads-a-number-or-a-string judges it + if (f.Ev is not null) return $"read {f.Name}: the value object threw {Jx.Ex(f.Ev)} where the decimal read it\n text={Jx.Cut(f.Json)}"; + if (f.Cp.Count != f.Cv.Count || f.Cp.Zip(f.Cv).Any(z => !Same(z.First, z.Second))) return $"read {f.Name}: the value object read {Show(f.Cv)}, the decimal {Show(f.Cp)}\n text={Jx.Cut(f.Json)}"; + } + if (writeLeftOut && (readNothing is not null || compared.Count == 0)) return WriteLeftOut(c); + c.Note(NoteKeys.Compared, $"{(number ? "number" : "string")} {text} -> {canon}, the value object's document the decimal's"); + return null; + }); + return f1 ?? f2 ?? f3 ?? f4 ?? f5; + } +} diff --git a/tools/probe/corpus/harness/EfExtra.cs b/tools/probe/corpus/harness/EfExtra.cs new file mode 100644 index 00000000..26ef7def --- /dev/null +++ b/tools/probe/corpus/harness/EfExtra.cs @@ -0,0 +1,48 @@ +// Entity shapes and helpers for the EF claims E07, E13, E14, E15 (reuse EfHarness.cs: EfDb, EfModel, Q, EfRegistry, KeyRegistry). +#nullable enable +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Probes.Shapes; +namespace Probes.Ef; + +public class EntL<T> where T : notnull { public int Id { get; set; } public List<T> Items { get; set; } = []; } +public class EntComp<TA, TB> where TA : notnull where TB : notnull { public TA A { get; set; } = default!; public TB B { get; set; } = default!; public string Tag { get; set; } = ""; } +public abstract class RootE<T> where T : notnull { public T Id { get; set; } = default!; public string Name { get; set; } = ""; } +public class LeafA<T> : RootE<T> where T : notnull { public int X { get; set; } } +public class LeafB<T> : RootE<T> where T : notnull { public int Y { get; set; } } + +public static class EfX +{ + /// <summary>The i-th distinct plain value of a wrapped type.</summary> + public static object Sample(string wrapped, int i) => wrapped switch + { + "string" => "val-" + (char)('a' + i), + "int" => 10 + i, + "long" => 4_000_000_000L + i, + "Guid" => new Guid(i + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + "decimal" => 1.5m + i, + _ => throw new ArgumentException(wrapped), + }; + public static EfEntry Top(string kind, string wrapped) => EfRegistry.All.FirstOrDefault(x => x.Kind == kind && x.Wrapped == wrapped && x.Site == "top-level") + ?? EfRegistry.All.First(x => (x.Kind is "record-struct" or "sealed-record-class") && x.Wrapped == wrapped && x.Site == "top-level"); + public static readonly string[] Kinds = ["record-struct", "sealed-record-class", "validated-class", "validated-struct"]; + public static object New(Type t) => Activator.CreateInstance(t)!; + public static void Set(object o, string prop, object? v) => o.GetType().GetProperty(prop)!.SetValue(o, v); + public static object? Get(object o, string prop) => o.GetType().GetProperty(prop)!.GetValue(o); + public static string Plainify(object? k) + { + if (k is not null && k.GetType().Namespace == "Probes.Ef") k = k.GetType().GetProperty("Value")!.GetValue(k); + return Convert.ToString(k, System.Globalization.CultureInfo.InvariantCulture) ?? "null"; + } + public static IEnumerable<object> Enumerate(IQueryable q) { foreach (var o in q) yield return o!; } + public static async Task<string?> RawScalar(EfDb db, string sql) + { + await using var cmd = db.Connection.CreateCommand(); cmd.CommandText = sql; + return Convert.ToString(await cmd.ExecuteScalarAsync(), System.Globalization.CultureInfo.InvariantCulture); + } + public static async Task Raw(EfDb db, string sql) + { + await using var cmd = db.Connection.CreateCommand(); cmd.CommandText = sql; await cmd.ExecuteNonQueryAsync(); + } +} diff --git a/tools/probe/corpus/harness/EfExtra2.cs b/tools/probe/corpus/harness/EfExtra2.cs new file mode 100644 index 00000000..2cffd4cf --- /dev/null +++ b/tools/probe/corpus/harness/EfExtra2.cs @@ -0,0 +1,82 @@ +// Shared by the EF claims E06, E11, E12, E16 (after EfHarness.cs / EfExtra.cs): a context with ConventionS and the order of UseTypeKit, +// and an application-owned converter ("C:" + the plain value as text) for any type. +#nullable enable +using System.Globalization; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Probes.Shapes; +namespace Probes.Ef; + +public sealed class DynCtx2(DbContextOptions options, Action<ModelBuilder> configure, Action<ModelConfigurationBuilder>? conventions) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => configure(modelBuilder); + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) => conventions?.Invoke(configurationBuilder); +} + +public sealed class EfDb2 : IAsyncDisposable +{ + public SqliteConnection Connection { get; } + public DynCtx2 Context { get; } + // order: "none" (no UseTypeKit), "before-provider" or "after-provider". + public EfDb2(Action<ModelBuilder> configure, string order, Action<ModelConfigurationBuilder>? conventions = null) + { + Connection = new SqliteConnection("Data Source=:memory:"); + Connection.Open(); + var b = new DbContextOptionsBuilder<DynCtx2>(); + if (order == "before-provider") b.UseTypeKit(); + b.UseSqlite(Connection); + if (order == "after-provider") b.UseTypeKit(); + b.ReplaceService<IModelCacheKeyFactory, UniqueModelKey>(); + Context = new DynCtx2(b.Options, configure, conventions); + } + public async ValueTask DisposeAsync() { await Context.DisposeAsync(); await Connection.DisposeAsync(); } +} + +/// <summary>An application-owned conversion of T to text: "C:" + the plain value. T is the value object or its wrapped type.</summary> +public static class OwnConv<T> +{ + public static Func<T, string> ToF = _ => throw new InvalidOperationException("OwnConv not set up"); + public static Func<string, T> FromF = _ => throw new InvalidOperationException("OwnConv not set up"); + public static string To(T v) => ToF(v); + public static T From(string s) => FromF(s); + public static void Setup(string wrapped, Func<object, object> make, Func<object, object> plain) + { + ToF = v => "C:" + Fmt(plain(v!)); + FromF = s => (T)make(Parse(wrapped, s[2..])); + } + public static string Fmt(object p) => Convert.ToString(p, CultureInfo.InvariantCulture)!; + public static object Parse(string wrapped, string s) => wrapped switch + { + "string" => s, "int" => int.Parse(s, CultureInfo.InvariantCulture), "long" => long.Parse(s, CultureInfo.InvariantCulture), + "Guid" => Guid.Parse(s), "decimal" => decimal.Parse(s, CultureInfo.InvariantCulture), "char" => s[0], + _ => throw new ArgumentException(wrapped), + }; +} + +public sealed class OwnConverter<T>() : ValueConverter<T, string>(v => OwnConv<T>.To(v), s => OwnConv<T>.From(s)); + +public static class OwnConvs +{ + /// <summary>Sets OwnConv up for the value object (voSide) or for its wrapped type, returns the converter type.</summary> + public static Type Setup(EfEntry e, bool vo) + { + if (vo) + { + typeof(OwnConv<>).MakeGenericType(e.Vo).GetMethod("Setup")!.Invoke(null, [e.Wrapped, (Func<object, object>)e.MakeVo, (Func<object, object>)(v => v.GetType().GetProperty("Value")!.GetValue(v)!)]); + return typeof(OwnConverter<>).MakeGenericType(e.Vo); + } + typeof(OwnConv<>).MakeGenericType(e.Plain).GetMethod("Setup")!.Invoke(null, [e.Wrapped, (Func<object, object>)(p => p), (Func<object, object>)(p => p)]); + return typeof(OwnConverter<>).MakeGenericType(e.Plain); + } +} + +public static class EfX2 +{ + public static async Task<string?> RawScalar(SqliteConnection c, string sql) + { + await using var cmd = c.CreateCommand(); cmd.CommandText = sql; + return Convert.ToString(await cmd.ExecuteScalarAsync(), CultureInfo.InvariantCulture); + } +} diff --git a/tools/probe/corpus/harness/EfHarness.cs b/tools/probe/corpus/harness/EfHarness.cs new file mode 100644 index 00000000..c6de75a1 --- /dev/null +++ b/tools/probe/corpus/harness/EfHarness.cs @@ -0,0 +1,191 @@ +// Shared EF row-applying code for the EF satellite claims (surface ef): the entity shapes, a DbContext built from a +// configuration delegate, the model descriptor and the query helpers. Written once; later EF claims reuse it. +#nullable enable +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Probes.Shapes; +namespace Probes.Ef; + +public sealed record EfEntry(string Kind, string Wrapped, string Site, Type Vo, Type Plain, Func<object> Valid) +{ + /// <summary>The value object around a plain value, by its public seams: From, else FromKnownGood.</summary> + public object MakeVo(object plain) + { + var from = Vo.GetMethod("From", BindingFlags.Public | BindingFlags.Static, [Plain]); + if (from is not null) return from.Invoke(null, [plain])!; + var known = Vo.GetMethod("FromKnownGood", BindingFlags.Public | BindingFlags.Static, [Plain, typeof(string)]); + return known is null ? throw new InvalidOperationException($"{Vo} has neither From nor FromKnownGood") : known.Invoke(null, [plain, null])!; + } + + /// <summary>The generated companion extension GetValue / ValueOrNull of this value object (a static class in its namespace; the first parameter is the value object, or its Nullable for ValueOrNull).</summary> + public MethodInfo? Companion(string name) + { + var first = name == "ValueOrNull" && Vo.IsValueType ? typeof(Nullable<>).MakeGenericType(Vo) : Vo; + return Vo.Assembly.GetTypes().Where(t => t.IsAbstract && t.IsSealed && t.Namespace == Vo.Namespace) + .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static)) + .FirstOrDefault(m => m.Name == name && m.GetParameters() is [var p] && p.ParameterType == first); + } +} + +public static partial class EfRegistry +{ + public static readonly List<EfEntry> All = Build(); + static List<EfEntry> Build() { var e = new List<EfEntry>(); Fill(e); return e; } + static partial void Fill(List<EfEntry> e); + public static EfEntry Of(Row r) { var k = r["shape.kind"]; var w = r["shape.wrapped"]; var s = r["shape.site"]; return All.First(x => x.Kind == k && x.Wrapped == w && x.Site == s); } +} + +public class EntP<T> where T : notnull { public int Id { get; set; } public T P { get; set; } = default!; } +public class EntNC<T> where T : class { public int Id { get; set; } public T? P { get; set; } } +public class EntK<T> where T : notnull { public T Id { get; set; } = default!; public string Tag { get; set; } = ""; } +public class Own<T> where T : notnull { public T P { get; set; } = default!; } +public class EntO<T> where T : notnull { public int Id { get; set; } public Own<T> O { get; set; } = new(); } +public class EntC<T> where T : notnull { public int Id { get; set; } public Own<T> C { get; set; } = new(); } +public sealed class Box<T> { public T Value = default!; } + +public sealed class UniqueModelKey : Microsoft.EntityFrameworkCore.Infrastructure.IModelCacheKeyFactory +{ + public object Create(DbContext context, bool designTime) => new object(); +} + +public sealed class DynCtx(DbContextOptions options, Action<ModelBuilder> configure) : DbContext(options) +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) => configure(modelBuilder); +} + +public sealed class EfDb : IAsyncDisposable +{ + public SqliteConnection Connection { get; } + public DynCtx Context { get; } + public EfDb(Action<ModelBuilder> configure, bool typeKit, Action<DbContextOptionsBuilder>? extra = null) + { + Connection = new SqliteConnection("Data Source=:memory:"); + Connection.Open(); + var b = new DbContextOptionsBuilder<DynCtx>().UseSqlite(Connection).ReplaceService<IModelCacheKeyFactory, UniqueModelKey>(); + if (typeKit) b.UseTypeKit(); + extra?.Invoke(b); + Context = new DynCtx(b.Options, configure); + } + public async ValueTask DisposeAsync() { await Context.DisposeAsync(); await Connection.DisposeAsync(); } +} + +public static class EfModel +{ + public static Type Entity(string mapping, Type t) => mapping switch + { + "property" => typeof(EntP<>).MakeGenericType(t), + "nullable" => t.IsValueType ? typeof(EntP<>).MakeGenericType(typeof(Nullable<>).MakeGenericType(t)) : typeof(EntNC<>).MakeGenericType(t), + "key" => typeof(EntK<>).MakeGenericType(t), + "owned" => typeof(EntO<>).MakeGenericType(t), + "complex" => typeof(EntC<>).MakeGenericType(t), + _ => throw new ArgumentException("mapping " + mapping), + }; + + public static readonly Dictionary<string, string> ColumnTypes = new() + { + ["string"] = "VARCHAR(40)", ["Uri"] = "VARCHAR(40)", ["Guid"] = "UNIQUEIDENTIFIER", ["int"] = "BIGINT", ["long"] = "BIGINT", ["byte"] = "TINYINT", + ["ulong"] = "BIGINT", ["decimal"] = "NUMERIC(10,2)", ["double"] = "FLOAT", ["bool"] = "BIT", ["char"] = "CHAR(1)", ["DateTime"] = "DATETIME", + ["DateTimeOffset"] = "DATETIMEOFFSET", ["DateOnly"] = "DATE", ["TimeOnly"] = "TIME", ["TimeSpan"] = "TIME", ["enum"] = "SMALLINT", ["flags-enum"] = "SMALLINT", + }; + + /// <summary>Maps the entity of a mapping in table "t", applying a facet to the column property (and UseAutoincrement to an integer key when asked).</summary> + public static Action<ModelBuilder> Configure(string mapping, Type entity, Action<IMutableProperty>? facet, bool autoincrement) => mb => + { + var eb = mb.Entity(entity); + eb.ToTable("t"); + var t = entity.GetGenericArguments()[0]; + switch (mapping) + { + case "property" or "nullable": eb.HasKey("Id"); if (facet is not null) facet(eb.Property("P").Metadata); break; + case "key": + eb.HasKey("Id"); + var pb = eb.Property("Id"); + facet?.Invoke(pb.Metadata); + if (autoincrement) Microsoft.EntityFrameworkCore.SqlitePropertyBuilderExtensions.UseAutoincrement(pb); + break; + case "owned": + eb.HasKey("Id"); + eb.OwnsOne(typeof(Own<>).MakeGenericType(t), "O", ob => { if (facet is not null) facet(ob.Property("P").Metadata); }); + break; + case "complex": + eb.HasKey("Id"); + eb.ComplexProperty("C", cb => { if (facet is not null) facet(cb.Property("P").Metadata); }); + break; + } + }; + + /// <summary>What the model says about every mapped column: store type, nullability, facets, key and generation.</summary> + public static string Describe(DbContext db) + { + var model = db.GetService<IDesignTimeModel>().Model; + var lines = new List<string>(); + string Line(string table, string prefix, IProperty p) + { + string col = "?"; try { col = ((IReadOnlyProperty)p).GetColumnType() ?? "?"; } catch { try { col = Microsoft.EntityFrameworkCore.RelationalPropertyExtensions.GetRelationalTypeMapping(p).StoreType; } catch { col = "?"; } } + string nul; try { nul = p.IsColumnNullable().ToString(); } catch { nul = p.IsNullable.ToString(); } + return $"{table}.{prefix}{p.Name}: type={col} nullable={nul} max={p.GetMaxLength()} prec={p.GetPrecision()} scale={p.GetScale()} unicode={p.IsUnicode()} pk={p.IsPrimaryKey()} gen={p.ValueGenerated}"; + } + foreach (var et in model.GetEntityTypes()) + { + var table = et.GetTableName() ?? "(none)"; + foreach (var p in et.GetProperties()) lines.Add(Line(table, "", p)); + foreach (var cp in et.GetComplexProperties()) foreach (var p in cp.ComplexType.GetProperties()) lines.Add(Line(table, cp.Name + ".", p)); + } + lines.Sort(StringComparer.Ordinal); + return string.Join("\n", lines); + } + + public static string FirstDiff(string a, string b) + { + var x = a.Split('\n'); var y = b.Split('\n'); + for (var i = 0; i < Math.Max(x.Length, y.Length); i++) + if ((i < x.Length ? x[i] : "<none>") != (i < y.Length ? y[i] : "<none>")) return $"VO: {(i < x.Length ? x[i] : "<none>")} | plain: {(i < y.Length ? y[i] : "<none>")}"; + return ""; + } +} + +/// <summary> +/// How a check of a two-sided EF row ends where a side threw: the value object's context under <c>UseTypeKit()</c> and the +/// wrapped twin's under plain EF ran the same step, named by <c>what</c>. Neither threw: false, compare on. The value object +/// alone: its violation. Both, with exceptions of one type: nothing to compare, inconclusive, since no fact or sentence says why +/// the framework refuses it. Both, otherwise: a violation, since the value object did not fail as its twin does. The twin alone: +/// inconclusive, no twin to compare with. +/// </summary> +public static class EfSides +{ + public static bool Threw(Check c, Exception? vo, Exception? plain, string what, out string? failure) + { + failure = null; + if (vo is null && plain is null) return false; + if (vo is not null && plain is not null) + failure = vo.GetType() == plain.GetType() + ? c.Inconclusive($"both sides refuse {what} alike: {vo.GetType().Name}: {Short(vo.Message)}") + : $"{what}: the value object's side threw {vo.GetType().Name}: {Short(vo.Message)}; the wrapped type's {plain.GetType().Name}: {Short(plain.Message)}"; + else if (plain is not null) failure = c.Inconclusive($"the wrapped type's side refuses {what}, so there is no twin to compare with: {plain.GetType().Name}: {Short(plain.Message)}"); + else failure = $"{what} with the value object threw {vo!.GetType().Name}: {Short(vo.Message)}; the wrapped type's did not"; + return true; + } + + static string Short(string s) { s = s.Split('\n')[0]; return s.Length > 300 ? s[..300] : s; } +} + +public static class Q +{ + public static IQueryable Call(string name, Type[] typeArgs, IQueryable src, params Expression[] args) + { + static bool OneParamLambda(ParameterInfo p) => + p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(Expression<>) && p.ParameterType.GetGenericArguments()[0].GetGenericArguments().Length == 2; + var m = typeof(Queryable).GetMethods().First(x => x.Name == name && x.IsGenericMethodDefinition && x.GetGenericArguments().Length == typeArgs.Length + && x.GetParameters().Length == args.Length + 1 && x.GetParameters().Skip(1).All(OneParamLambda)); + return (IQueryable)m.MakeGenericMethod(typeArgs).Invoke(null, [src, .. args.Select(a => (object)a)])!; + } + public static IQueryable SetOf(DbContext db, Type entity) => + (IQueryable)typeof(DbContext).GetMethods().First(m => m.Name == "Set" && m.GetParameters().Length == 0).MakeGenericMethod(entity).Invoke(db, null)!; + public static object NewBox(Type t, object? value) { var b = Activator.CreateInstance(typeof(Box<>).MakeGenericType(t))!; typeof(Box<>).MakeGenericType(t).GetField("Value")!.SetValue(b, value); return b; } + public static string Sql(IQueryable q) => q.ToQueryString(); +} diff --git a/tools/probe/corpus/harness/EfShapes.g.cs b/tools/probe/corpus/harness/EfShapes.g.cs new file mode 100644 index 00000000..95fa592f --- /dev/null +++ b/tools/probe/corpus/harness/EfShapes.g.cs @@ -0,0 +1,962 @@ +// <auto-generated/> by tools/probe/generators/ef.cs catalogue; the EF probes' shape catalogue (shape.kind x shape.wrapped x shape.site). Do not edit. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using Probes.Shapes; +namespace Probes.Ef; +public struct S_E_record_struct_DateOnly +{ +public readonly partial record struct V : IValue<DateOnly>; +} +public static class C2_E_record_struct_DateOnly +{ + public static class Inner + { +public readonly partial record struct V : IValue<DateOnly>; + } +} +public readonly partial record struct E_record_struct_DateOnly : IValue<DateOnly>; +public static class C_E_record_struct_DateTime +{ +public readonly partial record struct V : IValue<DateTime>; +} +public static class C2_E_record_struct_DateTime +{ + public static class Inner + { +public readonly partial record struct V : IValue<DateTime>; + } +} +public readonly partial record struct E_record_struct_DateTime : IValue<DateTime>; +public readonly partial record struct E_record_struct_DateTimeOffset : IValue<DateTimeOffset>; +public static class C_E_record_struct_Guid +{ +public readonly partial record struct V : IValue<Guid>; +} +public static class C2_E_record_struct_Guid +{ + public static class Inner + { +public readonly partial record struct V : IValue<Guid>; + } +} +public readonly partial record struct E_record_struct_Guid : IValue<Guid>; +public static class C2_E_record_struct_TimeOnly +{ + public static class Inner + { +public readonly partial record struct V : IValue<TimeOnly>; + } +} +public static class C_E_record_struct_TimeSpan +{ +public readonly partial record struct V : IValue<TimeSpan>; +} +public struct S_E_record_struct_TimeSpan +{ +public readonly partial record struct V : IValue<TimeSpan>; +} +public static class C_E_record_struct_Uri +{ +public readonly partial record struct V : IValue<Uri>; +} +public static class C2_E_record_struct_Uri +{ + public static class Inner + { +public readonly partial record struct V : IValue<Uri>; + } +} +public readonly partial record struct E_record_struct_Uri : IValue<Uri>; +public struct S_E_record_struct_bool +{ +public readonly partial record struct V : IValue<bool>; +} +public readonly partial record struct E_record_struct_bool : IValue<bool>; +public static class C2_E_record_struct_byte +{ + public static class Inner + { +public readonly partial record struct V : IValue<byte>; + } +} +public readonly partial record struct E_record_struct_byte : IValue<byte>; +public static class C_E_record_struct_char +{ +public readonly partial record struct V : IValue<char>; +} +public struct S_E_record_struct_char +{ +public readonly partial record struct V : IValue<char>; +} +public readonly partial record struct E_record_struct_char : IValue<char>; +public struct S_E_record_struct_decimal +{ +public readonly partial record struct V : IValue<decimal>; +} +public static class C2_E_record_struct_decimal +{ + public static class Inner + { +public readonly partial record struct V : IValue<decimal>; + } +} +public readonly partial record struct E_record_struct_decimal : IValue<decimal>; +public struct S_E_record_struct_double +{ +public readonly partial record struct V : IValue<double>; +} +public static class C2_E_record_struct_enum +{ + public static class Inner + { +public readonly partial record struct V : IValue<PEnum>; + } +} +public readonly partial record struct E_record_struct_enum : IValue<PEnum>; +public static class C2_E_record_struct_flags_enum +{ + public static class Inner + { +public readonly partial record struct V : IValue<PFlags>; + } +} +public readonly partial record struct E_record_struct_flags_enum : IValue<PFlags>; +public static class C2_E_record_struct_int +{ + public static class Inner + { +public readonly partial record struct V : IValue<int>; + } +} +public readonly partial record struct E_record_struct_int : IValue<int>; +public static class C2_E_record_struct_long +{ + public static class Inner + { +public readonly partial record struct V : IValue<long>; + } +} +public readonly partial record struct E_record_struct_long : IValue<long>; +public static class C_E_record_struct_string +{ +public readonly partial record struct V : IValue<string>; +} +public struct S_E_record_struct_ulong +{ +public readonly partial record struct V : IValue<ulong>; +} +public struct S_E_sealed_record_class_DateOnly +{ +public sealed partial record class V : IValue<DateOnly>; +} +public static class C2_E_sealed_record_class_DateOnly +{ + public static class Inner + { +public sealed partial record class V : IValue<DateOnly>; + } +} +public struct S_E_sealed_record_class_DateTime +{ +public sealed partial record class V : IValue<DateTime>; +} +public static class C2_E_sealed_record_class_DateTime +{ + public static class Inner + { +public sealed partial record class V : IValue<DateTime>; + } +} +public static class C_E_sealed_record_class_DateTimeOffset +{ +public sealed partial record class V : IValue<DateTimeOffset>; +} +public struct S_E_sealed_record_class_DateTimeOffset +{ +public sealed partial record class V : IValue<DateTimeOffset>; +} +public static class C2_E_sealed_record_class_DateTimeOffset +{ + public static class Inner + { +public sealed partial record class V : IValue<DateTimeOffset>; + } +} +public static class C2_E_sealed_record_class_Guid +{ + public static class Inner + { +public sealed partial record class V : IValue<Guid>; + } +} +public static class C_E_sealed_record_class_TimeOnly +{ +public sealed partial record class V : IValue<TimeOnly>; +} +public struct S_E_sealed_record_class_TimeOnly +{ +public sealed partial record class V : IValue<TimeOnly>; +} +public static class C2_E_sealed_record_class_TimeOnly +{ + public static class Inner + { +public sealed partial record class V : IValue<TimeOnly>; + } +} +public sealed partial record class E_sealed_record_class_TimeSpan : IValue<TimeSpan>; +public static class C_E_sealed_record_class_Uri +{ +public sealed partial record class V : IValue<Uri>; +} +public struct S_E_sealed_record_class_Uri +{ +public sealed partial record class V : IValue<Uri>; +} +public static class C2_E_sealed_record_class_Uri +{ + public static class Inner + { +public sealed partial record class V : IValue<Uri>; + } +} +public static class C2_E_sealed_record_class_bool +{ + public static class Inner + { +public sealed partial record class V : IValue<bool>; + } +} +public static class C_E_sealed_record_class_byte +{ +public sealed partial record class V : IValue<byte>; +} +public struct S_E_sealed_record_class_byte +{ +public sealed partial record class V : IValue<byte>; +} +public struct S_E_sealed_record_class_char +{ +public sealed partial record class V : IValue<char>; +} +public sealed partial record class E_sealed_record_class_char : IValue<char>; +public static class C_E_sealed_record_class_decimal +{ +public sealed partial record class V : IValue<decimal>; +} +public static class C2_E_sealed_record_class_decimal +{ + public static class Inner + { +public sealed partial record class V : IValue<decimal>; + } +} +public static class C2_E_sealed_record_class_double +{ + public static class Inner + { +public sealed partial record class V : IValue<double>; + } +} +public sealed partial record class E_sealed_record_class_double : IValue<double>; +public sealed partial record class E_sealed_record_class_enum : IValue<PEnum>; +public static class C_E_sealed_record_class_flags_enum +{ +public sealed partial record class V : IValue<PFlags>; +} +public sealed partial record class E_sealed_record_class_flags_enum : IValue<PFlags>; +public static class C_E_sealed_record_class_int +{ +public sealed partial record class V : IValue<int>; +} +public struct S_E_sealed_record_class_int +{ +public sealed partial record class V : IValue<int>; +} +public static class C_E_sealed_record_class_long +{ +public sealed partial record class V : IValue<long>; +} +public static class C_E_sealed_record_class_string +{ +public sealed partial record class V : IValue<string>; +} +public struct S_E_sealed_record_class_string +{ +public sealed partial record class V : IValue<string>; +} +public sealed partial record class E_sealed_record_class_string : IValue<string>; +public static class C_E_sealed_record_class_ulong +{ +public sealed partial record class V : IValue<ulong>; +} +public struct S_E_validated_class_DateOnly +{ +public sealed partial record class V : IValidatedValue<V, DateOnly, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateOnly value) => + EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_class_DateTimeOffset +{ +public sealed partial record class V : IValidatedValue<V, DateTimeOffset, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateTimeOffset value) => + EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C_E_validated_class_Guid +{ +public sealed partial record class V : IValidatedValue<V, Guid, ShapeFault> +{ + public static Result<V, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_class_Guid +{ +public sealed partial record class V : IValidatedValue<V, Guid, ShapeFault> +{ + public static Result<V, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public sealed partial record class E_validated_class_Guid : IValidatedValue<E_validated_class_Guid, Guid, ShapeFault> +{ + public static Result<E_validated_class_Guid, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new E_validated_class_Guid(value); +} +public static class C_E_validated_class_TimeOnly +{ +public sealed partial record class V : IValidatedValue<V, TimeOnly, ShapeFault> +{ + public static Result<V, ShapeFault> Create(TimeOnly value) => + EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public sealed partial record class E_validated_class_TimeOnly : IValidatedValue<E_validated_class_TimeOnly, TimeOnly, ShapeFault> +{ + public static Result<E_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => + EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new E_validated_class_TimeOnly(value); +} +public static class C2_E_validated_class_TimeSpan +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, TimeSpan, ShapeFault> +{ + public static Result<V, ShapeFault> Create(TimeSpan value) => + EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public struct S_E_validated_class_Uri +{ +public sealed partial record class V : IValidatedValue<V, Uri, ShapeFault> +{ + public static Result<V, ShapeFault> Create(Uri value) => + EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C_E_validated_class_bool +{ +public sealed partial record class V : IValidatedValue<V, bool, ShapeFault> +{ + public static Result<V, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_class_bool +{ +public sealed partial record class V : IValidatedValue<V, bool, ShapeFault> +{ + public static Result<V, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public sealed partial record class E_validated_class_bool : IValidatedValue<E_validated_class_bool, bool, ShapeFault> +{ + public static Result<E_validated_class_bool, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new E_validated_class_bool(value); +} +public static class C_E_validated_class_byte +{ +public sealed partial record class V : IValidatedValue<V, byte, ShapeFault> +{ + public static Result<V, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public sealed partial record class E_validated_class_byte : IValidatedValue<E_validated_class_byte, byte, ShapeFault> +{ + public static Result<E_validated_class_byte, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new E_validated_class_byte(value); +} +public static class C_E_validated_class_char +{ +public sealed partial record class V : IValidatedValue<V, char, ShapeFault> +{ + public static Result<V, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_char +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, char, ShapeFault> +{ + public static Result<V, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static class C_E_validated_class_double +{ +public sealed partial record class V : IValidatedValue<V, double, ShapeFault> +{ + public static Result<V, ShapeFault> Create(double value) => + EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_double +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, double, ShapeFault> +{ + public static Result<V, ShapeFault> Create(double value) => + EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public struct S_E_validated_class_enum +{ +public sealed partial record class V : IValidatedValue<V, PEnum, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PEnum value) => + EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_enum +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, PEnum, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PEnum value) => + EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static class C_E_validated_class_flags_enum +{ +public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PFlags value) => + EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_class_flags_enum +{ +public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PFlags value) => + EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_flags_enum +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PFlags value) => + EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static class C2_E_validated_class_int +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, int, ShapeFault> +{ + public static Result<V, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public sealed partial record class E_validated_class_int : IValidatedValue<E_validated_class_int, int, ShapeFault> +{ + public static Result<E_validated_class_int, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new E_validated_class_int(value); +} +public struct S_E_validated_class_long +{ +public sealed partial record class V : IValidatedValue<V, long, ShapeFault> +{ + public static Result<V, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_long +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, long, ShapeFault> +{ + public static Result<V, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public struct S_E_validated_class_string +{ +public sealed partial record class V : IValidatedValue<V, string, ShapeFault> +{ + public static Result<V, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_class_string +{ + public static class Inner + { +public sealed partial record class V : IValidatedValue<V, string, ShapeFault> +{ + public static Result<V, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public struct S_E_validated_class_ulong +{ +public sealed partial record class V : IValidatedValue<V, ulong, ShapeFault> +{ + public static Result<V, ShapeFault> Create(ulong value) => + EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public sealed partial record class E_validated_class_ulong : IValidatedValue<E_validated_class_ulong, ulong, ShapeFault> +{ + public static Result<E_validated_class_ulong, ShapeFault> Create(ulong value) => + EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new E_validated_class_ulong(value); +} +public static class C_E_validated_struct_DateOnly +{ +public readonly partial record struct V : IValidatedValue<V, DateOnly, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateOnly value) => + EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public readonly partial record struct E_validated_struct_DateOnly : IValidatedValue<E_validated_struct_DateOnly, DateOnly, ShapeFault> +{ + public static Result<E_validated_struct_DateOnly, ShapeFault> Create(DateOnly value) => + EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_DateOnly(value); +} +public static class C_E_validated_struct_DateTime +{ +public readonly partial record struct V : IValidatedValue<V, DateTime, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateTime value) => + EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_DateTime +{ +public readonly partial record struct V : IValidatedValue<V, DateTime, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateTime value) => + EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C_E_validated_struct_DateTimeOffset +{ +public readonly partial record struct V : IValidatedValue<V, DateTimeOffset, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateTimeOffset value) => + EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_struct_DateTimeOffset +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, DateTimeOffset, ShapeFault> +{ + public static Result<V, ShapeFault> Create(DateTimeOffset value) => + EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static class C_E_validated_struct_Guid +{ +public readonly partial record struct V : IValidatedValue<V, Guid, ShapeFault> +{ + public static Result<V, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_Guid +{ +public readonly partial record struct V : IValidatedValue<V, Guid, ShapeFault> +{ + public static Result<V, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_TimeOnly +{ +public readonly partial record struct V : IValidatedValue<V, TimeOnly, ShapeFault> +{ + public static Result<V, ShapeFault> Create(TimeOnly value) => + EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public readonly partial record struct E_validated_struct_TimeOnly : IValidatedValue<E_validated_struct_TimeOnly, TimeOnly, ShapeFault> +{ + public static Result<E_validated_struct_TimeOnly, ShapeFault> Create(TimeOnly value) => + EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_TimeOnly(value); +} +public static class C_E_validated_struct_TimeSpan +{ +public readonly partial record struct V : IValidatedValue<V, TimeSpan, ShapeFault> +{ + public static Result<V, ShapeFault> Create(TimeSpan value) => + EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public readonly partial record struct E_validated_struct_Uri : IValidatedValue<E_validated_struct_Uri, Uri, ShapeFault> +{ + public static Result<E_validated_struct_Uri, ShapeFault> Create(Uri value) => + EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_Uri(value); +} +public static class C_E_validated_struct_bool +{ +public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> +{ + public static Result<V, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_bool +{ +public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> +{ + public static Result<V, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_struct_bool +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> +{ + public static Result<V, ShapeFault> Create(bool value) => + EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public struct S_E_validated_struct_byte +{ +public readonly partial record struct V : IValidatedValue<V, byte, ShapeFault> +{ + public static Result<V, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_struct_byte +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, byte, ShapeFault> +{ + public static Result<V, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static class C2_E_validated_struct_char +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, char, ShapeFault> +{ + public static Result<V, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public readonly partial record struct E_validated_struct_char : IValidatedValue<E_validated_struct_char, char, ShapeFault> +{ + public static Result<E_validated_struct_char, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new E_validated_struct_char(value); +} +public static class C_E_validated_struct_decimal +{ +public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> +{ + public static Result<V, ShapeFault> Create(decimal value) => + EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_decimal +{ +public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> +{ + public static Result<V, ShapeFault> Create(decimal value) => + EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_struct_decimal +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> +{ + public static Result<V, ShapeFault> Create(decimal value) => + EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public readonly partial record struct E_validated_struct_decimal : IValidatedValue<E_validated_struct_decimal, decimal, ShapeFault> +{ + public static Result<E_validated_struct_decimal, ShapeFault> Create(decimal value) => + EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_decimal(value); +} +public static class C_E_validated_struct_double +{ +public readonly partial record struct V : IValidatedValue<V, double, ShapeFault> +{ + public static Result<V, ShapeFault> Create(double value) => + EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public readonly partial record struct E_validated_struct_double : IValidatedValue<E_validated_struct_double, double, ShapeFault> +{ + public static Result<E_validated_struct_double, ShapeFault> Create(double value) => + EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_double(value); +} +public static class C_E_validated_struct_enum +{ +public readonly partial record struct V : IValidatedValue<V, PEnum, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PEnum value) => + EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_flags_enum +{ +public readonly partial record struct V : IValidatedValue<V, PFlags, ShapeFault> +{ + public static Result<V, ShapeFault> Create(PFlags value) => + EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C_E_validated_struct_int +{ +public readonly partial record struct V : IValidatedValue<V, int, ShapeFault> +{ + public static Result<V, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public struct S_E_validated_struct_int +{ +public readonly partial record struct V : IValidatedValue<V, int, ShapeFault> +{ + public static Result<V, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); +} +} +public static class C2_E_validated_struct_long +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, long, ShapeFault> +{ + public static Result<V, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public readonly partial record struct E_validated_struct_long : IValidatedValue<E_validated_struct_long, long, ShapeFault> +{ + public static Result<E_validated_struct_long, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new E_validated_struct_long(value); +} +public static class C2_E_validated_struct_string +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, string, ShapeFault> +{ + public static Result<V, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public readonly partial record struct E_validated_struct_string : IValidatedValue<E_validated_struct_string, string, ShapeFault> +{ + public static Result<E_validated_struct_string, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new E_validated_struct_string(value); +} +public static class C2_E_validated_struct_ulong +{ + public static class Inner + { +public readonly partial record struct V : IValidatedValue<V, ulong, ShapeFault> +{ + public static Result<V, ShapeFault> Create(ulong value) => + EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); +} + } +} +public static partial class EfRegistry +{ + static partial void Fill(List<EfEntry> e) + { + e.Add(new EfEntry("record-struct", "DateOnly", "nested-in-struct", typeof(S_E_record_struct_DateOnly.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("record-struct", "DateOnly", "nested-two-levels", typeof(C2_E_record_struct_DateOnly.Inner.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("record-struct", "DateOnly", "top-level", typeof(E_record_struct_DateOnly), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("record-struct", "DateTime", "nested-in-class", typeof(C_E_record_struct_DateTime.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("record-struct", "DateTime", "nested-two-levels", typeof(C2_E_record_struct_DateTime.Inner.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("record-struct", "DateTime", "top-level", typeof(E_record_struct_DateTime), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("record-struct", "DateTimeOffset", "top-level", typeof(E_record_struct_DateTimeOffset), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("record-struct", "Guid", "nested-in-class", typeof(C_E_record_struct_Guid.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("record-struct", "Guid", "nested-two-levels", typeof(C2_E_record_struct_Guid.Inner.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("record-struct", "Guid", "top-level", typeof(E_record_struct_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("record-struct", "TimeOnly", "nested-two-levels", typeof(C2_E_record_struct_TimeOnly.Inner.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("record-struct", "TimeSpan", "nested-in-class", typeof(C_E_record_struct_TimeSpan.V), typeof(TimeSpan), () => TimeSpan.FromMinutes(90))); + e.Add(new EfEntry("record-struct", "TimeSpan", "nested-in-struct", typeof(S_E_record_struct_TimeSpan.V), typeof(TimeSpan), () => TimeSpan.FromMinutes(90))); + e.Add(new EfEntry("record-struct", "Uri", "nested-in-class", typeof(C_E_record_struct_Uri.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("record-struct", "Uri", "nested-two-levels", typeof(C2_E_record_struct_Uri.Inner.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("record-struct", "Uri", "top-level", typeof(E_record_struct_Uri), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("record-struct", "bool", "nested-in-struct", typeof(S_E_record_struct_bool.V), typeof(bool), () => true)); + e.Add(new EfEntry("record-struct", "bool", "top-level", typeof(E_record_struct_bool), typeof(bool), () => true)); + e.Add(new EfEntry("record-struct", "byte", "nested-two-levels", typeof(C2_E_record_struct_byte.Inner.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("record-struct", "byte", "top-level", typeof(E_record_struct_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("record-struct", "char", "nested-in-class", typeof(C_E_record_struct_char.V), typeof(char), () => 'a')); + e.Add(new EfEntry("record-struct", "char", "nested-in-struct", typeof(S_E_record_struct_char.V), typeof(char), () => 'a')); + e.Add(new EfEntry("record-struct", "char", "top-level", typeof(E_record_struct_char), typeof(char), () => 'a')); + e.Add(new EfEntry("record-struct", "decimal", "nested-in-struct", typeof(S_E_record_struct_decimal.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("record-struct", "decimal", "nested-two-levels", typeof(C2_E_record_struct_decimal.Inner.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("record-struct", "decimal", "top-level", typeof(E_record_struct_decimal), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("record-struct", "double", "nested-in-struct", typeof(S_E_record_struct_double.V), typeof(double), () => 1.5)); + e.Add(new EfEntry("record-struct", "enum", "nested-two-levels", typeof(C2_E_record_struct_enum.Inner.V), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("record-struct", "enum", "top-level", typeof(E_record_struct_enum), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("record-struct", "flags-enum", "nested-two-levels", typeof(C2_E_record_struct_flags_enum.Inner.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("record-struct", "flags-enum", "top-level", typeof(E_record_struct_flags_enum), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("record-struct", "int", "nested-two-levels", typeof(C2_E_record_struct_int.Inner.V), typeof(int), () => 42)); + e.Add(new EfEntry("record-struct", "int", "top-level", typeof(E_record_struct_int), typeof(int), () => 42)); + e.Add(new EfEntry("record-struct", "long", "nested-two-levels", typeof(C2_E_record_struct_long.Inner.V), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("record-struct", "long", "top-level", typeof(E_record_struct_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("record-struct", "string", "nested-in-class", typeof(C_E_record_struct_string.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("record-struct", "ulong", "nested-in-struct", typeof(S_E_record_struct_ulong.V), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("sealed-record-class", "DateOnly", "nested-in-struct", typeof(S_E_sealed_record_class_DateOnly.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("sealed-record-class", "DateOnly", "nested-two-levels", typeof(C2_E_sealed_record_class_DateOnly.Inner.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("sealed-record-class", "DateTime", "nested-in-struct", typeof(S_E_sealed_record_class_DateTime.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("sealed-record-class", "DateTime", "nested-two-levels", typeof(C2_E_sealed_record_class_DateTime.Inner.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("sealed-record-class", "DateTimeOffset", "nested-in-class", typeof(C_E_sealed_record_class_DateTimeOffset.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("sealed-record-class", "DateTimeOffset", "nested-in-struct", typeof(S_E_sealed_record_class_DateTimeOffset.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("sealed-record-class", "DateTimeOffset", "nested-two-levels", typeof(C2_E_sealed_record_class_DateTimeOffset.Inner.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("sealed-record-class", "Guid", "nested-two-levels", typeof(C2_E_sealed_record_class_Guid.Inner.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("sealed-record-class", "TimeOnly", "nested-in-class", typeof(C_E_sealed_record_class_TimeOnly.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("sealed-record-class", "TimeOnly", "nested-in-struct", typeof(S_E_sealed_record_class_TimeOnly.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("sealed-record-class", "TimeOnly", "nested-two-levels", typeof(C2_E_sealed_record_class_TimeOnly.Inner.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("sealed-record-class", "TimeSpan", "top-level", typeof(E_sealed_record_class_TimeSpan), typeof(TimeSpan), () => TimeSpan.FromMinutes(90))); + e.Add(new EfEntry("sealed-record-class", "Uri", "nested-in-class", typeof(C_E_sealed_record_class_Uri.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("sealed-record-class", "Uri", "nested-in-struct", typeof(S_E_sealed_record_class_Uri.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("sealed-record-class", "Uri", "nested-two-levels", typeof(C2_E_sealed_record_class_Uri.Inner.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("sealed-record-class", "bool", "nested-two-levels", typeof(C2_E_sealed_record_class_bool.Inner.V), typeof(bool), () => true)); + e.Add(new EfEntry("sealed-record-class", "byte", "nested-in-class", typeof(C_E_sealed_record_class_byte.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("sealed-record-class", "byte", "nested-in-struct", typeof(S_E_sealed_record_class_byte.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("sealed-record-class", "char", "nested-in-struct", typeof(S_E_sealed_record_class_char.V), typeof(char), () => 'a')); + e.Add(new EfEntry("sealed-record-class", "char", "top-level", typeof(E_sealed_record_class_char), typeof(char), () => 'a')); + e.Add(new EfEntry("sealed-record-class", "decimal", "nested-in-class", typeof(C_E_sealed_record_class_decimal.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("sealed-record-class", "decimal", "nested-two-levels", typeof(C2_E_sealed_record_class_decimal.Inner.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("sealed-record-class", "double", "nested-two-levels", typeof(C2_E_sealed_record_class_double.Inner.V), typeof(double), () => 1.5)); + e.Add(new EfEntry("sealed-record-class", "double", "top-level", typeof(E_sealed_record_class_double), typeof(double), () => 1.5)); + e.Add(new EfEntry("sealed-record-class", "enum", "top-level", typeof(E_sealed_record_class_enum), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("sealed-record-class", "flags-enum", "nested-in-class", typeof(C_E_sealed_record_class_flags_enum.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("sealed-record-class", "flags-enum", "top-level", typeof(E_sealed_record_class_flags_enum), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("sealed-record-class", "int", "nested-in-class", typeof(C_E_sealed_record_class_int.V), typeof(int), () => 42)); + e.Add(new EfEntry("sealed-record-class", "int", "nested-in-struct", typeof(S_E_sealed_record_class_int.V), typeof(int), () => 42)); + e.Add(new EfEntry("sealed-record-class", "long", "nested-in-class", typeof(C_E_sealed_record_class_long.V), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("sealed-record-class", "string", "nested-in-class", typeof(C_E_sealed_record_class_string.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("sealed-record-class", "string", "nested-in-struct", typeof(S_E_sealed_record_class_string.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("sealed-record-class", "string", "top-level", typeof(E_sealed_record_class_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("sealed-record-class", "ulong", "nested-in-class", typeof(C_E_sealed_record_class_ulong.V), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("validated-class", "DateOnly", "nested-in-struct", typeof(S_E_validated_class_DateOnly.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("validated-class", "DateTimeOffset", "nested-in-struct", typeof(S_E_validated_class_DateTimeOffset.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("validated-class", "Guid", "nested-in-class", typeof(C_E_validated_class_Guid.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-class", "Guid", "nested-in-struct", typeof(S_E_validated_class_Guid.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-class", "Guid", "top-level", typeof(E_validated_class_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-class", "TimeOnly", "nested-in-class", typeof(C_E_validated_class_TimeOnly.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("validated-class", "TimeOnly", "top-level", typeof(E_validated_class_TimeOnly), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("validated-class", "TimeSpan", "nested-two-levels", typeof(C2_E_validated_class_TimeSpan.Inner.V), typeof(TimeSpan), () => TimeSpan.FromMinutes(90))); + e.Add(new EfEntry("validated-class", "Uri", "nested-in-struct", typeof(S_E_validated_class_Uri.V), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("validated-class", "bool", "nested-in-class", typeof(C_E_validated_class_bool.V), typeof(bool), () => true)); + e.Add(new EfEntry("validated-class", "bool", "nested-in-struct", typeof(S_E_validated_class_bool.V), typeof(bool), () => true)); + e.Add(new EfEntry("validated-class", "bool", "top-level", typeof(E_validated_class_bool), typeof(bool), () => true)); + e.Add(new EfEntry("validated-class", "byte", "nested-in-class", typeof(C_E_validated_class_byte.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-class", "byte", "top-level", typeof(E_validated_class_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-class", "char", "nested-in-class", typeof(C_E_validated_class_char.V), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-class", "char", "nested-two-levels", typeof(C2_E_validated_class_char.Inner.V), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-class", "double", "nested-in-class", typeof(C_E_validated_class_double.V), typeof(double), () => 1.5)); + e.Add(new EfEntry("validated-class", "double", "nested-two-levels", typeof(C2_E_validated_class_double.Inner.V), typeof(double), () => 1.5)); + e.Add(new EfEntry("validated-class", "enum", "nested-in-struct", typeof(S_E_validated_class_enum.V), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("validated-class", "enum", "nested-two-levels", typeof(C2_E_validated_class_enum.Inner.V), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("validated-class", "flags-enum", "nested-in-class", typeof(C_E_validated_class_flags_enum.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("validated-class", "flags-enum", "nested-in-struct", typeof(S_E_validated_class_flags_enum.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("validated-class", "flags-enum", "nested-two-levels", typeof(C2_E_validated_class_flags_enum.Inner.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("validated-class", "int", "nested-two-levels", typeof(C2_E_validated_class_int.Inner.V), typeof(int), () => 42)); + e.Add(new EfEntry("validated-class", "int", "top-level", typeof(E_validated_class_int), typeof(int), () => 42)); + e.Add(new EfEntry("validated-class", "long", "nested-in-struct", typeof(S_E_validated_class_long.V), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-class", "long", "nested-two-levels", typeof(C2_E_validated_class_long.Inner.V), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-class", "string", "nested-in-struct", typeof(S_E_validated_class_string.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-class", "string", "nested-two-levels", typeof(C2_E_validated_class_string.Inner.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-class", "ulong", "nested-in-struct", typeof(S_E_validated_class_ulong.V), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("validated-class", "ulong", "top-level", typeof(E_validated_class_ulong), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("validated-struct", "DateOnly", "nested-in-class", typeof(C_E_validated_struct_DateOnly.V), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("validated-struct", "DateOnly", "top-level", typeof(E_validated_struct_DateOnly), typeof(DateOnly), () => new DateOnly(2026,10,15))); + e.Add(new EfEntry("validated-struct", "DateTime", "nested-in-class", typeof(C_E_validated_struct_DateTime.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("validated-struct", "DateTime", "nested-in-struct", typeof(S_E_validated_struct_DateTime.V), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc))); + e.Add(new EfEntry("validated-struct", "DateTimeOffset", "nested-in-class", typeof(C_E_validated_struct_DateTimeOffset.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("validated-struct", "DateTimeOffset", "nested-two-levels", typeof(C2_E_validated_struct_DateTimeOffset.Inner.V), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero))); + e.Add(new EfEntry("validated-struct", "Guid", "nested-in-class", typeof(C_E_validated_struct_Guid.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-struct", "Guid", "nested-in-struct", typeof(S_E_validated_struct_Guid.V), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-struct", "TimeOnly", "nested-in-struct", typeof(S_E_validated_struct_TimeOnly.V), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("validated-struct", "TimeOnly", "top-level", typeof(E_validated_struct_TimeOnly), typeof(TimeOnly), () => new TimeOnly(8,30,15))); + e.Add(new EfEntry("validated-struct", "TimeSpan", "nested-in-class", typeof(C_E_validated_struct_TimeSpan.V), typeof(TimeSpan), () => TimeSpan.FromMinutes(90))); + e.Add(new EfEntry("validated-struct", "Uri", "top-level", typeof(E_validated_struct_Uri), typeof(Uri), () => new Uri("https://example.com/a"))); + e.Add(new EfEntry("validated-struct", "bool", "nested-in-class", typeof(C_E_validated_struct_bool.V), typeof(bool), () => true)); + e.Add(new EfEntry("validated-struct", "bool", "nested-in-struct", typeof(S_E_validated_struct_bool.V), typeof(bool), () => true)); + e.Add(new EfEntry("validated-struct", "bool", "nested-two-levels", typeof(C2_E_validated_struct_bool.Inner.V), typeof(bool), () => true)); + e.Add(new EfEntry("validated-struct", "byte", "nested-in-struct", typeof(S_E_validated_struct_byte.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-struct", "byte", "nested-two-levels", typeof(C2_E_validated_struct_byte.Inner.V), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-struct", "char", "nested-two-levels", typeof(C2_E_validated_struct_char.Inner.V), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-struct", "char", "top-level", typeof(E_validated_struct_char), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-struct", "decimal", "nested-in-class", typeof(C_E_validated_struct_decimal.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("validated-struct", "decimal", "nested-in-struct", typeof(S_E_validated_struct_decimal.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("validated-struct", "decimal", "nested-two-levels", typeof(C2_E_validated_struct_decimal.Inner.V), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("validated-struct", "decimal", "top-level", typeof(E_validated_struct_decimal), typeof(decimal), () => 7.80m)); + e.Add(new EfEntry("validated-struct", "double", "nested-in-class", typeof(C_E_validated_struct_double.V), typeof(double), () => 1.5)); + e.Add(new EfEntry("validated-struct", "double", "top-level", typeof(E_validated_struct_double), typeof(double), () => 1.5)); + e.Add(new EfEntry("validated-struct", "enum", "nested-in-class", typeof(C_E_validated_struct_enum.V), typeof(PEnum), () => PEnum.B)); + e.Add(new EfEntry("validated-struct", "flags-enum", "nested-in-struct", typeof(S_E_validated_struct_flags_enum.V), typeof(PFlags), () => PFlags.P | PFlags.Q)); + e.Add(new EfEntry("validated-struct", "int", "nested-in-class", typeof(C_E_validated_struct_int.V), typeof(int), () => 42)); + e.Add(new EfEntry("validated-struct", "int", "nested-in-struct", typeof(S_E_validated_struct_int.V), typeof(int), () => 42)); + e.Add(new EfEntry("validated-struct", "long", "nested-two-levels", typeof(C2_E_validated_struct_long.Inner.V), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-struct", "long", "top-level", typeof(E_validated_struct_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-struct", "string", "nested-two-levels", typeof(C2_E_validated_struct_string.Inner.V), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-struct", "string", "top-level", typeof(E_validated_struct_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-struct", "ulong", "nested-two-levels", typeof(C2_E_validated_struct_ulong.Inner.V), typeof(ulong), () => 4200000000UL)); + } +} diff --git a/tools/probe/corpus/harness/EfShapes3.cs b/tools/probe/corpus/harness/EfShapes3.cs new file mode 100644 index 00000000..b4b846b7 --- /dev/null +++ b/tools/probe/corpus/harness/EfShapes3.cs @@ -0,0 +1,26 @@ +// Lookup of the top-level shapes of EfShapes3.g.cs as EfEntry (so MakeVo/Companion work): the complete kind x wrapped grid. +#nullable enable +namespace Probes.Ef3; +public static class E3 +{ + public static Probes.Ef.EfEntry Find(string kind, string wrapped) + { + var (vo, plain) = Reg3.All[(kind, wrapped)]; + var src = Probes.Ef.EfRegistry.All.Where(x => x.Wrapped == wrapped && x.Kind == (kind.StartsWith("validated") ? "validated-struct" : "record-struct")).FirstOrDefault() + ?? Probes.Ef.EfRegistry.All.First(x => x.Wrapped == wrapped); + return new Probes.Ef.EfEntry(kind, wrapped, "top-level", vo, plain, src.Valid); + } +} +public static class E4 +{ + /// <summary>The shape at a site; in-generic-type (CMTK1005, refused at build) runs at top-level and says so.</summary> + public static Probes.Ef.EfEntry At(Probes.Shapes.Row r, string kind, string wrapped, string site) + { + if (site == "in-generic-type") { r.Substituted("shape.site", "top-level", "CMTK1005: a value object nested in a generic type does not build"); site = "top-level"; } + if (site == "top-level") return E3.Find(kind, wrapped); + var (vo, plain) = Probes.Ef4.Reg4.All[(kind, wrapped, site)]; + var src = Probes.Ef.EfRegistry.All.Where(x => x.Wrapped == wrapped && x.Kind == (kind.StartsWith("validated") ? "validated-struct" : "record-struct")).FirstOrDefault() + ?? Probes.Ef.EfRegistry.All.First(x => x.Wrapped == wrapped); + return new Probes.Ef.EfEntry(kind, wrapped, site, vo, plain, src.Valid); + } +} diff --git a/tools/probe/corpus/harness/EfShapes3.g.cs b/tools/probe/corpus/harness/EfShapes3.g.cs new file mode 100644 index 00000000..ef335f76 --- /dev/null +++ b/tools/probe/corpus/harness/EfShapes3.g.cs @@ -0,0 +1,155 @@ +// <auto-generated/> by tools/probe/generators/ef.cs top-level: top-level shapes for every (kind, wrapped) the E03/E09/E10 probes need. Do not edit. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using Probes.Shapes; +namespace Probes.Ef3; +public readonly partial record struct X_rs_DateOnly : IValue<DateOnly>; +public sealed partial record class X_rc_DateOnly : IValue<DateOnly>; +public readonly partial record struct X_vs_DateOnly : IValidatedValue<X_vs_DateOnly, DateOnly, ShapeFault> { public static Result<X_vs_DateOnly, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vs_DateOnly(value); } +public sealed partial record class X_vc_DateOnly : IValidatedValue<X_vc_DateOnly, DateOnly, ShapeFault> { public static Result<X_vc_DateOnly, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vc_DateOnly(value); } +public readonly partial record struct X_rs_DateTime : IValue<DateTime>; +public sealed partial record class X_rc_DateTime : IValue<DateTime>; +public readonly partial record struct X_vs_DateTime : IValidatedValue<X_vs_DateTime, DateTime, ShapeFault> { public static Result<X_vs_DateTime, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vs_DateTime(value); } +public sealed partial record class X_vc_DateTime : IValidatedValue<X_vc_DateTime, DateTime, ShapeFault> { public static Result<X_vc_DateTime, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vc_DateTime(value); } +public readonly partial record struct X_rs_DateTimeOffset : IValue<DateTimeOffset>; +public sealed partial record class X_rc_DateTimeOffset : IValue<DateTimeOffset>; +public readonly partial record struct X_vs_DateTimeOffset : IValidatedValue<X_vs_DateTimeOffset, DateTimeOffset, ShapeFault> { public static Result<X_vs_DateTimeOffset, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vs_DateTimeOffset(value); } +public sealed partial record class X_vc_DateTimeOffset : IValidatedValue<X_vc_DateTimeOffset, DateTimeOffset, ShapeFault> { public static Result<X_vc_DateTimeOffset, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vc_DateTimeOffset(value); } +public readonly partial record struct X_rs_Guid : IValue<Guid>; +public sealed partial record class X_rc_Guid : IValue<Guid>; +public readonly partial record struct X_vs_Guid : IValidatedValue<X_vs_Guid, Guid, ShapeFault> { public static Result<X_vs_Guid, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new X_vs_Guid(value); } +public sealed partial record class X_vc_Guid : IValidatedValue<X_vc_Guid, Guid, ShapeFault> { public static Result<X_vc_Guid, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new X_vc_Guid(value); } +public readonly partial record struct X_rs_TimeOnly : IValue<TimeOnly>; +public sealed partial record class X_rc_TimeOnly : IValue<TimeOnly>; +public readonly partial record struct X_vs_TimeOnly : IValidatedValue<X_vs_TimeOnly, TimeOnly, ShapeFault> { public static Result<X_vs_TimeOnly, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vs_TimeOnly(value); } +public sealed partial record class X_vc_TimeOnly : IValidatedValue<X_vc_TimeOnly, TimeOnly, ShapeFault> { public static Result<X_vc_TimeOnly, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vc_TimeOnly(value); } +public readonly partial record struct X_rs_TimeSpan : IValue<TimeSpan>; +public sealed partial record class X_rc_TimeSpan : IValue<TimeSpan>; +public readonly partial record struct X_vs_TimeSpan : IValidatedValue<X_vs_TimeSpan, TimeSpan, ShapeFault> { public static Result<X_vs_TimeSpan, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vs_TimeSpan(value); } +public sealed partial record class X_vc_TimeSpan : IValidatedValue<X_vc_TimeSpan, TimeSpan, ShapeFault> { public static Result<X_vc_TimeSpan, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new X_vc_TimeSpan(value); } +public readonly partial record struct X_rs_Uri : IValue<Uri>; +public sealed partial record class X_rc_Uri : IValue<Uri>; +public readonly partial record struct X_vs_Uri : IValidatedValue<X_vs_Uri, Uri, ShapeFault> { public static Result<X_vs_Uri, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new X_vs_Uri(value); } +public sealed partial record class X_vc_Uri : IValidatedValue<X_vc_Uri, Uri, ShapeFault> { public static Result<X_vc_Uri, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new X_vc_Uri(value); } +public readonly partial record struct X_rs_bool : IValue<bool>; +public sealed partial record class X_rc_bool : IValue<bool>; +public readonly partial record struct X_vs_bool : IValidatedValue<X_vs_bool, bool, ShapeFault> { public static Result<X_vs_bool, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new X_vs_bool(value); } +public sealed partial record class X_vc_bool : IValidatedValue<X_vc_bool, bool, ShapeFault> { public static Result<X_vc_bool, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new X_vc_bool(value); } +public readonly partial record struct X_rs_byte : IValue<byte>; +public sealed partial record class X_rc_byte : IValue<byte>; +public readonly partial record struct X_vs_byte : IValidatedValue<X_vs_byte, byte, ShapeFault> { public static Result<X_vs_byte, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new X_vs_byte(value); } +public sealed partial record class X_vc_byte : IValidatedValue<X_vc_byte, byte, ShapeFault> { public static Result<X_vc_byte, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new X_vc_byte(value); } +public readonly partial record struct X_rs_char : IValue<char>; +public sealed partial record class X_rc_char : IValue<char>; +public readonly partial record struct X_vs_char : IValidatedValue<X_vs_char, char, ShapeFault> { public static Result<X_vs_char, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new X_vs_char(value); } +public sealed partial record class X_vc_char : IValidatedValue<X_vc_char, char, ShapeFault> { public static Result<X_vc_char, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new X_vc_char(value); } +public readonly partial record struct X_rs_decimal : IValue<decimal>; +public sealed partial record class X_rc_decimal : IValue<decimal>; +public readonly partial record struct X_vs_decimal : IValidatedValue<X_vs_decimal, decimal, ShapeFault> { public static Result<X_vs_decimal, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new X_vs_decimal(value); } +public sealed partial record class X_vc_decimal : IValidatedValue<X_vc_decimal, decimal, ShapeFault> { public static Result<X_vc_decimal, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new X_vc_decimal(value); } +public readonly partial record struct X_rs_double : IValue<double>; +public sealed partial record class X_rc_double : IValue<double>; +public readonly partial record struct X_vs_double : IValidatedValue<X_vs_double, double, ShapeFault> { public static Result<X_vs_double, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new X_vs_double(value); } +public sealed partial record class X_vc_double : IValidatedValue<X_vc_double, double, ShapeFault> { public static Result<X_vc_double, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new X_vc_double(value); } +public readonly partial record struct X_rs_enum : IValue<PEnum>; +public sealed partial record class X_rc_enum : IValue<PEnum>; +public readonly partial record struct X_vs_enum : IValidatedValue<X_vs_enum, PEnum, ShapeFault> { public static Result<X_vs_enum, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new X_vs_enum(value); } +public sealed partial record class X_vc_enum : IValidatedValue<X_vc_enum, PEnum, ShapeFault> { public static Result<X_vc_enum, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new X_vc_enum(value); } +public readonly partial record struct X_rs_flags_enum : IValue<PFlags>; +public sealed partial record class X_rc_flags_enum : IValue<PFlags>; +public readonly partial record struct X_vs_flags_enum : IValidatedValue<X_vs_flags_enum, PFlags, ShapeFault> { public static Result<X_vs_flags_enum, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new X_vs_flags_enum(value); } +public sealed partial record class X_vc_flags_enum : IValidatedValue<X_vc_flags_enum, PFlags, ShapeFault> { public static Result<X_vc_flags_enum, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new X_vc_flags_enum(value); } +public readonly partial record struct X_rs_int : IValue<int>; +public sealed partial record class X_rc_int : IValue<int>; +public readonly partial record struct X_vs_int : IValidatedValue<X_vs_int, int, ShapeFault> { public static Result<X_vs_int, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new X_vs_int(value); } +public sealed partial record class X_vc_int : IValidatedValue<X_vc_int, int, ShapeFault> { public static Result<X_vc_int, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new X_vc_int(value); } +public readonly partial record struct X_rs_long : IValue<long>; +public sealed partial record class X_rc_long : IValue<long>; +public readonly partial record struct X_vs_long : IValidatedValue<X_vs_long, long, ShapeFault> { public static Result<X_vs_long, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new X_vs_long(value); } +public sealed partial record class X_vc_long : IValidatedValue<X_vc_long, long, ShapeFault> { public static Result<X_vc_long, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new X_vc_long(value); } +public readonly partial record struct X_rs_string : IValue<string>; +public sealed partial record class X_rc_string : IValue<string>; +public readonly partial record struct X_vs_string : IValidatedValue<X_vs_string, string, ShapeFault> { public static Result<X_vs_string, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new X_vs_string(value); } +public sealed partial record class X_vc_string : IValidatedValue<X_vc_string, string, ShapeFault> { public static Result<X_vc_string, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new X_vc_string(value); } +public readonly partial record struct X_rs_ulong : IValue<ulong>; +public sealed partial record class X_rc_ulong : IValue<ulong>; +public readonly partial record struct X_vs_ulong : IValidatedValue<X_vs_ulong, ulong, ShapeFault> { public static Result<X_vs_ulong, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new X_vs_ulong(value); } +public sealed partial record class X_vc_ulong : IValidatedValue<X_vc_ulong, ulong, ShapeFault> { public static Result<X_vc_ulong, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new X_vc_ulong(value); } +public static class Reg3 { + public static readonly Dictionary<(string Kind, string Wrapped), (Type Vo, Type Plain)> All = new() { + [("record-struct", "DateOnly")] = (typeof(X_rs_DateOnly), typeof(DateOnly)), + [("sealed-record-class", "DateOnly")] = (typeof(X_rc_DateOnly), typeof(DateOnly)), + [("validated-struct", "DateOnly")] = (typeof(X_vs_DateOnly), typeof(DateOnly)), + [("validated-class", "DateOnly")] = (typeof(X_vc_DateOnly), typeof(DateOnly)), + [("record-struct", "DateTime")] = (typeof(X_rs_DateTime), typeof(DateTime)), + [("sealed-record-class", "DateTime")] = (typeof(X_rc_DateTime), typeof(DateTime)), + [("validated-struct", "DateTime")] = (typeof(X_vs_DateTime), typeof(DateTime)), + [("validated-class", "DateTime")] = (typeof(X_vc_DateTime), typeof(DateTime)), + [("record-struct", "DateTimeOffset")] = (typeof(X_rs_DateTimeOffset), typeof(DateTimeOffset)), + [("sealed-record-class", "DateTimeOffset")] = (typeof(X_rc_DateTimeOffset), typeof(DateTimeOffset)), + [("validated-struct", "DateTimeOffset")] = (typeof(X_vs_DateTimeOffset), typeof(DateTimeOffset)), + [("validated-class", "DateTimeOffset")] = (typeof(X_vc_DateTimeOffset), typeof(DateTimeOffset)), + [("record-struct", "Guid")] = (typeof(X_rs_Guid), typeof(Guid)), + [("sealed-record-class", "Guid")] = (typeof(X_rc_Guid), typeof(Guid)), + [("validated-struct", "Guid")] = (typeof(X_vs_Guid), typeof(Guid)), + [("validated-class", "Guid")] = (typeof(X_vc_Guid), typeof(Guid)), + [("record-struct", "TimeOnly")] = (typeof(X_rs_TimeOnly), typeof(TimeOnly)), + [("sealed-record-class", "TimeOnly")] = (typeof(X_rc_TimeOnly), typeof(TimeOnly)), + [("validated-struct", "TimeOnly")] = (typeof(X_vs_TimeOnly), typeof(TimeOnly)), + [("validated-class", "TimeOnly")] = (typeof(X_vc_TimeOnly), typeof(TimeOnly)), + [("record-struct", "TimeSpan")] = (typeof(X_rs_TimeSpan), typeof(TimeSpan)), + [("sealed-record-class", "TimeSpan")] = (typeof(X_rc_TimeSpan), typeof(TimeSpan)), + [("validated-struct", "TimeSpan")] = (typeof(X_vs_TimeSpan), typeof(TimeSpan)), + [("validated-class", "TimeSpan")] = (typeof(X_vc_TimeSpan), typeof(TimeSpan)), + [("record-struct", "Uri")] = (typeof(X_rs_Uri), typeof(Uri)), + [("sealed-record-class", "Uri")] = (typeof(X_rc_Uri), typeof(Uri)), + [("validated-struct", "Uri")] = (typeof(X_vs_Uri), typeof(Uri)), + [("validated-class", "Uri")] = (typeof(X_vc_Uri), typeof(Uri)), + [("record-struct", "bool")] = (typeof(X_rs_bool), typeof(bool)), + [("sealed-record-class", "bool")] = (typeof(X_rc_bool), typeof(bool)), + [("validated-struct", "bool")] = (typeof(X_vs_bool), typeof(bool)), + [("validated-class", "bool")] = (typeof(X_vc_bool), typeof(bool)), + [("record-struct", "byte")] = (typeof(X_rs_byte), typeof(byte)), + [("sealed-record-class", "byte")] = (typeof(X_rc_byte), typeof(byte)), + [("validated-struct", "byte")] = (typeof(X_vs_byte), typeof(byte)), + [("validated-class", "byte")] = (typeof(X_vc_byte), typeof(byte)), + [("record-struct", "char")] = (typeof(X_rs_char), typeof(char)), + [("sealed-record-class", "char")] = (typeof(X_rc_char), typeof(char)), + [("validated-struct", "char")] = (typeof(X_vs_char), typeof(char)), + [("validated-class", "char")] = (typeof(X_vc_char), typeof(char)), + [("record-struct", "decimal")] = (typeof(X_rs_decimal), typeof(decimal)), + [("sealed-record-class", "decimal")] = (typeof(X_rc_decimal), typeof(decimal)), + [("validated-struct", "decimal")] = (typeof(X_vs_decimal), typeof(decimal)), + [("validated-class", "decimal")] = (typeof(X_vc_decimal), typeof(decimal)), + [("record-struct", "double")] = (typeof(X_rs_double), typeof(double)), + [("sealed-record-class", "double")] = (typeof(X_rc_double), typeof(double)), + [("validated-struct", "double")] = (typeof(X_vs_double), typeof(double)), + [("validated-class", "double")] = (typeof(X_vc_double), typeof(double)), + [("record-struct", "enum")] = (typeof(X_rs_enum), typeof(PEnum)), + [("sealed-record-class", "enum")] = (typeof(X_rc_enum), typeof(PEnum)), + [("validated-struct", "enum")] = (typeof(X_vs_enum), typeof(PEnum)), + [("validated-class", "enum")] = (typeof(X_vc_enum), typeof(PEnum)), + [("record-struct", "flags-enum")] = (typeof(X_rs_flags_enum), typeof(PFlags)), + [("sealed-record-class", "flags-enum")] = (typeof(X_rc_flags_enum), typeof(PFlags)), + [("validated-struct", "flags-enum")] = (typeof(X_vs_flags_enum), typeof(PFlags)), + [("validated-class", "flags-enum")] = (typeof(X_vc_flags_enum), typeof(PFlags)), + [("record-struct", "int")] = (typeof(X_rs_int), typeof(int)), + [("sealed-record-class", "int")] = (typeof(X_rc_int), typeof(int)), + [("validated-struct", "int")] = (typeof(X_vs_int), typeof(int)), + [("validated-class", "int")] = (typeof(X_vc_int), typeof(int)), + [("record-struct", "long")] = (typeof(X_rs_long), typeof(long)), + [("sealed-record-class", "long")] = (typeof(X_rc_long), typeof(long)), + [("validated-struct", "long")] = (typeof(X_vs_long), typeof(long)), + [("validated-class", "long")] = (typeof(X_vc_long), typeof(long)), + [("record-struct", "string")] = (typeof(X_rs_string), typeof(string)), + [("sealed-record-class", "string")] = (typeof(X_rc_string), typeof(string)), + [("validated-struct", "string")] = (typeof(X_vs_string), typeof(string)), + [("validated-class", "string")] = (typeof(X_vc_string), typeof(string)), + [("record-struct", "ulong")] = (typeof(X_rs_ulong), typeof(ulong)), + [("sealed-record-class", "ulong")] = (typeof(X_rc_ulong), typeof(ulong)), + [("validated-struct", "ulong")] = (typeof(X_vs_ulong), typeof(ulong)), + [("validated-class", "ulong")] = (typeof(X_vc_ulong), typeof(ulong)), + }; +} diff --git a/tools/probe/corpus/harness/EfShapes4.g.cs b/tools/probe/corpus/harness/EfShapes4.g.cs new file mode 100644 index 00000000..27234211 --- /dev/null +++ b/tools/probe/corpus/harness/EfShapes4.g.cs @@ -0,0 +1,443 @@ +// <auto-generated/> by tools/probe/generators/ef.cs nested: nested-site shapes (nested-in-class, nested-in-struct, nested-two-levels) for every kind x wrapped. Do not edit. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using Probes.Shapes; +namespace Probes.Ef4; +public static class NC_rs_DateOnly { public readonly partial record struct V : IValue<DateOnly>; } +public struct NS_rs_DateOnly { public readonly partial record struct V : IValue<DateOnly>; } +public static class N2_rs_DateOnly { public static class Inner { public readonly partial record struct V : IValue<DateOnly>; } } +public static class NC_rc_DateOnly { public sealed partial record class V : IValue<DateOnly>; } +public struct NS_rc_DateOnly { public sealed partial record class V : IValue<DateOnly>; } +public static class N2_rc_DateOnly { public static class Inner { public sealed partial record class V : IValue<DateOnly>; } } +public static class NC_vs_DateOnly { public readonly partial record struct V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_DateOnly { public readonly partial record struct V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_DateOnly { public static class Inner { public readonly partial record struct V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_DateOnly { public sealed partial record class V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_DateOnly { public sealed partial record class V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_DateOnly { public static class Inner { public sealed partial record class V : IValidatedValue<V, DateOnly, ShapeFault> { public static Result<V, ShapeFault> Create(DateOnly value) => EqualityComparer<DateOnly>.Default.Equals(value, DateOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_DateTime { public readonly partial record struct V : IValue<DateTime>; } +public struct NS_rs_DateTime { public readonly partial record struct V : IValue<DateTime>; } +public static class N2_rs_DateTime { public static class Inner { public readonly partial record struct V : IValue<DateTime>; } } +public static class NC_rc_DateTime { public sealed partial record class V : IValue<DateTime>; } +public struct NS_rc_DateTime { public sealed partial record class V : IValue<DateTime>; } +public static class N2_rc_DateTime { public static class Inner { public sealed partial record class V : IValue<DateTime>; } } +public static class NC_vs_DateTime { public readonly partial record struct V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_DateTime { public readonly partial record struct V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_DateTime { public static class Inner { public readonly partial record struct V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_DateTime { public sealed partial record class V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_DateTime { public sealed partial record class V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_DateTime { public static class Inner { public sealed partial record class V : IValidatedValue<V, DateTime, ShapeFault> { public static Result<V, ShapeFault> Create(DateTime value) => EqualityComparer<DateTime>.Default.Equals(value, DateTime.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_DateTimeOffset { public readonly partial record struct V : IValue<DateTimeOffset>; } +public struct NS_rs_DateTimeOffset { public readonly partial record struct V : IValue<DateTimeOffset>; } +public static class N2_rs_DateTimeOffset { public static class Inner { public readonly partial record struct V : IValue<DateTimeOffset>; } } +public static class NC_rc_DateTimeOffset { public sealed partial record class V : IValue<DateTimeOffset>; } +public struct NS_rc_DateTimeOffset { public sealed partial record class V : IValue<DateTimeOffset>; } +public static class N2_rc_DateTimeOffset { public static class Inner { public sealed partial record class V : IValue<DateTimeOffset>; } } +public static class NC_vs_DateTimeOffset { public readonly partial record struct V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_DateTimeOffset { public readonly partial record struct V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_DateTimeOffset { public static class Inner { public readonly partial record struct V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_DateTimeOffset { public sealed partial record class V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_DateTimeOffset { public sealed partial record class V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_DateTimeOffset { public static class Inner { public sealed partial record class V : IValidatedValue<V, DateTimeOffset, ShapeFault> { public static Result<V, ShapeFault> Create(DateTimeOffset value) => EqualityComparer<DateTimeOffset>.Default.Equals(value, DateTimeOffset.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_Guid { public readonly partial record struct V : IValue<Guid>; } +public struct NS_rs_Guid { public readonly partial record struct V : IValue<Guid>; } +public static class N2_rs_Guid { public static class Inner { public readonly partial record struct V : IValue<Guid>; } } +public static class NC_rc_Guid { public sealed partial record class V : IValue<Guid>; } +public struct NS_rc_Guid { public sealed partial record class V : IValue<Guid>; } +public static class N2_rc_Guid { public static class Inner { public sealed partial record class V : IValue<Guid>; } } +public static class NC_vs_Guid { public readonly partial record struct V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_Guid { public readonly partial record struct V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_Guid { public static class Inner { public readonly partial record struct V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_Guid { public sealed partial record class V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_Guid { public sealed partial record class V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_Guid { public static class Inner { public sealed partial record class V : IValidatedValue<V, Guid, ShapeFault> { public static Result<V, ShapeFault> Create(Guid value) => EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_TimeOnly { public readonly partial record struct V : IValue<TimeOnly>; } +public struct NS_rs_TimeOnly { public readonly partial record struct V : IValue<TimeOnly>; } +public static class N2_rs_TimeOnly { public static class Inner { public readonly partial record struct V : IValue<TimeOnly>; } } +public static class NC_rc_TimeOnly { public sealed partial record class V : IValue<TimeOnly>; } +public struct NS_rc_TimeOnly { public sealed partial record class V : IValue<TimeOnly>; } +public static class N2_rc_TimeOnly { public static class Inner { public sealed partial record class V : IValue<TimeOnly>; } } +public static class NC_vs_TimeOnly { public readonly partial record struct V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_TimeOnly { public readonly partial record struct V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_TimeOnly { public static class Inner { public readonly partial record struct V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_TimeOnly { public sealed partial record class V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_TimeOnly { public sealed partial record class V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_TimeOnly { public static class Inner { public sealed partial record class V : IValidatedValue<V, TimeOnly, ShapeFault> { public static Result<V, ShapeFault> Create(TimeOnly value) => EqualityComparer<TimeOnly>.Default.Equals(value, TimeOnly.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_TimeSpan { public readonly partial record struct V : IValue<TimeSpan>; } +public struct NS_rs_TimeSpan { public readonly partial record struct V : IValue<TimeSpan>; } +public static class N2_rs_TimeSpan { public static class Inner { public readonly partial record struct V : IValue<TimeSpan>; } } +public static class NC_rc_TimeSpan { public sealed partial record class V : IValue<TimeSpan>; } +public struct NS_rc_TimeSpan { public sealed partial record class V : IValue<TimeSpan>; } +public static class N2_rc_TimeSpan { public static class Inner { public sealed partial record class V : IValue<TimeSpan>; } } +public static class NC_vs_TimeSpan { public readonly partial record struct V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_TimeSpan { public readonly partial record struct V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_TimeSpan { public static class Inner { public readonly partial record struct V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_TimeSpan { public sealed partial record class V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_TimeSpan { public sealed partial record class V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_TimeSpan { public static class Inner { public sealed partial record class V : IValidatedValue<V, TimeSpan, ShapeFault> { public static Result<V, ShapeFault> Create(TimeSpan value) => EqualityComparer<TimeSpan>.Default.Equals(value, TimeSpan.MinValue) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_Uri { public readonly partial record struct V : IValue<Uri>; } +public struct NS_rs_Uri { public readonly partial record struct V : IValue<Uri>; } +public static class N2_rs_Uri { public static class Inner { public readonly partial record struct V : IValue<Uri>; } } +public static class NC_rc_Uri { public sealed partial record class V : IValue<Uri>; } +public struct NS_rc_Uri { public sealed partial record class V : IValue<Uri>; } +public static class N2_rc_Uri { public static class Inner { public sealed partial record class V : IValue<Uri>; } } +public static class NC_vs_Uri { public readonly partial record struct V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_Uri { public readonly partial record struct V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_Uri { public static class Inner { public readonly partial record struct V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_Uri { public sealed partial record class V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_Uri { public sealed partial record class V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_Uri { public static class Inner { public sealed partial record class V : IValidatedValue<V, Uri, ShapeFault> { public static Result<V, ShapeFault> Create(Uri value) => EqualityComparer<Uri>.Default.Equals(value, new Uri("https://example.com/refused")) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_bool { public readonly partial record struct V : IValue<bool>; } +public struct NS_rs_bool { public readonly partial record struct V : IValue<bool>; } +public static class N2_rs_bool { public static class Inner { public readonly partial record struct V : IValue<bool>; } } +public static class NC_rc_bool { public sealed partial record class V : IValue<bool>; } +public struct NS_rc_bool { public sealed partial record class V : IValue<bool>; } +public static class N2_rc_bool { public static class Inner { public sealed partial record class V : IValue<bool>; } } +public static class NC_vs_bool { public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_bool { public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_bool { public static class Inner { public readonly partial record struct V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_bool { public sealed partial record class V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_bool { public sealed partial record class V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_bool { public static class Inner { public sealed partial record class V : IValidatedValue<V, bool, ShapeFault> { public static Result<V, ShapeFault> Create(bool value) => EqualityComparer<bool>.Default.Equals(value, false) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_byte { public readonly partial record struct V : IValue<byte>; } +public struct NS_rs_byte { public readonly partial record struct V : IValue<byte>; } +public static class N2_rs_byte { public static class Inner { public readonly partial record struct V : IValue<byte>; } } +public static class NC_rc_byte { public sealed partial record class V : IValue<byte>; } +public struct NS_rc_byte { public sealed partial record class V : IValue<byte>; } +public static class N2_rc_byte { public static class Inner { public sealed partial record class V : IValue<byte>; } } +public static class NC_vs_byte { public readonly partial record struct V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_byte { public readonly partial record struct V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_byte { public static class Inner { public readonly partial record struct V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_byte { public sealed partial record class V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_byte { public sealed partial record class V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_byte { public static class Inner { public sealed partial record class V : IValidatedValue<V, byte, ShapeFault> { public static Result<V, ShapeFault> Create(byte value) => EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_char { public readonly partial record struct V : IValue<char>; } +public struct NS_rs_char { public readonly partial record struct V : IValue<char>; } +public static class N2_rs_char { public static class Inner { public readonly partial record struct V : IValue<char>; } } +public static class NC_rc_char { public sealed partial record class V : IValue<char>; } +public struct NS_rc_char { public sealed partial record class V : IValue<char>; } +public static class N2_rc_char { public static class Inner { public sealed partial record class V : IValue<char>; } } +public static class NC_vs_char { public readonly partial record struct V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_char { public readonly partial record struct V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_char { public static class Inner { public readonly partial record struct V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_char { public sealed partial record class V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_char { public sealed partial record class V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_char { public static class Inner { public sealed partial record class V : IValidatedValue<V, char, ShapeFault> { public static Result<V, ShapeFault> Create(char value) => EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_decimal { public readonly partial record struct V : IValue<decimal>; } +public struct NS_rs_decimal { public readonly partial record struct V : IValue<decimal>; } +public static class N2_rs_decimal { public static class Inner { public readonly partial record struct V : IValue<decimal>; } } +public static class NC_rc_decimal { public sealed partial record class V : IValue<decimal>; } +public struct NS_rc_decimal { public sealed partial record class V : IValue<decimal>; } +public static class N2_rc_decimal { public static class Inner { public sealed partial record class V : IValue<decimal>; } } +public static class NC_vs_decimal { public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_decimal { public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_decimal { public static class Inner { public readonly partial record struct V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_decimal { public sealed partial record class V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_decimal { public sealed partial record class V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_decimal { public static class Inner { public sealed partial record class V : IValidatedValue<V, decimal, ShapeFault> { public static Result<V, ShapeFault> Create(decimal value) => EqualityComparer<decimal>.Default.Equals(value, -1m) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_double { public readonly partial record struct V : IValue<double>; } +public struct NS_rs_double { public readonly partial record struct V : IValue<double>; } +public static class N2_rs_double { public static class Inner { public readonly partial record struct V : IValue<double>; } } +public static class NC_rc_double { public sealed partial record class V : IValue<double>; } +public struct NS_rc_double { public sealed partial record class V : IValue<double>; } +public static class N2_rc_double { public static class Inner { public sealed partial record class V : IValue<double>; } } +public static class NC_vs_double { public readonly partial record struct V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_double { public readonly partial record struct V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_double { public static class Inner { public readonly partial record struct V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_double { public sealed partial record class V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_double { public sealed partial record class V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_double { public static class Inner { public sealed partial record class V : IValidatedValue<V, double, ShapeFault> { public static Result<V, ShapeFault> Create(double value) => EqualityComparer<double>.Default.Equals(value, -1.0) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_enum { public readonly partial record struct V : IValue<PEnum>; } +public struct NS_rs_enum { public readonly partial record struct V : IValue<PEnum>; } +public static class N2_rs_enum { public static class Inner { public readonly partial record struct V : IValue<PEnum>; } } +public static class NC_rc_enum { public sealed partial record class V : IValue<PEnum>; } +public struct NS_rc_enum { public sealed partial record class V : IValue<PEnum>; } +public static class N2_rc_enum { public static class Inner { public sealed partial record class V : IValue<PEnum>; } } +public static class NC_vs_enum { public readonly partial record struct V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_enum { public readonly partial record struct V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_enum { public static class Inner { public readonly partial record struct V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_enum { public sealed partial record class V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_enum { public sealed partial record class V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_enum { public static class Inner { public sealed partial record class V : IValidatedValue<V, PEnum, ShapeFault> { public static Result<V, ShapeFault> Create(PEnum value) => EqualityComparer<PEnum>.Default.Equals(value, (PEnum)99) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_flags_enum { public readonly partial record struct V : IValue<PFlags>; } +public struct NS_rs_flags_enum { public readonly partial record struct V : IValue<PFlags>; } +public static class N2_rs_flags_enum { public static class Inner { public readonly partial record struct V : IValue<PFlags>; } } +public static class NC_rc_flags_enum { public sealed partial record class V : IValue<PFlags>; } +public struct NS_rc_flags_enum { public sealed partial record class V : IValue<PFlags>; } +public static class N2_rc_flags_enum { public static class Inner { public sealed partial record class V : IValue<PFlags>; } } +public static class NC_vs_flags_enum { public readonly partial record struct V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_flags_enum { public readonly partial record struct V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_flags_enum { public static class Inner { public readonly partial record struct V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_flags_enum { public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_flags_enum { public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_flags_enum { public static class Inner { public sealed partial record class V : IValidatedValue<V, PFlags, ShapeFault> { public static Result<V, ShapeFault> Create(PFlags value) => EqualityComparer<PFlags>.Default.Equals(value, (PFlags)0) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_int { public readonly partial record struct V : IValue<int>; } +public struct NS_rs_int { public readonly partial record struct V : IValue<int>; } +public static class N2_rs_int { public static class Inner { public readonly partial record struct V : IValue<int>; } } +public static class NC_rc_int { public sealed partial record class V : IValue<int>; } +public struct NS_rc_int { public sealed partial record class V : IValue<int>; } +public static class N2_rc_int { public static class Inner { public sealed partial record class V : IValue<int>; } } +public static class NC_vs_int { public readonly partial record struct V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_int { public readonly partial record struct V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_int { public static class Inner { public readonly partial record struct V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_int { public sealed partial record class V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_int { public sealed partial record class V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_int { public static class Inner { public sealed partial record class V : IValidatedValue<V, int, ShapeFault> { public static Result<V, ShapeFault> Create(int value) => EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_long { public readonly partial record struct V : IValue<long>; } +public struct NS_rs_long { public readonly partial record struct V : IValue<long>; } +public static class N2_rs_long { public static class Inner { public readonly partial record struct V : IValue<long>; } } +public static class NC_rc_long { public sealed partial record class V : IValue<long>; } +public struct NS_rc_long { public sealed partial record class V : IValue<long>; } +public static class N2_rc_long { public static class Inner { public sealed partial record class V : IValue<long>; } } +public static class NC_vs_long { public readonly partial record struct V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_long { public readonly partial record struct V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_long { public static class Inner { public readonly partial record struct V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_long { public sealed partial record class V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_long { public sealed partial record class V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_long { public static class Inner { public sealed partial record class V : IValidatedValue<V, long, ShapeFault> { public static Result<V, ShapeFault> Create(long value) => EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_string { public readonly partial record struct V : IValue<string>; } +public struct NS_rs_string { public readonly partial record struct V : IValue<string>; } +public static class N2_rs_string { public static class Inner { public readonly partial record struct V : IValue<string>; } } +public static class NC_rc_string { public sealed partial record class V : IValue<string>; } +public struct NS_rc_string { public sealed partial record class V : IValue<string>; } +public static class N2_rc_string { public static class Inner { public sealed partial record class V : IValue<string>; } } +public static class NC_vs_string { public readonly partial record struct V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_string { public readonly partial record struct V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_string { public static class Inner { public readonly partial record struct V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_string { public sealed partial record class V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_string { public sealed partial record class V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_string { public static class Inner { public sealed partial record class V : IValidatedValue<V, string, ShapeFault> { public static Result<V, ShapeFault> Create(string value) => EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_rs_ulong { public readonly partial record struct V : IValue<ulong>; } +public struct NS_rs_ulong { public readonly partial record struct V : IValue<ulong>; } +public static class N2_rs_ulong { public static class Inner { public readonly partial record struct V : IValue<ulong>; } } +public static class NC_rc_ulong { public sealed partial record class V : IValue<ulong>; } +public struct NS_rc_ulong { public sealed partial record class V : IValue<ulong>; } +public static class N2_rc_ulong { public static class Inner { public sealed partial record class V : IValue<ulong>; } } +public static class NC_vs_ulong { public readonly partial record struct V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vs_ulong { public readonly partial record struct V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vs_ulong { public static class Inner { public readonly partial record struct V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class NC_vc_ulong { public sealed partial record class V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public struct NS_vc_ulong { public sealed partial record class V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } +public static class N2_vc_ulong { public static class Inner { public sealed partial record class V : IValidatedValue<V, ulong, ShapeFault> { public static Result<V, ShapeFault> Create(ulong value) => EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new V(value); } } } +public static class Reg4 { + public static readonly Dictionary<(string Kind, string Wrapped, string Site), (Type Vo, Type Plain)> All = new() { + [("record-struct", "DateOnly", "nested-in-class")] = (typeof(NC_rs_DateOnly.V), typeof(DateOnly)), + [("record-struct", "DateOnly", "nested-in-struct")] = (typeof(NS_rs_DateOnly.V), typeof(DateOnly)), + [("record-struct", "DateOnly", "nested-two-levels")] = (typeof(N2_rs_DateOnly.Inner.V), typeof(DateOnly)), + [("sealed-record-class", "DateOnly", "nested-in-class")] = (typeof(NC_rc_DateOnly.V), typeof(DateOnly)), + [("sealed-record-class", "DateOnly", "nested-in-struct")] = (typeof(NS_rc_DateOnly.V), typeof(DateOnly)), + [("sealed-record-class", "DateOnly", "nested-two-levels")] = (typeof(N2_rc_DateOnly.Inner.V), typeof(DateOnly)), + [("validated-struct", "DateOnly", "nested-in-class")] = (typeof(NC_vs_DateOnly.V), typeof(DateOnly)), + [("validated-struct", "DateOnly", "nested-in-struct")] = (typeof(NS_vs_DateOnly.V), typeof(DateOnly)), + [("validated-struct", "DateOnly", "nested-two-levels")] = (typeof(N2_vs_DateOnly.Inner.V), typeof(DateOnly)), + [("validated-class", "DateOnly", "nested-in-class")] = (typeof(NC_vc_DateOnly.V), typeof(DateOnly)), + [("validated-class", "DateOnly", "nested-in-struct")] = (typeof(NS_vc_DateOnly.V), typeof(DateOnly)), + [("validated-class", "DateOnly", "nested-two-levels")] = (typeof(N2_vc_DateOnly.Inner.V), typeof(DateOnly)), + [("record-struct", "DateTime", "nested-in-class")] = (typeof(NC_rs_DateTime.V), typeof(DateTime)), + [("record-struct", "DateTime", "nested-in-struct")] = (typeof(NS_rs_DateTime.V), typeof(DateTime)), + [("record-struct", "DateTime", "nested-two-levels")] = (typeof(N2_rs_DateTime.Inner.V), typeof(DateTime)), + [("sealed-record-class", "DateTime", "nested-in-class")] = (typeof(NC_rc_DateTime.V), typeof(DateTime)), + [("sealed-record-class", "DateTime", "nested-in-struct")] = (typeof(NS_rc_DateTime.V), typeof(DateTime)), + [("sealed-record-class", "DateTime", "nested-two-levels")] = (typeof(N2_rc_DateTime.Inner.V), typeof(DateTime)), + [("validated-struct", "DateTime", "nested-in-class")] = (typeof(NC_vs_DateTime.V), typeof(DateTime)), + [("validated-struct", "DateTime", "nested-in-struct")] = (typeof(NS_vs_DateTime.V), typeof(DateTime)), + [("validated-struct", "DateTime", "nested-two-levels")] = (typeof(N2_vs_DateTime.Inner.V), typeof(DateTime)), + [("validated-class", "DateTime", "nested-in-class")] = (typeof(NC_vc_DateTime.V), typeof(DateTime)), + [("validated-class", "DateTime", "nested-in-struct")] = (typeof(NS_vc_DateTime.V), typeof(DateTime)), + [("validated-class", "DateTime", "nested-two-levels")] = (typeof(N2_vc_DateTime.Inner.V), typeof(DateTime)), + [("record-struct", "DateTimeOffset", "nested-in-class")] = (typeof(NC_rs_DateTimeOffset.V), typeof(DateTimeOffset)), + [("record-struct", "DateTimeOffset", "nested-in-struct")] = (typeof(NS_rs_DateTimeOffset.V), typeof(DateTimeOffset)), + [("record-struct", "DateTimeOffset", "nested-two-levels")] = (typeof(N2_rs_DateTimeOffset.Inner.V), typeof(DateTimeOffset)), + [("sealed-record-class", "DateTimeOffset", "nested-in-class")] = (typeof(NC_rc_DateTimeOffset.V), typeof(DateTimeOffset)), + [("sealed-record-class", "DateTimeOffset", "nested-in-struct")] = (typeof(NS_rc_DateTimeOffset.V), typeof(DateTimeOffset)), + [("sealed-record-class", "DateTimeOffset", "nested-two-levels")] = (typeof(N2_rc_DateTimeOffset.Inner.V), typeof(DateTimeOffset)), + [("validated-struct", "DateTimeOffset", "nested-in-class")] = (typeof(NC_vs_DateTimeOffset.V), typeof(DateTimeOffset)), + [("validated-struct", "DateTimeOffset", "nested-in-struct")] = (typeof(NS_vs_DateTimeOffset.V), typeof(DateTimeOffset)), + [("validated-struct", "DateTimeOffset", "nested-two-levels")] = (typeof(N2_vs_DateTimeOffset.Inner.V), typeof(DateTimeOffset)), + [("validated-class", "DateTimeOffset", "nested-in-class")] = (typeof(NC_vc_DateTimeOffset.V), typeof(DateTimeOffset)), + [("validated-class", "DateTimeOffset", "nested-in-struct")] = (typeof(NS_vc_DateTimeOffset.V), typeof(DateTimeOffset)), + [("validated-class", "DateTimeOffset", "nested-two-levels")] = (typeof(N2_vc_DateTimeOffset.Inner.V), typeof(DateTimeOffset)), + [("record-struct", "Guid", "nested-in-class")] = (typeof(NC_rs_Guid.V), typeof(Guid)), + [("record-struct", "Guid", "nested-in-struct")] = (typeof(NS_rs_Guid.V), typeof(Guid)), + [("record-struct", "Guid", "nested-two-levels")] = (typeof(N2_rs_Guid.Inner.V), typeof(Guid)), + [("sealed-record-class", "Guid", "nested-in-class")] = (typeof(NC_rc_Guid.V), typeof(Guid)), + [("sealed-record-class", "Guid", "nested-in-struct")] = (typeof(NS_rc_Guid.V), typeof(Guid)), + [("sealed-record-class", "Guid", "nested-two-levels")] = (typeof(N2_rc_Guid.Inner.V), typeof(Guid)), + [("validated-struct", "Guid", "nested-in-class")] = (typeof(NC_vs_Guid.V), typeof(Guid)), + [("validated-struct", "Guid", "nested-in-struct")] = (typeof(NS_vs_Guid.V), typeof(Guid)), + [("validated-struct", "Guid", "nested-two-levels")] = (typeof(N2_vs_Guid.Inner.V), typeof(Guid)), + [("validated-class", "Guid", "nested-in-class")] = (typeof(NC_vc_Guid.V), typeof(Guid)), + [("validated-class", "Guid", "nested-in-struct")] = (typeof(NS_vc_Guid.V), typeof(Guid)), + [("validated-class", "Guid", "nested-two-levels")] = (typeof(N2_vc_Guid.Inner.V), typeof(Guid)), + [("record-struct", "TimeOnly", "nested-in-class")] = (typeof(NC_rs_TimeOnly.V), typeof(TimeOnly)), + [("record-struct", "TimeOnly", "nested-in-struct")] = (typeof(NS_rs_TimeOnly.V), typeof(TimeOnly)), + [("record-struct", "TimeOnly", "nested-two-levels")] = (typeof(N2_rs_TimeOnly.Inner.V), typeof(TimeOnly)), + [("sealed-record-class", "TimeOnly", "nested-in-class")] = (typeof(NC_rc_TimeOnly.V), typeof(TimeOnly)), + [("sealed-record-class", "TimeOnly", "nested-in-struct")] = (typeof(NS_rc_TimeOnly.V), typeof(TimeOnly)), + [("sealed-record-class", "TimeOnly", "nested-two-levels")] = (typeof(N2_rc_TimeOnly.Inner.V), typeof(TimeOnly)), + [("validated-struct", "TimeOnly", "nested-in-class")] = (typeof(NC_vs_TimeOnly.V), typeof(TimeOnly)), + [("validated-struct", "TimeOnly", "nested-in-struct")] = (typeof(NS_vs_TimeOnly.V), typeof(TimeOnly)), + [("validated-struct", "TimeOnly", "nested-two-levels")] = (typeof(N2_vs_TimeOnly.Inner.V), typeof(TimeOnly)), + [("validated-class", "TimeOnly", "nested-in-class")] = (typeof(NC_vc_TimeOnly.V), typeof(TimeOnly)), + [("validated-class", "TimeOnly", "nested-in-struct")] = (typeof(NS_vc_TimeOnly.V), typeof(TimeOnly)), + [("validated-class", "TimeOnly", "nested-two-levels")] = (typeof(N2_vc_TimeOnly.Inner.V), typeof(TimeOnly)), + [("record-struct", "TimeSpan", "nested-in-class")] = (typeof(NC_rs_TimeSpan.V), typeof(TimeSpan)), + [("record-struct", "TimeSpan", "nested-in-struct")] = (typeof(NS_rs_TimeSpan.V), typeof(TimeSpan)), + [("record-struct", "TimeSpan", "nested-two-levels")] = (typeof(N2_rs_TimeSpan.Inner.V), typeof(TimeSpan)), + [("sealed-record-class", "TimeSpan", "nested-in-class")] = (typeof(NC_rc_TimeSpan.V), typeof(TimeSpan)), + [("sealed-record-class", "TimeSpan", "nested-in-struct")] = (typeof(NS_rc_TimeSpan.V), typeof(TimeSpan)), + [("sealed-record-class", "TimeSpan", "nested-two-levels")] = (typeof(N2_rc_TimeSpan.Inner.V), typeof(TimeSpan)), + [("validated-struct", "TimeSpan", "nested-in-class")] = (typeof(NC_vs_TimeSpan.V), typeof(TimeSpan)), + [("validated-struct", "TimeSpan", "nested-in-struct")] = (typeof(NS_vs_TimeSpan.V), typeof(TimeSpan)), + [("validated-struct", "TimeSpan", "nested-two-levels")] = (typeof(N2_vs_TimeSpan.Inner.V), typeof(TimeSpan)), + [("validated-class", "TimeSpan", "nested-in-class")] = (typeof(NC_vc_TimeSpan.V), typeof(TimeSpan)), + [("validated-class", "TimeSpan", "nested-in-struct")] = (typeof(NS_vc_TimeSpan.V), typeof(TimeSpan)), + [("validated-class", "TimeSpan", "nested-two-levels")] = (typeof(N2_vc_TimeSpan.Inner.V), typeof(TimeSpan)), + [("record-struct", "Uri", "nested-in-class")] = (typeof(NC_rs_Uri.V), typeof(Uri)), + [("record-struct", "Uri", "nested-in-struct")] = (typeof(NS_rs_Uri.V), typeof(Uri)), + [("record-struct", "Uri", "nested-two-levels")] = (typeof(N2_rs_Uri.Inner.V), typeof(Uri)), + [("sealed-record-class", "Uri", "nested-in-class")] = (typeof(NC_rc_Uri.V), typeof(Uri)), + [("sealed-record-class", "Uri", "nested-in-struct")] = (typeof(NS_rc_Uri.V), typeof(Uri)), + [("sealed-record-class", "Uri", "nested-two-levels")] = (typeof(N2_rc_Uri.Inner.V), typeof(Uri)), + [("validated-struct", "Uri", "nested-in-class")] = (typeof(NC_vs_Uri.V), typeof(Uri)), + [("validated-struct", "Uri", "nested-in-struct")] = (typeof(NS_vs_Uri.V), typeof(Uri)), + [("validated-struct", "Uri", "nested-two-levels")] = (typeof(N2_vs_Uri.Inner.V), typeof(Uri)), + [("validated-class", "Uri", "nested-in-class")] = (typeof(NC_vc_Uri.V), typeof(Uri)), + [("validated-class", "Uri", "nested-in-struct")] = (typeof(NS_vc_Uri.V), typeof(Uri)), + [("validated-class", "Uri", "nested-two-levels")] = (typeof(N2_vc_Uri.Inner.V), typeof(Uri)), + [("record-struct", "bool", "nested-in-class")] = (typeof(NC_rs_bool.V), typeof(bool)), + [("record-struct", "bool", "nested-in-struct")] = (typeof(NS_rs_bool.V), typeof(bool)), + [("record-struct", "bool", "nested-two-levels")] = (typeof(N2_rs_bool.Inner.V), typeof(bool)), + [("sealed-record-class", "bool", "nested-in-class")] = (typeof(NC_rc_bool.V), typeof(bool)), + [("sealed-record-class", "bool", "nested-in-struct")] = (typeof(NS_rc_bool.V), typeof(bool)), + [("sealed-record-class", "bool", "nested-two-levels")] = (typeof(N2_rc_bool.Inner.V), typeof(bool)), + [("validated-struct", "bool", "nested-in-class")] = (typeof(NC_vs_bool.V), typeof(bool)), + [("validated-struct", "bool", "nested-in-struct")] = (typeof(NS_vs_bool.V), typeof(bool)), + [("validated-struct", "bool", "nested-two-levels")] = (typeof(N2_vs_bool.Inner.V), typeof(bool)), + [("validated-class", "bool", "nested-in-class")] = (typeof(NC_vc_bool.V), typeof(bool)), + [("validated-class", "bool", "nested-in-struct")] = (typeof(NS_vc_bool.V), typeof(bool)), + [("validated-class", "bool", "nested-two-levels")] = (typeof(N2_vc_bool.Inner.V), typeof(bool)), + [("record-struct", "byte", "nested-in-class")] = (typeof(NC_rs_byte.V), typeof(byte)), + [("record-struct", "byte", "nested-in-struct")] = (typeof(NS_rs_byte.V), typeof(byte)), + [("record-struct", "byte", "nested-two-levels")] = (typeof(N2_rs_byte.Inner.V), typeof(byte)), + [("sealed-record-class", "byte", "nested-in-class")] = (typeof(NC_rc_byte.V), typeof(byte)), + [("sealed-record-class", "byte", "nested-in-struct")] = (typeof(NS_rc_byte.V), typeof(byte)), + [("sealed-record-class", "byte", "nested-two-levels")] = (typeof(N2_rc_byte.Inner.V), typeof(byte)), + [("validated-struct", "byte", "nested-in-class")] = (typeof(NC_vs_byte.V), typeof(byte)), + [("validated-struct", "byte", "nested-in-struct")] = (typeof(NS_vs_byte.V), typeof(byte)), + [("validated-struct", "byte", "nested-two-levels")] = (typeof(N2_vs_byte.Inner.V), typeof(byte)), + [("validated-class", "byte", "nested-in-class")] = (typeof(NC_vc_byte.V), typeof(byte)), + [("validated-class", "byte", "nested-in-struct")] = (typeof(NS_vc_byte.V), typeof(byte)), + [("validated-class", "byte", "nested-two-levels")] = (typeof(N2_vc_byte.Inner.V), typeof(byte)), + [("record-struct", "char", "nested-in-class")] = (typeof(NC_rs_char.V), typeof(char)), + [("record-struct", "char", "nested-in-struct")] = (typeof(NS_rs_char.V), typeof(char)), + [("record-struct", "char", "nested-two-levels")] = (typeof(N2_rs_char.Inner.V), typeof(char)), + [("sealed-record-class", "char", "nested-in-class")] = (typeof(NC_rc_char.V), typeof(char)), + [("sealed-record-class", "char", "nested-in-struct")] = (typeof(NS_rc_char.V), typeof(char)), + [("sealed-record-class", "char", "nested-two-levels")] = (typeof(N2_rc_char.Inner.V), typeof(char)), + [("validated-struct", "char", "nested-in-class")] = (typeof(NC_vs_char.V), typeof(char)), + [("validated-struct", "char", "nested-in-struct")] = (typeof(NS_vs_char.V), typeof(char)), + [("validated-struct", "char", "nested-two-levels")] = (typeof(N2_vs_char.Inner.V), typeof(char)), + [("validated-class", "char", "nested-in-class")] = (typeof(NC_vc_char.V), typeof(char)), + [("validated-class", "char", "nested-in-struct")] = (typeof(NS_vc_char.V), typeof(char)), + [("validated-class", "char", "nested-two-levels")] = (typeof(N2_vc_char.Inner.V), typeof(char)), + [("record-struct", "decimal", "nested-in-class")] = (typeof(NC_rs_decimal.V), typeof(decimal)), + [("record-struct", "decimal", "nested-in-struct")] = (typeof(NS_rs_decimal.V), typeof(decimal)), + [("record-struct", "decimal", "nested-two-levels")] = (typeof(N2_rs_decimal.Inner.V), typeof(decimal)), + [("sealed-record-class", "decimal", "nested-in-class")] = (typeof(NC_rc_decimal.V), typeof(decimal)), + [("sealed-record-class", "decimal", "nested-in-struct")] = (typeof(NS_rc_decimal.V), typeof(decimal)), + [("sealed-record-class", "decimal", "nested-two-levels")] = (typeof(N2_rc_decimal.Inner.V), typeof(decimal)), + [("validated-struct", "decimal", "nested-in-class")] = (typeof(NC_vs_decimal.V), typeof(decimal)), + [("validated-struct", "decimal", "nested-in-struct")] = (typeof(NS_vs_decimal.V), typeof(decimal)), + [("validated-struct", "decimal", "nested-two-levels")] = (typeof(N2_vs_decimal.Inner.V), typeof(decimal)), + [("validated-class", "decimal", "nested-in-class")] = (typeof(NC_vc_decimal.V), typeof(decimal)), + [("validated-class", "decimal", "nested-in-struct")] = (typeof(NS_vc_decimal.V), typeof(decimal)), + [("validated-class", "decimal", "nested-two-levels")] = (typeof(N2_vc_decimal.Inner.V), typeof(decimal)), + [("record-struct", "double", "nested-in-class")] = (typeof(NC_rs_double.V), typeof(double)), + [("record-struct", "double", "nested-in-struct")] = (typeof(NS_rs_double.V), typeof(double)), + [("record-struct", "double", "nested-two-levels")] = (typeof(N2_rs_double.Inner.V), typeof(double)), + [("sealed-record-class", "double", "nested-in-class")] = (typeof(NC_rc_double.V), typeof(double)), + [("sealed-record-class", "double", "nested-in-struct")] = (typeof(NS_rc_double.V), typeof(double)), + [("sealed-record-class", "double", "nested-two-levels")] = (typeof(N2_rc_double.Inner.V), typeof(double)), + [("validated-struct", "double", "nested-in-class")] = (typeof(NC_vs_double.V), typeof(double)), + [("validated-struct", "double", "nested-in-struct")] = (typeof(NS_vs_double.V), typeof(double)), + [("validated-struct", "double", "nested-two-levels")] = (typeof(N2_vs_double.Inner.V), typeof(double)), + [("validated-class", "double", "nested-in-class")] = (typeof(NC_vc_double.V), typeof(double)), + [("validated-class", "double", "nested-in-struct")] = (typeof(NS_vc_double.V), typeof(double)), + [("validated-class", "double", "nested-two-levels")] = (typeof(N2_vc_double.Inner.V), typeof(double)), + [("record-struct", "enum", "nested-in-class")] = (typeof(NC_rs_enum.V), typeof(PEnum)), + [("record-struct", "enum", "nested-in-struct")] = (typeof(NS_rs_enum.V), typeof(PEnum)), + [("record-struct", "enum", "nested-two-levels")] = (typeof(N2_rs_enum.Inner.V), typeof(PEnum)), + [("sealed-record-class", "enum", "nested-in-class")] = (typeof(NC_rc_enum.V), typeof(PEnum)), + [("sealed-record-class", "enum", "nested-in-struct")] = (typeof(NS_rc_enum.V), typeof(PEnum)), + [("sealed-record-class", "enum", "nested-two-levels")] = (typeof(N2_rc_enum.Inner.V), typeof(PEnum)), + [("validated-struct", "enum", "nested-in-class")] = (typeof(NC_vs_enum.V), typeof(PEnum)), + [("validated-struct", "enum", "nested-in-struct")] = (typeof(NS_vs_enum.V), typeof(PEnum)), + [("validated-struct", "enum", "nested-two-levels")] = (typeof(N2_vs_enum.Inner.V), typeof(PEnum)), + [("validated-class", "enum", "nested-in-class")] = (typeof(NC_vc_enum.V), typeof(PEnum)), + [("validated-class", "enum", "nested-in-struct")] = (typeof(NS_vc_enum.V), typeof(PEnum)), + [("validated-class", "enum", "nested-two-levels")] = (typeof(N2_vc_enum.Inner.V), typeof(PEnum)), + [("record-struct", "flags-enum", "nested-in-class")] = (typeof(NC_rs_flags_enum.V), typeof(PFlags)), + [("record-struct", "flags-enum", "nested-in-struct")] = (typeof(NS_rs_flags_enum.V), typeof(PFlags)), + [("record-struct", "flags-enum", "nested-two-levels")] = (typeof(N2_rs_flags_enum.Inner.V), typeof(PFlags)), + [("sealed-record-class", "flags-enum", "nested-in-class")] = (typeof(NC_rc_flags_enum.V), typeof(PFlags)), + [("sealed-record-class", "flags-enum", "nested-in-struct")] = (typeof(NS_rc_flags_enum.V), typeof(PFlags)), + [("sealed-record-class", "flags-enum", "nested-two-levels")] = (typeof(N2_rc_flags_enum.Inner.V), typeof(PFlags)), + [("validated-struct", "flags-enum", "nested-in-class")] = (typeof(NC_vs_flags_enum.V), typeof(PFlags)), + [("validated-struct", "flags-enum", "nested-in-struct")] = (typeof(NS_vs_flags_enum.V), typeof(PFlags)), + [("validated-struct", "flags-enum", "nested-two-levels")] = (typeof(N2_vs_flags_enum.Inner.V), typeof(PFlags)), + [("validated-class", "flags-enum", "nested-in-class")] = (typeof(NC_vc_flags_enum.V), typeof(PFlags)), + [("validated-class", "flags-enum", "nested-in-struct")] = (typeof(NS_vc_flags_enum.V), typeof(PFlags)), + [("validated-class", "flags-enum", "nested-two-levels")] = (typeof(N2_vc_flags_enum.Inner.V), typeof(PFlags)), + [("record-struct", "int", "nested-in-class")] = (typeof(NC_rs_int.V), typeof(int)), + [("record-struct", "int", "nested-in-struct")] = (typeof(NS_rs_int.V), typeof(int)), + [("record-struct", "int", "nested-two-levels")] = (typeof(N2_rs_int.Inner.V), typeof(int)), + [("sealed-record-class", "int", "nested-in-class")] = (typeof(NC_rc_int.V), typeof(int)), + [("sealed-record-class", "int", "nested-in-struct")] = (typeof(NS_rc_int.V), typeof(int)), + [("sealed-record-class", "int", "nested-two-levels")] = (typeof(N2_rc_int.Inner.V), typeof(int)), + [("validated-struct", "int", "nested-in-class")] = (typeof(NC_vs_int.V), typeof(int)), + [("validated-struct", "int", "nested-in-struct")] = (typeof(NS_vs_int.V), typeof(int)), + [("validated-struct", "int", "nested-two-levels")] = (typeof(N2_vs_int.Inner.V), typeof(int)), + [("validated-class", "int", "nested-in-class")] = (typeof(NC_vc_int.V), typeof(int)), + [("validated-class", "int", "nested-in-struct")] = (typeof(NS_vc_int.V), typeof(int)), + [("validated-class", "int", "nested-two-levels")] = (typeof(N2_vc_int.Inner.V), typeof(int)), + [("record-struct", "long", "nested-in-class")] = (typeof(NC_rs_long.V), typeof(long)), + [("record-struct", "long", "nested-in-struct")] = (typeof(NS_rs_long.V), typeof(long)), + [("record-struct", "long", "nested-two-levels")] = (typeof(N2_rs_long.Inner.V), typeof(long)), + [("sealed-record-class", "long", "nested-in-class")] = (typeof(NC_rc_long.V), typeof(long)), + [("sealed-record-class", "long", "nested-in-struct")] = (typeof(NS_rc_long.V), typeof(long)), + [("sealed-record-class", "long", "nested-two-levels")] = (typeof(N2_rc_long.Inner.V), typeof(long)), + [("validated-struct", "long", "nested-in-class")] = (typeof(NC_vs_long.V), typeof(long)), + [("validated-struct", "long", "nested-in-struct")] = (typeof(NS_vs_long.V), typeof(long)), + [("validated-struct", "long", "nested-two-levels")] = (typeof(N2_vs_long.Inner.V), typeof(long)), + [("validated-class", "long", "nested-in-class")] = (typeof(NC_vc_long.V), typeof(long)), + [("validated-class", "long", "nested-in-struct")] = (typeof(NS_vc_long.V), typeof(long)), + [("validated-class", "long", "nested-two-levels")] = (typeof(N2_vc_long.Inner.V), typeof(long)), + [("record-struct", "string", "nested-in-class")] = (typeof(NC_rs_string.V), typeof(string)), + [("record-struct", "string", "nested-in-struct")] = (typeof(NS_rs_string.V), typeof(string)), + [("record-struct", "string", "nested-two-levels")] = (typeof(N2_rs_string.Inner.V), typeof(string)), + [("sealed-record-class", "string", "nested-in-class")] = (typeof(NC_rc_string.V), typeof(string)), + [("sealed-record-class", "string", "nested-in-struct")] = (typeof(NS_rc_string.V), typeof(string)), + [("sealed-record-class", "string", "nested-two-levels")] = (typeof(N2_rc_string.Inner.V), typeof(string)), + [("validated-struct", "string", "nested-in-class")] = (typeof(NC_vs_string.V), typeof(string)), + [("validated-struct", "string", "nested-in-struct")] = (typeof(NS_vs_string.V), typeof(string)), + [("validated-struct", "string", "nested-two-levels")] = (typeof(N2_vs_string.Inner.V), typeof(string)), + [("validated-class", "string", "nested-in-class")] = (typeof(NC_vc_string.V), typeof(string)), + [("validated-class", "string", "nested-in-struct")] = (typeof(NS_vc_string.V), typeof(string)), + [("validated-class", "string", "nested-two-levels")] = (typeof(N2_vc_string.Inner.V), typeof(string)), + [("record-struct", "ulong", "nested-in-class")] = (typeof(NC_rs_ulong.V), typeof(ulong)), + [("record-struct", "ulong", "nested-in-struct")] = (typeof(NS_rs_ulong.V), typeof(ulong)), + [("record-struct", "ulong", "nested-two-levels")] = (typeof(N2_rs_ulong.Inner.V), typeof(ulong)), + [("sealed-record-class", "ulong", "nested-in-class")] = (typeof(NC_rc_ulong.V), typeof(ulong)), + [("sealed-record-class", "ulong", "nested-in-struct")] = (typeof(NS_rc_ulong.V), typeof(ulong)), + [("sealed-record-class", "ulong", "nested-two-levels")] = (typeof(N2_rc_ulong.Inner.V), typeof(ulong)), + [("validated-struct", "ulong", "nested-in-class")] = (typeof(NC_vs_ulong.V), typeof(ulong)), + [("validated-struct", "ulong", "nested-in-struct")] = (typeof(NS_vs_ulong.V), typeof(ulong)), + [("validated-struct", "ulong", "nested-two-levels")] = (typeof(N2_vs_ulong.Inner.V), typeof(ulong)), + [("validated-class", "ulong", "nested-in-class")] = (typeof(NC_vc_ulong.V), typeof(ulong)), + [("validated-class", "ulong", "nested-in-struct")] = (typeof(NS_vc_ulong.V), typeof(ulong)), + [("validated-class", "ulong", "nested-two-levels")] = (typeof(N2_vc_ulong.Inner.V), typeof(ulong)), + }; +} diff --git a/tools/probe/corpus/harness/Equality.cs b/tools/probe/corpus/harness/Equality.cs new file mode 100644 index 00000000..7cabd460 --- /dev/null +++ b/tools/probe/corpus/harness/Equality.cs @@ -0,0 +1,211 @@ +// Claim L05-equality (README.md#what-typekit-guarantees; Generators README "Equality", "Comparison"; oracle "Equality (metamorphic)" in +// .claude/probe-axes.md): equal value objects have equal hash codes, CompareTo is zero exactly when Equals is true, and both agree with the +// wrapped type's (ordinal for a string), through every base in the inheritance chain, and wherever the value object is held. A closed set's +// members are singletons, so its equality is reference equality (docs/closed-sets.md). +// Oracle: metamorphic. Two value objects over values the wrapped type calls equal in different forms (7.8m and 7.80m, 0.0 and -0.0, one +// instant at two offsets, two string instances), and one over a value it calls unequal and orders after. +using System.Collections; +using System.Reflection; +using Probes.Shapes; +namespace Probes.Json; + +public sealed record L05Entry(string Kind, string Wrapped, string Inheritance, string Site, Type Type, Func<object> A1, Func<object> A2, Func<object> B); + +public abstract record L05PolyBase; +public sealed record L05PolyDerived<T> : L05PolyBase { public T Value { get; set; } = default!; } + +public static partial class L05Registry +{ + static readonly List<L05Entry> All = []; + static L05Registry() => Fill(); + static partial void Fill(); + static void Add(string kind, string wrapped, string inheritance, string site, Type type, Func<object> a1, Func<object> a2, Func<object> b) => + All.Add(new(kind, wrapped, inheritance, site, type, a1, a2, b)); + public static L05Entry? Get(string kind, string wrapped, string inheritance, string site) => + All.FirstOrDefault(x => x.Kind == kind && x.Wrapped == wrapped && x.Inheritance == inheritance && x.Site == site); +} + +public static class Equality +{ + static IEqualityComparer Comparer(Type t) => (IEqualityComparer)typeof(EqualityComparer<>).MakeGenericType(t).GetProperty("Default")!.GetValue(null)!; + static int Sign(int x) => Math.Sign(x); + + static object Make(Type v, Type w, object value) + { + var m = v.GetMethods(BindingFlags.Public | BindingFlags.Static) + .First(x => x.Name is "From" or "FromKnownGood" && x.GetParameters() is [var p, ..] && p.ParameterType == w); + var args = m.GetParameters().Select((p, i) => i == 0 ? value : Type.Missing).ToArray(); + return m.Invoke(null, BindingFlags.OptionalParamBinding | BindingFlags.Default, null, args, null)!; + } + + static object? Op(Type t, string name, object a, object b) => + t.GetMethod(name, BindingFlags.Public | BindingFlags.Static, [t, t]) is { } m ? m.Invoke(null, [a, b]) : null; + + public static string? Check(Row r) + { + var kind = r["shape.kind"]; var inh = r["shape.inheritance"]; var site = r["shape.site"]; + var closed = kind == "closed-set"; + // claims.json excludes the pairs no type can have; a row the covering array made with one anyway runs the nearest shape that exists + if (site == "in-generic-type") { r.Substituted("shape.site", "top-level", "CMTK1005/CMTK1018: nothing nests in a generic type"); site = "top-level"; } + if (kind is "record-struct" or "validated-struct" && inh.StartsWith("base-")) { r.Substituted("shape.inheritance", "none", "a struct has no base type"); inh = "none"; } + var wrapped = closed ? "-" : r["shape.wrapped"]; + var en = L05Registry.Get(kind, wrapped, inh, site); + if (en is null) { r.Outcome("harness-error", $"no L05 shape for {kind}/{wrapped}/{inh}/{site}: run harness/gen_json_family.py"); return null; } + var v = en.Type; + object x1, x2, y; bool eq12; + Type? w = null; object? a1 = null, b = null; + if (closed) + { + x1 = en.A1(); x2 = en.A2(); y = en.B(); + if (!ReferenceEquals(x1, x2)) return "closed set: the member found in All is not the member itself"; + eq12 = true; + } + else + { + w = v.GetProperty("Value")!.PropertyType; + a1 = en.A1(); var a2 = en.A2(); b = en.B(); + var cw = Comparer(w); + if (!cw.Equals(a1, a2) || cw.Equals(a1, b)) { r.Outcome("harness-error", $"samples of {wrapped}: {a1} and {a2} must be equal, {b} not"); return null; } + eq12 = true; + x1 = Make(v, w, a1); x2 = Make(v, w, a2); y = Make(v, w, b); + if (!cw.Equals(v.GetProperty("Value")!.GetValue(x1), a1)) return $"Value of {v.Name} is not the value it was made from"; + } + // each failure goes to the obligation it breaks: the value object's own equality and wherever it is held, its ordering, its bases + List<string> eqF = [], ordF = [], baseF = []; + var fails = eqF; + void Expect(bool got, bool want, string what) { if (got != want) fails.Add($"{what}: {got}, expected {want}"); } + + // the value object's own equality + Expect(x1.Equals(x2), eq12, "Equals(object) of equal values"); + Expect(x1.Equals(y), false, "Equals(object) of unequal values"); + Expect(x1.Equals(null), false, "Equals(null)"); + Expect(Comparer(v).Equals(x1, x2), eq12, "EqualityComparer<V> of equal values"); + Expect(Comparer(v).Equals(x1, y), false, "EqualityComparer<V> of unequal values"); + if (x1!.GetHashCode() != x2!.GetHashCode()) fails.Add($"equal values hash apart: {x1.GetHashCode()} and {x2.GetHashCode()}"); + if (Comparer(v).GetHashCode(x1) != Comparer(v).GetHashCode(x2)) fails.Add("EqualityComparer<V> hashes equal values apart"); + if (Op(v, "op_Equality", x1, x2) is { } e12) Expect((bool)e12, eq12, "== of equal values"); else if (!closed) fails.Add("no operator =="); + if (Op(v, "op_Equality", x1, y) is { } e1b) Expect((bool)e1b, false, "== of unequal values"); + if (Op(v, "op_Inequality", x1, x2) is { } n12) Expect((bool)n12, !eq12, "!= of equal values"); + + // ordering: generated where the wrapped type is comparable, ordinal for a string, zero exactly when equal + fails = ordF; + var icv = typeof(IComparable<>).MakeGenericType(v); + if (!closed) + { + var comparableW = typeof(IComparable<>).MakeGenericType(w!).IsAssignableFrom(w) || typeof(IComparable).IsAssignableFrom(w); // an enum is IComparable only + if (icv.IsAssignableFrom(v) != comparableW) fails.Add($"IComparable<{v.Name}> is {(icv.IsAssignableFrom(v) ? "" : "not ")}implemented, the wrapped {w!.Name} is {(comparableW ? "" : "not ")}comparable"); + } + if (icv.IsAssignableFrom(v)) + { + var cmp = icv.GetMethod("CompareTo")!; + var c12 = (int)cmp.Invoke(x1, [x2])!; var c1b = (int)cmp.Invoke(x1, [y])!; var cb1 = (int)cmp.Invoke(y, [x1])!; + Expect(c12 == 0, eq12, "CompareTo of equal values is zero"); + if (c1b == 0) fails.Add("CompareTo of unequal values is zero"); + if (Sign(c1b) != -Sign(cb1)) fails.Add($"CompareTo is not antisymmetric: {c1b} and {cb1}"); + if (!closed) + { + var want = w == typeof(string) ? string.CompareOrdinal((string)a1!, (string)b!) : ((IComparer)typeof(Comparer<>).MakeGenericType(w!).GetProperty("Default")!.GetValue(null)!).Compare(a1, b); + if (Sign(c1b) != Sign(want)) fails.Add($"CompareTo orders {c1b}, the wrapped type {want}"); + } + if (x1 is IComparable nc) { if (Sign(nc.CompareTo(y)) != Sign(c1b) || nc.CompareTo(x2) != 0 == eq12) fails.Add("CompareTo(object) disagrees with CompareTo(TSelf)"); } + if (Op(v, "op_LessThan", x1, y) is { } lt) Expect((bool)lt, c1b < 0, "<"); + if (Op(v, "op_GreaterThan", x1, y) is { } gt) Expect((bool)gt, c1b > 0, ">"); + if (Op(v, "op_LessThanOrEqual", x1, x2) is { } le) Expect((bool)le, true, "<= of equal values"); + if (Op(v, "op_GreaterThanOrEqual", x1, x2) is { } ge) Expect((bool)ge, true, ">= of equal values"); + if (Op(v, "op_LessThan", x1, x2) is { } lt2) Expect((bool)lt2, false, "< of equal values"); + } + + // through every base, and every IEquatable<TBase> it implements + var bases = 0; + for (var t = v.BaseType; t is not null; t = t.BaseType) + { + // object and ValueType are every value object's, a base of none: held as one, it is its own equality + var own = t == typeof(object) || t == typeof(ValueType); + if (!own) bases++; + fails = own ? eqF : baseF; + var c = Comparer(t); + Expect(c.Equals(x1, x2), eq12, $"EqualityComparer<{t.Name}> of equal values"); + Expect(c.Equals(x1, y), false, $"EqualityComparer<{t.Name}> of unequal values"); + if (c.GetHashCode(x1) != c.GetHashCode(x2)) fails.Add($"EqualityComparer<{t.Name}> hashes equal values apart"); + if (Op(t, "op_Equality", x1, x2) is { } be) Expect((bool)be, eq12, $"{t.Name}'s == of equal values"); + var set = (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(t))!; + var add = set.GetType().GetMethod("Add")!; add.Invoke(set, [x1]); add.Invoke(set, [x2]); add.Invoke(set, [y]); + var count = set.Cast<object>().Count(); + if (count != 2) fails.Add($"HashSet<{t.Name}> of two equal values and one other holds {count}"); + } + foreach (var i in v.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEquatable<>))) + { + fails = i.GetGenericArguments()[0] == v ? eqF : baseF; // IEquatable<TSelf> is its own equality, IEquatable<TBase> a base's + var m = i.GetMethod("Equals")!; + Expect((bool)m.Invoke(x1, [x2])!, eq12, $"{Jx.Name(i)}.Equals of equal values"); + Expect((bool)m.Invoke(x1, [y])!, false, $"{Jx.Name(i)}.Equals of unequal values"); + } + + // wherever it is held + fails = eqF; + var position = r["shape.position"]; + var containing = position == "root" ? "class" : r["shape.containing"]; + var e = new Entry(kind, wrapped, v, v, () => x1, () => null, typeof(L05PolyBase), typeof(L05PolyDerived<>).MakeGenericType(v), typeof(L05PolyBase), typeof(L05PolyDerived<>).MakeGenericType(v)); + var h1 = Pos.Build(position, containing, e, true, x1); var h2 = Pos.Build(position, containing, e, true, x2); var h3 = Pos.Build(position, containing, e, true, y); + object? Member(Held h) => position == "root" ? h.Value : h.Declared.GetProperty("P") is { } p ? p.GetValue(h.Value) : h.Declared.GetField("F")!.GetValue(h.Value); + switch (position) + { + case "root": break; + case "list-element" or "array-element" or "immutable-array-element" or "read-only-list-element": + { + var l = (IList)Member(h1)!; + Expect(l.Contains(x2), eq12, $"{Jx.Name(l.GetType())}.Contains of an equal value"); + Expect(l.Contains(y), false, $"{Jx.Name(l.GetType())}.Contains of an unequal value"); + if (l.IndexOf(x2) != 0) fails.Add("IndexOf of an equal value is " + l.IndexOf(x2)); + break; + } + case "dictionary-key": + { + var d = (IDictionary)Member(h1)!; + Expect(d.Contains(x2), eq12, "dictionary lookup by an equal key"); + Expect(d.Contains(y), false, "dictionary lookup by an unequal key"); + break; + } + case "dictionary-value": + { + var d = Member(h1)!; var cv = d.GetType().GetMethod("ContainsValue")!; + Expect((bool)cv.Invoke(d, [x2])!, eq12, "ContainsValue of an equal value"); + Expect((bool)cv.Invoke(d, [y])!, false, "ContainsValue of an unequal value"); + break; + } + default: + { + // a container with value equality compares through the member; one with reference equality is compared by its member + var byValue = containing is "record-class" or "record-struct" or "struct"; + var (p1, p2, p3) = byValue ? (h1.Value, h2.Value, h3.Value) : (Member(h1), Member(h2), Member(h3)); + var what = byValue ? Jx.Name(h1.Declared) : $"the {position} of {Jx.Name(h1.Declared)}"; + Expect(Equals(p1, p2), eq12, what + " holding equal values"); + Expect(Equals(p1, p3), false, what + " holding unequal values"); + if (p1 is not null && p2 is not null && p1.GetHashCode() != p2.GetHashCode()) fails.Add(what + " holding equal values hashes them apart"); + break; + } + } + string What(List<string> f) => $"{Jx.Name(v)} ({kind}, {wrapped}, {inh}, {site}, {position} in {containing}): " + string.Join("; ", f); + var f1 = r.Check("equality-is-the-underlying-values", Baseline.Twin, c => + { + if (eqF.Count > 0) return What(eqF); + c.Note(NoteKeys.Compared, $"{v.Name} {position}: equality, against EqualityComparer<{(w is null ? v.Name : w.Name)}>"); + return null; + }); + var f2 = r.Check("ordering-follows-the-underlying-type", Baseline.Twin, c => + { + if (ordF.Count > 0) return What(ordF); + c.Note(NoteKeys.Compared, $"{v.Name}: ordering{(icv.IsAssignableFrom(v) ? "" : ", none generated where the wrapped type has none")}"); + return null; + }); + // a value object with a base record: each base compares it as it does (the chain is shape.inheritance's) + var f3 = bases == 0 ? null : r.Check("through-every-base", Baseline.Twin, c => + { + _ = c["shape.inheritance"]; + if (baseF.Count > 0) return What(baseF); + c.Note(NoteKeys.Compared, $"{v.Name}: {bases} base type(s)"); + return null; + }); + return f1 ?? f2 ?? f3; + } +} diff --git a/tools/probe/corpus/harness/FaultClaim.cs b/tools/probe/corpus/harness/FaultClaim.cs new file mode 100644 index 00000000..03018dd1 --- /dev/null +++ b/tools/probe/corpus/harness/FaultClaim.cs @@ -0,0 +1,412 @@ +// The shared runner of the fault claims C04-without-faults, C06-fault-kinds, C14-declared-problem and C15-mvc-fault-status-doc +// (AspNetCore README "A closed set as the fault type", "A status per endpoint", "Leaving out what a route cannot answer"; CHANGELOG 2.1.0 Fixed). +// A claim is a Spec: its minimal endpoints as a tree of groups, the MVC controllers of FxCtl (FaultClaimControllers.g.cs), what +// MapControllers()'s builder adds, and the errors each endpoint reports. Each declaration is listed where it is made, so an endpoint's +// declarations come in ASP.NET's metadata order: outer group, inner group, endpoint; or controller, action, then MapControllers()'s builder. +// The oracle is the README's rules applied to those declarations, never the product's own reading of them: +// - a member is answered with the last status its endpoint's metadata sets for it, else the mapped one ("When several statuses name one +// member, the last in ASP.NET's metadata order wins"); the mapping is by kind (README table), or its own with a problem type per kind; +// - a member left out anywhere in that metadata is listed nowhere in the operation, and reporting it throws an InvalidOperationException +// naming the endpoint and the member ("Leave out only what the route can't report"); +// - the operation lists every status of every remaining member of every answered type, the endpoint's own declared ones among them, all its +// responses ascending; each fault status's problem schema describes fault and rule, and its description names exactly the problem types +// of the members answered with it ("names its problem types", CHANGELOG 2.1.0); +// - a status that every endpoint of its scope leaves out stops the host, naming the member and the status. +// Each comparison is about one Aspect, and a claim's Spec.Of names the obligations it is checked for: the row runs one check (Row.Check) per +// obligation it reaches, against the README's rules (Spec.Against) or, for a refusal, the documented one (Spec.Refusals), every check on the +// one host and the answers fetched once. +// http.response picks what every endpoint of the row answers (one fault type, a composed fault, two types writing one name) and what the +// outermost scope adds (a status for another member, a member left out, a problem the endpoint declares itself). +using System.Text.Json.Nodes; +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Probes.Shapes; +namespace Probes.Faults; + +/// <summary>One declaration about one member: a status (WithFaultStatus, [FaultStatus]) or, with status 0, the member left out.</summary> +public sealed record Decl(string Type, string Member, int Status) +{ + public bool Out => Status == 0; + public bool Names(Mem m) => Type == m.Type && Member == m.Name; + public override string ToString() => Out ? $"WithoutFaults({Type}.{Member})" : $"WithFaultStatus({Type}.{Member}, {Status})"; +} +/// <summary>An MVC action of FxCtl: its route (before the result-type segment), what it declares, the problem statuses it declares itself.</summary> +public sealed record Act(string Route, Decl[] Decls, int[] Declared); +/// <summary>An MVC controller of FxCtl: the [ApiController] and the plain copy, what the class declares, its actions.</summary> +public sealed record Ctl(Type Api, Type Plain, Decl[] Decls, Act[] Actions); +public static partial class FxCtl +{ + public static readonly Dictionary<string, Ctl> All = Build(); + static Dictionary<string, Ctl> Build() { var d = new Dictionary<string, Ctl>(StringComparer.Ordinal); Fill(d); return d; } + static partial void Fill(Dictionary<string, Ctl> d); +} + +/// <summary>A minimal endpoint, relative to its group; How: "pp" = ProducesProblem(each Declared), "prt" = [ProducesResponseType<ProblemDetails>(404)].</summary> +public sealed record MEp(string Route, Decl[] Decls, int[]? Declared = null, string? How = null); +/// <summary>A route group: its prefix (never empty), what it declares, its inner groups and endpoints.</summary> +public sealed record MGrp(string Prefix, Decl[] Decls, MGrp[] Groups, MEp[] Eps); + +/// <summary>A member of a fault type the endpoints answer with, its kind and the status its kind maps to (README table).</summary> +public sealed record Mem(string Type, string Name, string Kind, int Status) { public string Q => Type + "." + Name; } + +public static class FxErr +{ + public static readonly Mem[] H = + [ + new("HFault", "Missing", "NotFound", 404), new("HFault", "Clash", "Conflict", 409), new("HFault", "Bad", "Invalid", 422), + new("HFault", "Denied", "Forbidden", 403), new("HFault", "Down", "Unavailable", 503), new("HFault", "Stale", "PreconditionFailed", 412), + new("HFault", "Garbled", "Malformed", 400), + ]; + static readonly Mem[] G = [new("GFault", "Gone", "NotFound", 404), new("GFault", "Raced", "Conflict", 409), new("GFault", "Old", "PreconditionFailed", 412)]; + static readonly Mem[] K = [new("KFault", "Clash", "Conflict", 409), new("KFault", "Lost", "NotFound", 404)]; + /// <summary>Every member of every type an endpoint answering <paramref name="t"/> answers with.</summary> + public static Mem[] Answered(string t) => t == "two" ? [.. H, .. G] : t == "dup" ? [.. H, .. K] : H; + /// <summary>e: 1-based index into H; 0 is a success.</summary> + static HFault Of(int e) => (HFault)typeof(HFault).GetField(H[e - 1].Name)!.GetValue(null)!; + public static Result<Fault<HFault>> One(int e) { if (e == 0) return Result<Fault<HFault>>.Success(); return Fault.Of(Of(e)); } + public static Result<Fault<HFault, GFault>> Two(int e) => e == 0 ? Result<Fault<HFault, GFault>>.Success() : Fault<HFault, GFault>.Of(Of(e)); + public static Result<Fault<HFault, KFault>> Dup(int e) => e == 0 ? Result<Fault<HFault, KFault>>.Success() : Fault<HFault, KFault>.Of(Of(e)); + public static int E(string member) => Array.FindIndex(H, m => m.Name == member) + 1; +} + +/// <summary>One endpoint as the oracle sees it: its URL in this host, the result it answers, its declarations in metadata order.</summary> +public sealed record Ep(bool Mvc, string Url, string T, Decl[] Decls, int[] Declared, Func<Mem, (int Status, string? Type)> Map) +{ + public bool LeftOut(Mem m) => Decls.Any(d => d.Out && d.Names(m)); + public int StatusOf(Mem m) => Decls.LastOrDefault(d => !d.Out && d.Names(m))?.Status ?? Map(m).Status; + public SortedSet<int> FaultStatuses() => new(FxErr.Answered(T).Where(m => !LeftOut(m)).Select(StatusOf)); + public SortedSet<int> DocStatuses() { var s = FaultStatuses(); foreach (var d in Declared) s.Add(d); return s; } + public HashSet<string> TypesAt(int status) => + [.. FxErr.Answered(T).Where(m => !LeftOut(m) && StatusOf(m) == status).Select(m => Map(m).Type).OfType<string>()]; +} + +/// <summary>What one comparison of <see cref="FaultClaim.Run"/> is about; a claim's <see cref="Spec.Of"/> names the obligations each is checked for.</summary> +public static class Aspect +{ + /// <summary>The host starts, or refuses to as the README says.</summary> + public const string Start = "start"; + /// <summary>Reporting a member left out throws, naming the endpoint and the member.</summary> + public const string LeftOut = "left-out"; + /// <summary>A member's status, on the wire and in the problem's status field.</summary> + public const string Status = "status"; + /// <summary>The problem's fault, its type and its detail.</summary> + public const string Fault = "fault", Type = "type", Detail = "detail"; + /// <summary>The operation's statuses (which), their order, the problem schema's fault and rule, and the problem types its description names.</summary> + public const string DocListed = "doc-listed", DocOrder = "doc-order", DocDescribes = "doc-describes", DocTypes = "doc-types"; + public static readonly string[] Answer = [Status, Fault, Type, Detail]; + public static readonly string[] Doc = [DocListed, DocOrder, DocDescribes, DocTypes]; +} + +/// <summary>What one claim's row asks: see the header.</summary> +public sealed class Spec +{ + /// <summary>The obligations a comparison of an aspect is checked for, given the member it is about (none for the start and the document).</summary> + public Func<string, Mem?, string[]> Of = (_, _) => ["claim"]; + /// <summary>What the README's rules are compared as: the sentence of the claim's source.</summary> + public Baseline Against = Baseline.Model; + /// <summary>The obligations whose check holds a refusal by its type and what it names.</summary> + public string[] Refusals = []; + public MGrp? Minimal; // under the outer group /m, where the host has minimal APIs + public string[] Controllers = []; // FxCtl keys, where it has MVC + public Decl[] Builder = []; // added to MapControllers()'s builder, after the response's own + public int[] Errors = []; // the HFault members each endpoint reports (FxErr.E) + public bool Typed = true; // the host's mapping gives a problem type per kind; else AddFaultResponses<TSet>() by kind + public string[]? Refused; // the host must not start, with an InvalidOperationException naming each of these + public Func<Exception, string?>? RefusedAs; // or: the host must not start, and this judges the exception (null: as expected) + public Func<Mem, (int Status, string? Type)>? Map; // how the host maps each member; default by kind, with a type per kind where Typed + public string? Mapping; // HostCfg.Mapping in place of Typed's + public Action<IServiceCollection>? Services; + public Action<IEndpointRouteBuilder>? Extra; // more minimal endpoints, at the root + public Type[] ExtraControllers = []; +} + +/// <summary>What http.response makes every endpoint of the row answer, and what the outermost scope adds.</summary> +public sealed record Resp(string T, Decl[] Outer, int[] Declared) +{ + public static Resp Of(Row r) => r["http.response"] switch + { + "composed-fault" => new("two", [], []), + "two-faults-same-name" => new("dup", [], []), + "group-with-fault-status" => new("one", [new("HFault", "Clash", 422)], []), + "endpoint-without-faults" => new("one", [new("HFault", "Missing", 0)], []), + "declared-produces-problem" => new("decl", [], [429]), + _ => new("one", [], []), + }; +} + +/// <summary>The baseline's problem response in MVC (see FaultClaim.DocRefusedByAspNetAlone).</summary> +[ApiController] +internal sealed class FxProblemBaseline : ControllerBase +{ + [HttpGet("baseline-problem/c"), ProducesResponseType<ProblemDetails>(404, "application/problem+json")] public IActionResult Get() => NoContent(); +} + +public static class FaultClaim +{ + public static string TypeOf(string kind) => "https://p.example/" + kind; + static string? Un(Row r, string? s) => r.Is("json.Converters", "own-for-wrapped") && s is not null && s.StartsWith("own:") ? s[4..].Trim('"') : s; + static string Cut(string? s) => s is null ? "(null)" : s.Length > 300 ? s[..300] + "..." : s; + static void Process(Row r, Entry en) { if (r.Is("process.converter-lookup", "wrapped-type-converter-looked-up-first")) _ = System.ComponentModel.TypeDescriptor.GetConverter(Make.Held(en.Plain)); } + + static T Member<T>(string name) => (T)typeof(T).GetField(name)!.GetValue(null)!; + static TB Apply<TB>(TB b, Decl d) where TB : IEndpointConventionBuilder => (d.Type, d.Out) switch + { + ("HFault", true) => b.WithoutFaults(Member<HFault>(d.Member)), + ("HFault", false) => b.WithFaultStatus(Member<HFault>(d.Member), d.Status), + ("GFault", true) => b.WithoutFaults(Member<GFault>(d.Member)), + ("GFault", false) => b.WithFaultStatus(Member<GFault>(d.Member), d.Status), + ("KFault", true) => b.WithoutFaults(Member<KFault>(d.Member)), + _ => b.WithFaultStatus(Member<KFault>(d.Member), d.Status), + }; + + static RouteHandlerBuilder MapT(IEndpointRouteBuilder g, string path, string t, string? how) => (t, how) switch + { + ("two", "prt") => g.MapGet(path, [ProducesResponseType<ProblemDetails>(404)] (int e) => FxErr.Two(e).ToHttpResult()), + ("dup", "prt") => g.MapGet(path, [ProducesResponseType<ProblemDetails>(404)] (int e) => FxErr.Dup(e).ToHttpResult()), + (_, "prt") => g.MapGet(path, [ProducesResponseType<ProblemDetails>(404)] (int e) => FxErr.One(e).ToHttpResult()), + ("two", _) => g.MapGet(path, (int e) => FxErr.Two(e).ToHttpResult()), + ("dup", _) => g.MapGet(path, (int e) => FxErr.Dup(e).ToHttpResult()), + _ => g.MapGet(path, (int e) => FxErr.One(e).ToHttpResult()), + }; + + /// <summary>Walks a minimal tree: maps it where <paramref name="b"/> is given, and lists its endpoints with their declarations in metadata order.</summary> + static void Walk(IEndpointRouteBuilder? b, MGrp g, Decl[] above, string prefix, Resp resp, List<(string Path, Decl[] Decls, int[] Declared)> eps) + { + RouteGroupBuilder? grp = null; + if (b is not null) { grp = b.MapGroup(g.Prefix); foreach (var d in g.Decls) Apply(grp, d); } + Decl[] decls = [.. above, .. g.Decls]; + var pre = prefix + g.Prefix; + foreach (var sub in g.Groups) Walk(grp, sub, decls, pre, resp, eps); + foreach (var ep in g.Eps) + { + var path = "/" + ep.Route + "/" + resp.T; + if (grp is not null) + { + var rb = MapT(grp, path, resp.T, ep.How); + foreach (var d in ep.Decls) Apply(rb, d); + if (ep.How == "pp") foreach (var s in ep.Declared ?? []) rb.ProducesProblem(s); + if (resp.T == "decl") rb.ProducesProblem(429); + } + eps.Add((pre + path, [.. decls, .. ep.Decls], [.. ep.Declared ?? [], .. resp.Declared])); + } + } + + static (List<Ep> Eps, HostCfg Cfg) Plan(Row r, Spec s, Resp resp) + { + var eps = new List<Ep>(); + var map = s.Map ?? (m => (m.Status, s.Typed ? TypeOf(m.Kind) : (string?)null)); + MGrp? root = s.Minimal is null ? null : new MGrp("/m", resp.Outer, [s.Minimal], []); + if (root is not null && Hx.HasMinimal(r)) + { + var found = new List<(string Path, Decl[] Decls, int[] Declared)>(); + Walk(null, root, [], "", resp, found); + eps.AddRange(found.Select(x => new Ep(false, Hx.M(r, x.Path), resp.T, x.Decls, x.Declared, map))); + } + var controllers = new List<Type>(); + Decl[] builder = [.. resp.Outer, .. s.Builder]; + if (Hx.HasMvc(r)) + foreach (var key in s.Controllers) + { + var c = FxCtl.All[key]; + controllers.Add(Hx.Plain(r) ? c.Plain : c.Api); + foreach (var a in c.Actions) + eps.Add(new Ep(true, Hx.C(r, "/c/" + a.Route + "/" + resp.T), resp.T, [.. c.Decls, .. a.Decls, .. builder], [.. a.Declared, .. resp.Declared], map)); + } + if (Hx.HasMvc(r)) controllers.AddRange(s.ExtraControllers); + var cfg = new HostCfg + { + FaultControllers = true, Controllers = [.. controllers], + Mapping = s.Mapping ?? (s.Typed ? "own-IFault-mapping" : "per-set"), + Services = sv => { if (s.Mapping is null && !s.Typed) sv.AddFaultResponses<KFault>(); s.Services?.Invoke(sv); }, + Minimal = b => { if (root is not null) Walk(b, root, [], "", resp, []); s.Extra?.Invoke(b); }, + ControllerBuilder = cb => { foreach (var d in builder) Apply(cb, d); return cb; }, + }; + return (eps, cfg); + } + + public static async Task<string?> Run(Row r, Spec s) + { + var en = Registry.Of(r); Process(r, en); + var resp = Resp.Of(r); + var (eps, cfg) = Plan(r, s, resp); + if (eps.Count == 0) return "harness: the row maps no endpoint of the claim (an excluded pair?)"; + WebApplication? app = null; Exception? down = null; + try { app = await Hx.Start(r, en, cfg); } + catch (Exception ex) when (ex is not Sides.InputsNotEquivalent) { down = ex; } + await using var _ = app; + Baseline Against(string o) => s.Refusals.Contains(o) ? Baseline.Refusal : s.Against; + string? failed = null; + + // A host the README says must not start: that is the row's one comparison. + if (s.Refused is not null || s.RefusedAs is not null) + { + foreach (var o in s.Of(Aspect.Start, null)) + { + var f = r.Check(o, Against(o), _ => down is null + ? (s.RefusedAs is not null ? "host started, though the README says this set stops the start" + : $"host started, though a status every endpoint of its scope leaves out must stop it ({string.Join(", ", s.Refused!)})") + : StartRefused(s, down)); + failed ??= f; + } + return failed; + } + + var members = s.Errors.Select(e => FxErr.H[e - 1]).ToList(); + var obligations = Aspect.Answer.Append(Aspect.LeftOut).SelectMany(a => members.SelectMany(m => s.Of(a, m))) + .Concat(Aspect.Doc.SelectMany(a => s.Of(a, null))).Distinct().ToList(); + if (down is not null) + { + foreach (var o in obligations) { var f = r.Check(o, Against(o), _ => "host did not start: " + down.GetType().Name + ": " + Cut(down.Message)); failed ??= f; } + return failed; + } + + var display = app!.Services.GetRequiredService<EndpointDataSource>().Endpoints.OfType<RouteEndpoint>() + .GroupBy(e => "/" + (e.RoutePattern.RawText ?? "").TrimStart('/'), StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key, g => g.First().DisplayName ?? "", StringComparer.OrdinalIgnoreCase); + JsonNode? doc = null; Exception? docFailed = null; + try { doc = await Hx.Doc(app); } + catch (Exception ex) { docFailed = ex; } + var wires = new Dictionary<(string Url, int E), Hx.Wire>(); + foreach (var ep in eps) + foreach (var e in s.Errors) + wires[(ep.Url, e)] = await Hx.Get(app, $"{ep.Url}?e={e}"); + + foreach (var o in obligations) + { + var f = await r.Check(o, Against(o), async c => + { + var wire = false; // whether the check compared an answer on the wire before the document + bool want(string aspect, Mem? m) + { + var wanted = s.Of(aspect, m).Contains(o); + if (wanted && !Aspect.Doc.Contains(aspect)) wire = true; + return wanted; + } + foreach (var ep in eps) + foreach (var e in s.Errors) + if (Wire(r, ep, FxErr.H[e - 1], wires[(ep.Url, e)], display, want) is { } fail) return fail; + if (!Aspect.Doc.Any(a => want(a, null))) return null; + // A document ASP.NET alone refuses too has nothing to check (noted there); any other failure is returned. + if (docFailed is not null) return await DocRefusedByAspNetAlone(c, r, en, cfg, docFailed, wire); + foreach (var ep in eps) + if (DocOf(r, doc!, ep, want) is { } fail) return fail; + return null; + }); + failed ??= f; + } + return failed; + } + + /// <summary>The host refused to start: as the README says, with an InvalidOperationException naming each of <see cref="Spec.Refused"/>, or as <see cref="Spec.RefusedAs"/> judges.</summary> + static string? StartRefused(Spec s, Exception ex) + { + if (s.RefusedAs is { } judge) return judge(ex); + var names = s.Refused!; + var ioe = ex as InvalidOperationException ?? ex.InnerException as InvalidOperationException; + if (ioe is null) return $"host refused to start, but with {ex.GetType().Name}, not InvalidOperationException: {Cut(ex.Message)}"; + foreach (var name in names) + if (!ioe.Message.Contains(name, StringComparison.Ordinal)) return $"host refused to start, but not naming {name}: {Cut(ioe.Message)}"; + return null; + } + + /// <summary> + /// Hx.DocRefusedByAspNetAlone, with a baseline that has a problem response: the same host without AddTypeKit(), plus one endpoint (and one + /// action, where the host has MVC) declaring ProducesProblem(404). AddTypeKit() adds its problem responses "as ProducesProblem would" + /// (README "A Result as a response"), so ASP.NET alone writing a document of the same host without any problem response says nothing about + /// them: under json.MaxDepth=3 System.Text.Json refuses the problem schema of ASP.NET's own ProducesProblem too (decision 99). + /// </summary> + static async Task<string?> DocRefusedByAspNetAlone(Check c, Row r, Entry en, HostCfg cfg, Exception withTypeKit, bool comparedBefore) + { + static string Say(Exception x) => x.GetType().Name + ": " + Cut(x.Message); + var alone = cfg.Copy(); alone.TypeKitOpenApi = false; + alone.Controllers = [.. cfg.Controllers ?? [], typeof(FxProblemBaseline)]; + alone.Minimal = b => { cfg.Minimal?.Invoke(b); b.MapGet("/baseline-problem", () => Results.NoContent()).ProducesProblem(404); }; + try + { + await using var app = await Hx.Start(r, en, alone); + await Hx.Doc(app); + } + // the same refusal by type and stage, never by its words (Hx.DocAlike) + catch (Exception ex) when (Hx.DocAlike(ex, withTypeKit)) + { + Hx.DocRefused(r, ex, comparedBefore); + return null; + } + catch (Exception ex) when (r.Is("json.MaxDepth", "3") && Depth(ex) && Depth(withTypeKit)) + { + // Both refuse the problem schema under MaxDepth=3, by System.Text.Json's own depth checks, but on different paths (TypeKit's + // transformer through the schema exporter, ProducesProblem through the document writer): a refusal of STJ's own, nothing to check + // (decision 99: under MaxDepth=3 System.Text.Json refuses the OpenAPI document of a problem response for plain ASP.NET too). + return c.Refused(Cite.Record("0099"), $"{Say(withTypeKit)} / {Say(ex)}"); + } + catch (Exception ex) { return $"document: AddTypeKit() fails ({Say(withTypeKit)}), ASP.NET alone with a ProducesProblem fails otherwise ({Say(ex)})"; } + return $"document: AddTypeKit() fails ({Say(withTypeKit)}), ASP.NET alone with a ProducesProblem writes it"; + } + + static bool Depth(Exception x) => x is InvalidOperationException or System.Text.Json.JsonException; + + /// <summary>One endpoint's answer <paramref name="w"/> to reporting <paramref name="m"/>, judged on the aspects <paramref name="want"/> names.</summary> + static string? Wire(Row r, Ep ep, Mem m, Hx.Wire w, Dictionary<string, string> display, Func<string, Mem?, bool> want) + { + var at = $"{ep.Url} reporting {m.Q}"; + if (ep.LeftOut(m)) + { + if (!want(Aspect.LeftOut, m)) return null; + if (w.Thrown is null) return $"{at}: left out of its document, but answered {(int)w.Status}: {Cut(w.Body)}"; + if (!w.Thrown.StartsWith(nameof(InvalidOperationException) + ":", StringComparison.Ordinal)) return $"{at}: left out, threw {Cut(w.Thrown)}, expected an InvalidOperationException"; + if (!display.TryGetValue(ep.Url, out var name) || name.Length == 0) return $"harness: no endpoint named at {ep.Url}"; + if (!w.Thrown.Contains(name, StringComparison.Ordinal) || !w.Thrown.Contains(m.Q, StringComparison.Ordinal)) + return $"{at}: the refusal does not name the endpoint ({name}) and the member ({m.Q}): {Cut(w.Thrown)}"; + return null; + } + if (!Aspect.Answer.Any(a => want(a, m))) return null; + if (w.Thrown is not null) return $"{at}: request threw {Cut(w.Thrown)}"; + var status = ep.StatusOf(m); + if (want(Aspect.Status, m) && (int)w.Status != status) return $"{at}: status {(int)w.Status}, expected {status}: {Cut(w.Body)}"; + JsonNode? body; try { body = JsonNode.Parse(w.Body); } catch { return $"{at}: body is not JSON: {Cut(w.Body)}"; } + var fault = Un(r, body?["fault"]?.GetValue<string>()); + if (want(Aspect.Fault, m) && fault != m.Name) return $"{at}: fault {fault ?? "(none)"}, expected {m.Name}"; + if (want(Aspect.Status, m) && int.Parse(body?["status"]?.ToString() is { } st ? Un(r, st)!.Trim('"') : "0") != status) return $"{at}: problem status field {body?["status"]}, expected {status}"; + if (want(Aspect.Type, m) && ep.Map(m).Type is { } type && Un(r, body?["type"]?.GetValue<string>()) != type) return $"{at}: type {body?["type"]}, expected {type}"; + var detail = Un(r, body?["detail"]?.GetValue<string>()); + var wantDetail = m.Q == "HFault.Missing" ? "Missing." : null; + if (want(Aspect.Detail, m) && detail != wantDetail) return $"{at}: detail {detail ?? "(none)"}, expected {wantDetail ?? "none (the member has no description)"}"; + return null; + } + + /// <summary>One endpoint's operation in the document, judged on the aspects <paramref name="want"/> names.</summary> + static string? DocOf(Row r, JsonNode doc, Ep ep, Func<string, Mem?, bool> want) + { + var op = doc["paths"]?[ep.Url]?["get"]; + if (op is null && ep.Mvc && !Hx.InDocument(r)) { r.Note(NoteKeys.DocControllerNotInApiExplorer); return null; } + if (op is null) return $"document has no GET {ep.Url}"; + var responses = op["responses"]?.AsObject(); + if (responses is null) return $"{ep.Url}: operation has no responses"; + var nums = responses.Select(kv => kv.Key).Where(k => int.TryParse(k, out _)).Select(int.Parse).ToList(); + if (want(Aspect.DocOrder, null) && !nums.SequenceEqual(nums.Order())) return $"{ep.Url}: responses {string.Join(",", responses.Select(kv => kv.Key))} are not in ascending order"; + var got = nums.Where(n => n >= 400).ToList(); + var listed = ep.DocStatuses(); + // which statuses are listed, as a set: their order is DocOrder's, above + if (want(Aspect.DocListed, null) && !got.Order().SequenceEqual(listed)) return $"{ep.Url}: document lists {string.Join(",", got)}, expected {string.Join(",", listed)} ({string.Join(" ", ep.Decls)})"; + foreach (var status in ep.FaultStatuses()) + { + var resp = responses[status.ToString()]; + var txt = Hx.Deep(doc, resp)?.ToJsonString() ?? ""; + if (want(Aspect.DocDescribes, null) && !txt.Contains("\"fault\"")) return $"{ep.Url} {status}: response schema does not describe fault: {Cut(txt)}"; + if (want(Aspect.DocDescribes, null) && !txt.Contains("\"rule\"")) return $"{ep.Url} {status}: response schema does not describe rule: {Cut(txt)}"; + var desc = resp?["description"]?.GetValue<string>() ?? ""; + const string Lead = "Problem types: "; + var i = desc.IndexOf(Lead, StringComparison.Ordinal); + var types = i < 0 ? [] : desc[(i + Lead.Length)..].Split(", ").ToHashSet(); + var wantTypes = ep.TypesAt(status); + if (want(Aspect.DocTypes, null) && !types.SetEquals(wantTypes)) + return $"{ep.Url} {status}: description \"{desc}\" names problem types {{{string.Join(", ", types)}}}, expected {{{string.Join(", ", wantTypes)}}}"; + } + return null; + } +} diff --git a/tools/probe/corpus/harness/FaultClaimControllers.g.cs b/tools/probe/corpus/harness/FaultClaimControllers.g.cs new file mode 100644 index 00000000..afec71e4 --- /dev/null +++ b/tools/probe/corpus/harness/FaultClaimControllers.g.cs @@ -0,0 +1,206 @@ +// Generated by tools/probe/generators/faults.cs (the fault claims' MVC controllers, C04 C06 C14 C15): do not edit by hand. +// Every action answers FxErr's errors; its route ends in the result type it answers (one, two, dup) or decl (one, with a +// [ProducesResponseType<ProblemDetails>(429)] of its own). The table FxCtl.All restates what each class and action declares, +// in ASP.NET's metadata order (controller, then action), as the input FaultClaim computes the expected response and document from. +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +namespace Probes.Faults; + +internal abstract class FxC04EpBase : ControllerBase +{ + [HttpGet("c/fx/ep/wo/one"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>> FxEpWoOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ep/wo/two"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxEpWoTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/ep/wo/dup"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxEpWoDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/ep/wo/decl"), WithoutFaults<HFault>("Down"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxEpWoDecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ep/keep/one")] public Results<NoContent, FaultProblem<HFault>> FxEpKeepOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ep/keep/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxEpKeepTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/ep/keep/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxEpKeepDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/ep/keep/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxEpKeepDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC04EpApi : FxC04EpBase { } +internal sealed class FxC04EpPlain : FxC04EpBase { } +[ApiController, FaultStatus<HFault>("Down", 507)] internal sealed class FxC04EpSApi : FxC04EpBase { } +[FaultStatus<HFault>("Down", 507)] internal sealed class FxC04EpSPlain : FxC04EpBase { } + +internal abstract class FxC04CtWoBase : ControllerBase +{ + [HttpGet("c/fx/ct/wo/a/one")] public Results<NoContent, FaultProblem<HFault>> FxCtWoAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/a/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxCtWoATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/a/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxCtWoADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/a/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxCtWoADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/b/one")] public Results<NoContent, FaultProblem<HFault>> FxCtWoBOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/b/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxCtWoBTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/b/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxCtWoBDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/ct/wo/b/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxCtWoBDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController, WithoutFaults<HFault>("Down")] internal sealed class FxC04CtWoApi : FxC04CtWoBase { } +[WithoutFaults<HFault>("Down")] internal sealed class FxC04CtWoPlain : FxC04CtWoBase { } + +internal abstract class FxC04CtKeepBase : ControllerBase +{ + [HttpGet("c/fx/ct/keep/one")] public Results<NoContent, FaultProblem<HFault>> FxCtKeepOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/ct/keep/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxCtKeepTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/ct/keep/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxCtKeepDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/ct/keep/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxCtKeepDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC04CtKeepApi : FxC04CtKeepBase { } +internal sealed class FxC04CtKeepPlain : FxC04CtKeepBase { } + +internal abstract class FxC04GrBase : ControllerBase +{ + [HttpGet("c/fx/gr/a/one")] public Results<NoContent, FaultProblem<HFault>> FxGrAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/gr/a/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxGrATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/gr/a/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxGrADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/gr/a/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxGrADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/gr/b/one")] public Results<NoContent, FaultProblem<HFault>> FxGrBOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/fx/gr/b/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> FxGrBTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/fx/gr/b/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> FxGrBDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/fx/gr/b/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> FxGrBDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC04GrApi : FxC04GrBase { } +internal sealed class FxC04GrPlain : FxC04GrBase { } + +internal abstract class FxC06KBase : ControllerBase +{ + [HttpGet("c/k/one")] public Results<NoContent, FaultProblem<HFault>> KOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/k/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> KTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/k/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> KDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/k/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> KDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC06KApi : FxC06KBase { } +internal sealed class FxC06KPlain : FxC06KBase { } + +internal abstract class FxC14PpBase : ControllerBase +{ + [HttpGet("c/d/pp/one"), ProducesResponseType<ProblemDetails>(404, "application/problem+json")] public Results<NoContent, FaultProblem<HFault>> DPpOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/d/pp/two"), ProducesResponseType<ProblemDetails>(404, "application/problem+json")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> DPpTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/d/pp/dup"), ProducesResponseType<ProblemDetails>(404, "application/problem+json")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> DPpDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/d/pp/decl"), ProducesResponseType<ProblemDetails>(404, "application/problem+json"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> DPpDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC14PpApi : FxC14PpBase { } +internal sealed class FxC14PpPlain : FxC14PpBase { } + +internal abstract class FxC14PrtBase : ControllerBase +{ + [HttpGet("c/d/prt/one"), ProducesResponseType<ProblemDetails>(404)] public Results<NoContent, FaultProblem<HFault>> DPrtOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/d/prt/two"), ProducesResponseType<ProblemDetails>(404)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> DPrtTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/d/prt/dup"), ProducesResponseType<ProblemDetails>(404)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> DPrtDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/d/prt/decl"), ProducesResponseType<ProblemDetails>(404), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> DPrtDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC14PrtApi : FxC14PrtBase { } +internal sealed class FxC14PrtPlain : FxC14PrtBase { } + +internal abstract class FxC14NoneBase : ControllerBase +{ + [HttpGet("c/d/none/one")] public Results<NoContent, FaultProblem<HFault>> DNoneOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/d/none/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> DNoneTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/d/none/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> DNoneDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/d/none/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> DNoneDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC14NoneApi : FxC14NoneBase { } +internal sealed class FxC14NonePlain : FxC14NoneBase { } + +internal abstract class FxC15MBase : ControllerBase +{ + [HttpGet("c/s/a/one")] public Results<NoContent, FaultProblem<HFault>> SAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/a/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/a/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/a/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/one"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>> SPOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/two"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SPTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/p/dup"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SPDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/p/decl"), FaultStatus<HFault>("Denied", 452), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SPDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC15MApi : FxC15MBase { } +internal sealed class FxC15MPlain : FxC15MBase { } + +internal abstract class FxC15ASBase : ControllerBase +{ + [HttpGet("c/s/a/one"), FaultStatus<HFault>("Denied", 451)] public Results<NoContent, FaultProblem<HFault>> SAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/a/two"), FaultStatus<HFault>("Denied", 451)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/a/dup"), FaultStatus<HFault>("Denied", 451)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/a/decl"), FaultStatus<HFault>("Denied", 451), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/k/one")] public Results<NoContent, FaultProblem<HFault>> SKOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/k/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SKTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/k/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SKDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/k/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SKDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC15ASApi : FxC15ASBase { } +internal sealed class FxC15ASPlain : FxC15ASBase { } + +internal abstract class FxC15AWBase : ControllerBase +{ + [HttpGet("c/s/a/one"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>> SAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/a/two"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/a/dup"), WithoutFaults<HFault>("Down")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/a/decl"), WithoutFaults<HFault>("Down"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/k/one")] public Results<NoContent, FaultProblem<HFault>> SKOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/k/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SKTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/k/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SKDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/k/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SKDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC15AWApi : FxC15AWBase { } +internal sealed class FxC15AWPlain : FxC15AWBase { } + +internal abstract class FxC15CSBase : ControllerBase +{ + [HttpGet("c/s/a/one")] public Results<NoContent, FaultProblem<HFault>> SAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/a/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/a/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/a/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/one"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>> SPOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/two"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SPTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/p/dup"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SPDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/p/decl"), FaultStatus<HFault>("Denied", 452), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SPDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController, FaultStatus<HFault>("Denied", 451)] internal sealed class FxC15CSApi : FxC15CSBase { } +[FaultStatus<HFault>("Denied", 451)] internal sealed class FxC15CSPlain : FxC15CSBase { } + +internal abstract class FxC15CWBase : ControllerBase +{ + [HttpGet("c/s/a/one")] public Results<NoContent, FaultProblem<HFault>> SAOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/a/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SATwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/a/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SADup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/a/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SADecl(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/one"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>> SPOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/p/two"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SPTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/p/dup"), FaultStatus<HFault>("Denied", 452)] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SPDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/p/decl"), FaultStatus<HFault>("Denied", 452), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SPDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController, WithoutFaults<HFault>("Down")] internal sealed class FxC15CWApi : FxC15CWBase { } +[WithoutFaults<HFault>("Down")] internal sealed class FxC15CWPlain : FxC15CWBase { } + +internal abstract class FxC15KBase : ControllerBase +{ + [HttpGet("c/s/k/one")] public Results<NoContent, FaultProblem<HFault>> SKOne(int e) => FxErr.One(e).ToHttpResult(); + [HttpGet("c/s/k/two")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>> SKTwo(int e) => FxErr.Two(e).ToHttpResult(); + [HttpGet("c/s/k/dup")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> SKDup(int e) => FxErr.Dup(e).ToHttpResult(); + [HttpGet("c/s/k/decl"), ProducesResponseType<ProblemDetails>(429)] public Results<NoContent, FaultProblem<HFault>> SKDecl(int e) => FxErr.One(e).ToHttpResult(); +} +[ApiController] internal sealed class FxC15KApi : FxC15KBase { } +internal sealed class FxC15KPlain : FxC15KBase { } + +public static partial class FxCtl +{ + static partial void Fill(Dictionary<string, Ctl> d) + { + d["C04Ep"] = new(typeof(FxC04EpApi), typeof(FxC04EpPlain), [], [new("fx/ep/wo", [new("HFault", "Down", 0)], []), new("fx/ep/keep", [], [])]); + d["C04EpS"] = new(typeof(FxC04EpSApi), typeof(FxC04EpSPlain), [new("HFault", "Down", 507)], [new("fx/ep/wo", [new("HFault", "Down", 0)], []), new("fx/ep/keep", [], [])]); + d["C04CtWo"] = new(typeof(FxC04CtWoApi), typeof(FxC04CtWoPlain), [new("HFault", "Down", 0)], [new("fx/ct/wo/a", [], []), new("fx/ct/wo/b", [], [])]); + d["C04CtKeep"] = new(typeof(FxC04CtKeepApi), typeof(FxC04CtKeepPlain), [], [new("fx/ct/keep", [], [])]); + d["C04Gr"] = new(typeof(FxC04GrApi), typeof(FxC04GrPlain), [], [new("fx/gr/a", [], []), new("fx/gr/b", [], [])]); + d["C06K"] = new(typeof(FxC06KApi), typeof(FxC06KPlain), [], [new("k", [], [])]); + d["C14Pp"] = new(typeof(FxC14PpApi), typeof(FxC14PpPlain), [], [new("d/pp", [], [404])]); + d["C14Prt"] = new(typeof(FxC14PrtApi), typeof(FxC14PrtPlain), [], [new("d/prt", [], [404])]); + d["C14None"] = new(typeof(FxC14NoneApi), typeof(FxC14NonePlain), [], [new("d/none", [], [])]); + d["C15M"] = new(typeof(FxC15MApi), typeof(FxC15MPlain), [], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]); + d["C15AS"] = new(typeof(FxC15ASApi), typeof(FxC15ASPlain), [], [new("s/a", [new("HFault", "Denied", 451)], []), new("s/k", [], [])]); + d["C15AW"] = new(typeof(FxC15AWApi), typeof(FxC15AWPlain), [], [new("s/a", [new("HFault", "Down", 0)], []), new("s/k", [], [])]); + d["C15CS"] = new(typeof(FxC15CSApi), typeof(FxC15CSPlain), [new("HFault", "Denied", 451)], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]); + d["C15CW"] = new(typeof(FxC15CWApi), typeof(FxC15CWPlain), [new("HFault", "Down", 0)], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]); + d["C15K"] = new(typeof(FxC15KApi), typeof(FxC15KPlain), [], [new("s/k", [], [])]); + } +} diff --git a/tools/probe/corpus/harness/FaultClaimSets.cs b/tools/probe/corpus/harness/FaultClaimSets.cs new file mode 100644 index 00000000..30111c23 --- /dev/null +++ b/tools/probe/corpus/harness/FaultClaimSets.cs @@ -0,0 +1,70 @@ +// Fault types for C07-fault-responses-blanket (AspNetCore README#every-fault-set-at-once), each answered by one endpoint of its own beside the +// claim's: an enum (which no mapping for every set covers), a fault set with a member of no kind, and two fault sets the host's mapping +// refuses (one it throws for, one it gives a status that is no error). +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.AspNetCore; +using CodoMetis.TypeKit.ClosedSets; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +namespace Probes.Faults; + +public enum FxEnum { Lost, Taken } +public sealed partial class FxNoKind : IFaultSet<FxNoKind> +{ + public static readonly FxNoKind Fine = new(FaultKind.NotFound, null); +#pragma warning disable CMTK0014 // a member of no kind, on purpose: the claim is that it stops the start + public static readonly FxNoKind Blank = new(default, null); +#pragma warning restore CMTK0014 + private FxNoKind(FaultKind kind, string? description) { Kind = kind; Description = description; } + public FaultKind Kind { get; } + public string? Description { get; } +} +public sealed partial class FxThrows : IFaultSet<FxThrows> +{ + public static readonly FxThrows Odd = new(FaultKind.Conflict, null); + private FxThrows(FaultKind kind, string? description) { Kind = kind; Description = description; } + public FaultKind Kind { get; } + public string? Description { get; } +} +public sealed partial class FxStatus : IFaultSet<FxStatus> +{ + public static readonly FxStatus Moved = new(FaultKind.NotFound, null); + private FxStatus(FaultKind kind, string? description) { Kind = kind; Description = description; } + public FaultKind Kind { get; } + public string? Description { get; } +} +/// <summary>What the application's mapping throws (set=mapping-throws).</summary> +public sealed class FxMappingRefused() : Exception("the mapping refuses FxThrows"); + +public static class FxSets +{ + public static Result<Fault<FxEnum>> Enum(int e) { if (e == 0) return Result<Fault<FxEnum>>.Success(); return Fault.Of(FxEnum.Lost); } + public static Result<Fault<FxNoKind>> NoKind(int e) { if (e == 0) return Result<Fault<FxNoKind>>.Success(); return Fault.Of(FxNoKind.Fine); } + public static Result<Fault<FxThrows>> Throws(int e) { if (e == 0) return Result<Fault<FxThrows>>.Success(); return Fault.Of(FxThrows.Odd); } + public static Result<Fault<FxStatus>> Status(int e) { if (e == 0) return Result<Fault<FxStatus>>.Success(); return Fault.Of(FxStatus.Moved); } + + /// <summary>The minimal endpoint answering the row's set, at /c7x.</summary> + public static void Map(IEndpointRouteBuilder b, string set) + { + switch (set) + { + case "enum-unmapped": b.MapGet("/c7x", (int e) => Enum(e).ToHttpResult()); break; + case "member-without-kind": b.MapGet("/c7x", (int e) => NoKind(e).ToHttpResult()); break; + case "mapping-throws": b.MapGet("/c7x", (int e) => Throws(e).ToHttpResult()); break; + case "status-not-error": b.MapGet("/c7x", (int e) => Status(e).ToHttpResult()); break; + } + } + /// <summary>The MVC controller answering the row's set.</summary> + public static Type[] Controllers(string set) => set switch + { + "enum-unmapped" => [typeof(FxC07Enum)], "member-without-kind" => [typeof(FxC07NoKind)], + "mapping-throws" => [typeof(FxC07Throws)], "status-not-error" => [typeof(FxC07Status)], _ => [], + }; +} +[ApiController] internal sealed class FxC07Enum : ControllerBase { [HttpGet("c/c7x/enum")] public Results<NoContent, FaultProblem<FxEnum>> Get(int e) => FxSets.Enum(e).ToHttpResult(); } +[ApiController] internal sealed class FxC07NoKind : ControllerBase { [HttpGet("c/c7x/nokind")] public Results<NoContent, FaultProblem<FxNoKind>> Get(int e) => FxSets.NoKind(e).ToHttpResult(); } +[ApiController] internal sealed class FxC07Throws : ControllerBase { [HttpGet("c/c7x/throws")] public Results<NoContent, FaultProblem<FxThrows>> Get(int e) => FxSets.Throws(e).ToHttpResult(); } +[ApiController] internal sealed class FxC07Status : ControllerBase { [HttpGet("c/c7x/status")] public Results<NoContent, FaultProblem<FxStatus>> Get(int e) => FxSets.Status(e).ToHttpResult(); } diff --git a/tools/probe/corpus/harness/HandOrdered.cs b/tools/probe/corpus/harness/HandOrdered.cs new file mode 100644 index 00000000..c1c72cfb --- /dev/null +++ b/tools/probe/corpus/harness/HandOrdered.cs @@ -0,0 +1,28 @@ +// Value objects that declare their own CompareTo(TSelf) (a descending order), for the EF claims about hand-written ordering. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006 +using CodoMetis.TypeKit.ValueObjects; +namespace Probes.Ef; + +public readonly partial record struct HO_int_struct : IValue<int> { public int CompareTo(HO_int_struct other) => other.Value.CompareTo(Value); } +public readonly partial record struct HO_string_struct : IValue<string> { public int CompareTo(HO_string_struct other) => string.CompareOrdinal(other.Value, Value); } +public readonly partial record struct HO_decimal_struct : IValue<decimal> { public int CompareTo(HO_decimal_struct other) => other.Value.CompareTo(Value); } +public readonly partial record struct HO_DateTime_struct : IValue<DateTime> { public int CompareTo(HO_DateTime_struct other) => other.Value.CompareTo(Value); } +public sealed partial record class HO_int_class : IValue<int> { public int CompareTo(HO_int_class? other) => other is null ? 1 : other.Value.CompareTo(Value); } +public sealed partial record class HO_string_class : IValue<string> { public int CompareTo(HO_string_class? other) => other is null ? 1 : string.CompareOrdinal(other.Value, Value); } + +public static class HandOrdered +{ + public sealed record Entry(string Name, Type Vo, Type Plain, object[] Values); + public static readonly Entry[] All = + [ + new("int-struct", typeof(HO_int_struct), typeof(int), [1, 2, 3]), + new("string-struct", typeof(HO_string_struct), typeof(string), ["a", "b", "c"]), + new("decimal-struct", typeof(HO_decimal_struct), typeof(decimal), [1.5m, 2.5m, 3.5m]), + new("DateTime-struct", typeof(HO_DateTime_struct), typeof(DateTime), [new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 1, 2, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 1, 3, 0, 0, 0, DateTimeKind.Utc)]), + new("int-class", typeof(HO_int_class), typeof(int), [1, 2, 3]), + new("string-class", typeof(HO_string_class), typeof(string), ["a", "b", "c"]), + ]; + public static Entry Of(string name) => All.First(e => e.Name == name); + public static object Make(Entry e, object plain) => e.Vo.GetMethod("From", [e.Plain])!.Invoke(null, [plain])!; +} diff --git a/tools/probe/corpus/harness/HttpBaseline.cs b/tools/probe/corpus/harness/HttpBaseline.cs index 6a10f719..731f5d84 100644 --- a/tools/probe/corpus/harness/HttpBaseline.cs +++ b/tools/probe/corpus/harness/HttpBaseline.cs @@ -1,10 +1,14 @@ // The baseline every http claim shares: under the row's options, a payload with the value object behaves like the same payload with the wrapped // type (response bytes, request reading, parameter binding, OpenAPI schema), in the hosting models the row's http.host names. -// Oracle: differential (JSON/wire/schema parity). Documented differences are in JsonParity.ReadMayDiffer. +// Oracle: differential (JSON/wire/schema parity), each comparison a check (Row.Check); a difference a document makes intended ends its check +// as that citation (Cite), and one no document makes intended is compared. using System.Net; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System.Text.Json; using System.Text.Json.Nodes; +using CodoMetis.TypeKit.Facts; namespace Probes.Shapes; public static class HttpBaseline @@ -21,48 +25,194 @@ static bool RefusesItsLimit(Entry e) return new[] { "MaxValue", "MinValue" }.Any(f => t.GetField(f)?.GetValue(null) is { } limit && limit.Equals(refused)); } - static string? Same(Row r, Entry e, string what, Hx.Wire p, Hx.Wire v, string input = "valid") + /// <summary> + /// One wire comparison with the twin, inside its check <paramref name="c"/>: the wrapped type's answer <paramref name="p"/> (side (2)) and the + /// value object's <paramref name="v"/> (side (3)); side (1) refusing alike was decided before the check (<see cref="Wire"/>). A difference a + /// document makes intended ends the check as that document's citation; BaseProbe calibrates the host on C07's rows, whose claim promises + /// nothing of the wire, so these cite the sentence itself, not a not-promised entry of C07. + /// </summary> + static string? Same(Check c, Row r, Entry e, string what, Hx.Wire p, Hx.Wire v, string input = "valid") { r.Log.AppendLine($"{what}: plain {(int)p.Status} {Cut(p.Body)} {p.Thrown} | vo {(int)v.Status} {Cut(v.Body)} {v.Thrown}"); bool ok(Hx.Wire w) => (int)w.Status is >= 200 and < 300; - if (!ok(p) && !ok(v)) return null; - if (r.Is("json.Converters", "own-for-value-object") && ok(v) && ok(p)) { r.Note("own-vo-converter"); return null; } + // both refused, and not as the twin without TypeKit refuses (Sides.Alike): evidence about refusing, and nothing to compare + if (!ok(p) && !ok(v)) return c.Inconclusive($"{what}: both refuse, not alike: wrapped {p.Side(what)}, value object {v.Side(what)}"); + // "Your own converter wins": the application's converter writes the value object instead of the generated one. + if (r.Is("json.Converters", "own-for-value-object") && ok(v)) return c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), what); if (ok(p) && !ok(v)) { - if (JsonParity.ReadMayDiffer(r, e, input, null) || (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class")) { r.Note(what + ":documented-diff"); return null; } - if (r.Is("json.MaxDepth", "3") && (v.Thrown ?? v.Body).Contains("maximum allowed depth")) { r.Note("stj-maxdepth"); return null; } + if (ReadRefusedAsDocumented(c, r, e, input, what)) return null; + // the same: the value object's array reads a JSON array of numbers, never the base64 string the twin's byte[] wrote and reads + if (ByteArray(r, e)) return c.Inapplicable(Cite.Docs("docs/json.md#the-wrapped-types-json"), what); // A closed set travels as its member's name; the wrapped type under the application's converter is no reference. - if (e.Kind == "closed-set" && r.Is("json.Converters", "own-for-wrapped")) { r.Note(what + ":closed-set-vs-own-wrapped-converter"); return null; } + if (e.Kind == "closed-set" && r.Is("json.Converters", "own-for-wrapped")) return c.Inapplicable(Cite.Docs("README.md#what-you-get"), what); // The validated shapes' Create refuses the wrapped type's limit on purpose, so the limit input is a refusal. - if (e.Kind.StartsWith("validated") && input == "at-type-limit" && RefusesItsLimit(e)) { r.Note(what + ":validated-refuses-type-limit"); return null; } + if (e.Kind.StartsWith("validated") && input == "at-type-limit" && RefusesItsLimit(e)) return c.Refused(Cite.Docs("docs/json.md#when-a-value-is-rejected"), what); return (e.Wrapped == "NodaTime.Instant" && what.StartsWith("mvc bind") ? "[F1] " : "") + $"{what}: wrapped answered {(int)p.Status} {Cut(p.Body)}, value object answered {(int)v.Status} {Cut(v.Body)} {v.Thrown}"; } if (!ok(p) && ok(v)) { - // README "What TypeKit guarantees": a JSON null is refused by a struct value object; a class value object - // takes it as a reference does, where the struct it wraps refuses it. - if (input == "null" && !e.Vo.IsValueType && e.Plain.IsValueType) { r.Note(what + ":class-vo-takes-null"); return null; } - if (r.Is("json.MaxDepth", "3") && (p.Thrown ?? p.Body).Contains("maximum allowed depth")) { r.Note("stj-maxdepth:wrapped-side"); return null; } + // docs/json.md "Null and missing": a struct value object rejects a JSON null; a sealed partial record reads it as null, + // like any class, where the struct it wraps refuses it. + if (input == "null" && Make.AdmitsNull(e.Vo) && !Make.AdmitsNull(e.Plain)) return c.Refused(Cite.Docs("docs/json.md#null-and-missing"), what); + return $"{what}: value object answered {(int)v.Status} {Cut(v.Body)} where wrapped answered {(int)p.Status} {Cut(p.Body)} {p.Thrown}"; } - if (!ok(p) && ok(v)) return $"{what}: value object answered {(int)v.Status} {Cut(v.Body)} where wrapped answered {(int)p.Status} {Cut(p.Body)} {p.Thrown}"; if (p.Body != v.Body) { - if (e.Kind == "closed-set") { r.Note(what + ":closed-set-differs"); return null; } - if (e.Wrapped == "byte" && r["shape.position"] == "array-element") { r.Note("byte[]-base64"); return null; } + // A closed set travels as its member's name, never as the string or number standing in for it. + if (e.Kind == "closed-set") return c.Inapplicable(Cite.Docs("README.md#what-you-get"), what); + // docs/json.md "The wrapped type's JSON": an array of value objects over byte is a JSON array of numbers, where the twin, a byte[], + // is one base64 string (KB-038). + if (ByteArray(r, e)) return c.Inapplicable(Cite.Docs("docs/json.md#the-wrapped-types-json"), what); return $"{what}: bodies differ: wrapped {Cut(p.Body)} vs value object {Cut(v.Body)}"; } + c.Note(NoteKeys.Compared, what); return null; } - public static async Task<string?> Run(Row r, Entry e, bool schema = true) + /// <summary> + /// One check of the wire, three-sided (Sides): side (2) <paramref name="p"/> and side (3) <paramref name="v"/> answered on the host with + /// TypeKit; side (1) is asked of the host without it (<paramref name="one"/>), lazily, only where they disagree or refuse. Where (1) + /// refuses alike with (3), the check is the framework's refusal against Baseline.Framework; where (1) answers and the value object's + /// write was refused under a depth limit, side (1) is a converter's value there (KB-040, in process); otherwise <paramref name="body"/> + /// compares, once (2) is held to (1). + /// </summary> + static async Task<string?> Wire(Row r, Entry e, string obligation, Baseline against, string what, Hx.Wire p, Hx.Wire v, Func<Task<Hx.Wire?>> one, Func<Check, Task<string?>> body) + { + var two = p.Side(what); var three = v.Side(what); + Sides.Answer? first = null; Sides.Side? framework = null; + if (two.Refused || three.Refused || p.Body != v.Body) + { + if (await one() is { } w) first = w.Side(what); + if (three.Refused && first is { Refused: true } && Sides.Alike(first, three)) framework = Sides.Measured(first, "the twin on a host without TypeKit"); + else if (three.Refused && v.Ex is { } thrown && first is { Refused: false } && (Sides.Why = null) is null + && Sides.WrittenByAConverter(r, e, Twin(r, e), Sides.WithoutTypeKit(JsonRow.Apply(r, e).Options!), new("write", null, thrown)) is { } converter + && Sides.Alike(converter.Answer, new("write", null, thrown))) + return Sides.Check(r, obligation, against, converter, new("write", null, thrown), null, null, _ => (string?)null); + } + var failed = await Sides.Check(r, obligation, against, framework, three, first, first is null ? null : two, body); + // the failure names side (1), for the triage of the row + return failed is null || first is null ? failed : failed + $" [side (1), the twin without TypeKit: {first}{(Sides.Why is { } why ? "; " + why : "")}]"; + } + + /// <summary>The wrapped twin's payload, as the echo writes it: the row's position and containing type around the wrapped value.</summary> + static Held Twin(Row r, Entry e) => Pos.Build(r["shape.position"], r.Get("shape.containing") ?? "class", e, false, e.Valid()); + + /// <summary>An array of value objects over byte, whose twin is a byte[] (KB-038).</summary> + static bool ByteArray(Row r, Entry e) => e.Wrapped == "byte" && r["shape.position"] == "array-element"; + + /// <summary>What MVC writes for a type's default: null for no content (a reference, or a Nullable), else its JSON under MVC's options.</summary> + static string? DefaultBody(WebApplication app, Type t) => + !t.IsValueType || Nullable.GetUnderlyingType(t) is not null ? null + : JsonSerializer.Serialize(Activator.CreateInstance(t), t, app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Mvc.JsonOptions>>().Value.JsonSerializerOptions); + + /// <summary> + /// Whether an answer is the type's default as MVC writes it: 204 for null, else 200 with its JSON, compared as JSON, since MVC's + /// formatter escapes with its own encoder where the options name none. + /// </summary> + static bool HoldsDefault(Hx.Wire w, string? body) + { + if (body is null) return w.Status == HttpStatusCode.NoContent; + if (w.Status != HttpStatusCode.OK) return false; + try { return JsonNode.DeepEquals(JsonNode.Parse(w.Body), JsonNode.Parse(body)); } + catch (JsonException) { return false; } + } + + /// <summary>A text the wrapped type does not read, for a twin that shows what MVC does with a binding failure; none where any text binds.</summary> + static string? Unreadable(Entry e) => e.Wrapped is "string" or "Uri" or "own-record-class" ? null : "abc"; + + /// <summary> + /// The value object refused a read the wrapped type answered, as a document says it does (the JSON family's documented differences, each + /// cited by its sentence; JsonParity.ReadMayDiffer listed them uncited): side (1) answered and (3) refused, which the citation makes + /// intended (Sides). The check ends as that refusal and true is returned. + /// </summary> + static bool ReadRefusedAsDocumented(Check c, Row r, Entry e, string input, string what) + { + if (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class") { c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), what); return true; } + if (e.Kind.StartsWith("validated") && input == "refused-by-create" && r.Get("json.Converters") != "StoredJsonConverterFactory") { c.Refused(Cite.Docs("docs/json.md#when-a-value-is-rejected"), what); return true; } + if (input == "null" && !Make.AdmitsNull(e.Vo)) { c.Refused(Cite.Docs("docs/json.md#null-and-missing"), what); return true; } + if (e.Kind == "closed-set" && input != "valid") { c.Refused(Cite.Docs("README.md#what-you-get"), what); return true; } + return false; + } + + /// <summary>Where two schemas first differ, and what each side has there: the comparison's 220 characters rarely reach it.</summary> + static string FirstDifference(JsonNode? p, JsonNode? v, string path) => + Difference(p, v, path) is var d ? $"{d.Path}: wrapped {Cut(d.P?.ToJsonString() ?? "(absent)")} | value object {Cut(d.V?.ToJsonString() ?? "(absent)")}" : ""; + + /// <summary> + /// Where two schemas first differ: the path as it is shown, the keys that lead there (a property's name, an element's index), each + /// side's node there (null where it is absent) and the node holding it, so a difference is told by its structure, never by its path's text. + /// </summary> + sealed record Diff(string Path, IReadOnlyList<string> Keys, JsonNode? P, JsonNode? V, JsonNode? ParentP, JsonNode? ParentV); + + static Diff Difference(JsonNode? p, JsonNode? v, string path) => Difference(p, v, path, [], null, null); + + static Diff Difference(JsonNode? p, JsonNode? v, string path, List<string> keys, JsonNode? parentP, JsonNode? parentV) + { + if (p is JsonObject po && v is JsonObject vo) + { + foreach (var k in po.Select(kv => kv.Key).Union(vo.Select(kv => kv.Key)).Order(StringComparer.Ordinal)) + if (!JsonNode.DeepEquals(po[k], vo[k])) return Difference(po[k], vo[k], path + "." + k, [.. keys, k], p, v); + } + else if (p is JsonArray pa && v is JsonArray va && pa.Count == va.Count) + { + for (var i = 0; i < pa.Count; i++) + if (!JsonNode.DeepEquals(pa[i], va[i])) return Difference(pa[i], va[i], $"{path}[{i}]", [.. keys, i.ToString(System.Globalization.CultureInfo.InvariantCulture)], p, v); + } + return new(path, keys, p, v, parentP, parentV); + } + + /// <summary>The node the keys of a <see cref="Diff"/> lead to in another schema, or null where it has none.</summary> + static JsonNode? At(JsonNode? node, IEnumerable<string> keys) + { + foreach (var k in keys) + node = node switch { JsonObject o => o[k], JsonArray a when int.TryParse(k, out var i) && i < a.Count => a[i], _ => null }; + return node; + } + + /// <summary> + /// Side (1) of a row's wire and schema comparisons: the same host started without TypeKit (HostCfg.WithoutTypeKit), once, on the first + /// comparison that asks for it; null where it does not start, which no comparison then excuses anything by. The twin's side (1) is a + /// host with the twin's endpoints alone (<c>plain</c>): ASP.NET alone describing the value object's types beside them names components + /// after them and writes the twin's own schemas otherwise. A host with both (<c>both</c>) answers for the value object's position. + /// </summary> + sealed class SideOne(Row r, Entry e, string only) : IAsyncDisposable + { + Task<WebApplication?>? started; + public string? Down { get; private set; } + public Task<WebApplication?> Host() => started ??= Start(); + async Task<WebApplication?> Start() + { + try { return await Hx.Start(r, e, new HostCfg { FaultControllers = false, MapBlanketMapping = false, WithoutTypeKit = true, PayloadOnly = only }); } + catch (Exception x) when (x is not Sides.InputsNotEquivalent) { Down = x.GetType().Name + ": " + Cut(x.Message); r.Log.AppendLine("side (1) host did not start: " + Down); return null; } + } + public async Task<Hx.Wire?> Ask(Func<WebApplication, Task<Hx.Wire>> ask) => await Host() is { } app ? await ask(app) : null; + public async ValueTask DisposeAsync() { if (started is not null && await started is { } app) await app.DisposeAsync(); } + } + + /// <summary> + /// BaseProbe's calibration of the shared host: every wire comparison a check of its own, named <paramref name="obligation"/> (BaseProbe + /// runs on rows of C07, which promises nothing of the wire, so its checks are the implicit <c>claim</c>). Per side: the echo against the + /// twin; the request read with the row's input.json, against the twin, or a closed set against its own response read back; the binding of + /// the row's http.binding with a valid value against the twin, and with a value Create refuses against the documented 400. Each is + /// three-sided (Sides): side (1), the twin on the same host without TypeKit, is asked where the twin and the value object disagree or + /// refuse. With <paramref name="schema"/>, the schema parity too (C21's check). Every check runs; the first failure is returned. + /// </summary> + public static async Task<string?> Run(Row r, Entry e, bool schema = true, string obligation = "claim") { WebApplication app; try { app = await Hx.Start(r, e, new HostCfg { FaultControllers = false, MapBlanketMapping = false }); } - catch (Exception ex) { return "baseline host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } + catch (Exception ex) when (ex is not Sides.InputsNotEquivalent) { return "baseline host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } await using var _ = app; + await using var one = new SideOne(r, e, "plain"); + await using var alone = new SideOne(r, e, "both"); var jopts = JsonRow.Apply(r, e).Options!; var position = r["shape.position"]; var plainSample = e.Valid(); + var input = r.Get("input.json") ?? "valid"; + var ownVo = r.Is("json.Converters", "own-for-value-object"); + var bind = r["http.binding"]; + string? failed = null; var sides = new List<(string name, Func<string, string> path, string echo, string inn, string bindPre)>(); if (Hx.HasMinimal(r)) sides.Add(("minimal", p => Hx.M(r, p), "/echo/", "/in/", "/bind/")); if (Hx.HasMvc(r)) sides.Add(("mvc", p => Hx.C(r, p), "/mvc/echo/", "/mvc/in/", "/mvc/bind/")); @@ -70,109 +220,357 @@ static bool RefusesItsLimit(Entry e) { // 1. response side var ep = await Hx.Get(app, path(echo + "plain")); var ev = await Hx.Get(app, path(echo + "vo")); - if (Same(r, e, name + " echo", ep, ev) is { } f1) return f1; - // 2. request side, with the row's input.json - if (e.Wrapped == "byte" && position == "array-element") r.Note("in:byte[]-is-base64-inherent"); - else if ((int)ep.Status == 200) + var f1 = await Wire(r, e, obligation, Baseline.Twin, name + " echo", ep, ev, () => one.Ask(h => Hx.Get(h, path(echo + "plain"))), + c => Task.FromResult(Same(c, r, e, name + " echo", ep, ev))); + failed ??= f1; + // 2. request side, with the row's input.json, where the wrapped type's echo gave a body to change + if ((int)ep.Status == 200) { - var input = (r.Get("input.json") ?? "valid"); - var text = JsonParity.InputFor(input, e, jopts, ep.Body, position); - var ownText = r.Is("json.Converters", "own-for-value-object") || e.Kind == "closed-set"; // a closed set travels as its name: it reads its own spelling, the string is only a stand-in - var textVo = text; - if (ownText) { r.Note("own-converter:value-object-reads-its-own-spelling"); text = ep.Body; textVo = ev.Body; } - var ip = await Hx.Send(app, HttpMethod.Post, path(inn + "plain"), text); var iv = await Hx.Send(app, HttpMethod.Post, path(inn + "vo"), textVo); - if (ownText) { if ((int)ev.Status == 200 && (int)iv.Status != 200 && (int)ip.Status == 200) return $"{name} in: the value object cannot read its own response ({(int)iv.Status}): {Cut(iv.Body)}"; r.Note("in:own-spelling"); } + string? f2; + if (ownVo) + // "Your own converter wins": the application's converter reads the value object, so the twin's text is none of its; + // only that it reads its own response back is held, and the check earns nothing. + f2 = await r.Check(obligation, Baseline.RoundTrip, async c => + { + var ip = await Hx.Send(app, HttpMethod.Post, path(inn + "plain"), ep.Body); var iv = await Hx.Send(app, HttpMethod.Post, path(inn + "vo"), ev.Body); + if ((int)ev.Status == 200 && (int)iv.Status != 200 && (int)ip.Status == 200) return $"{name} in: the value object cannot read its own response ({(int)iv.Status}): {Cut(iv.Body)}"; + return c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), name + " in"); + }); + else if (e.Kind == "closed-set") + // A closed set travels as its member's name (README "What you get"): the string standing in for it is no twin, so the + // value object reads its own response back. + f2 = await r.Check(obligation, Baseline.RoundTrip, async c => + { + if ((int)ev.Status != 200) return c.Inconclusive($"{name} in: the value object's echo answered {(int)ev.Status}, no response of its own to read back"); + var ip = await Hx.Send(app, HttpMethod.Post, path(inn + "plain"), ep.Body); var iv = await Hx.Send(app, HttpMethod.Post, path(inn + "vo"), ev.Body); + r.Log.AppendLine($"{name} in(own response): plain {(int)ip.Status} {Cut(ip.Body)} {ip.Thrown} | vo {(int)iv.Status} {Cut(iv.Body)} {iv.Thrown}"); + // The wrapped type does not read its own response back under the row's options either: nothing to tell. + if ((int)iv.Status != 200 && (int)ip.Status != 200) return c.Inconclusive($"{name} in: neither side reads its own response back ({(int)ip.Status}, {(int)iv.Status})"); + if ((int)iv.Status != 200) return $"{name} in: the closed set cannot read its own response ({(int)iv.Status}), the wrapped type reads its own: {Cut(iv.Body)} {iv.Thrown}"; + c.Note(NoteKeys.Compared, name + " in: its own response read back"); + return null; + }); else - if (Same(r, e, name + " in(" + input + ")", ip, iv, input) is { } f2) return f2; + { + var text = JsonParity.InputFor(input, e, jopts, ep.Body, position); + var ip = await Hx.Send(app, HttpMethod.Post, path(inn + "plain"), text); var iv = await Hx.Send(app, HttpMethod.Post, path(inn + "vo"), text); + var what = name + " in(" + input + ")"; + f2 = await Wire(r, e, obligation, Baseline.Twin, what, ip, iv, () => one.Ask(h => Hx.Send(h, HttpMethod.Post, path(inn + "plain"), text)), + c => Task.FromResult(Same(c, r, e, what, ip, iv, input))); + } + failed ??= f2; } // 3. binding, the row's http.binding - var bind = r["http.binding"]; - if (e.Wrapped == "own-record-class" && bind != "body") { r.Note("bind:record-class-not-bindable-from-text"); bind = "none"; } - if (e.Wrapped == "NodaTime.Instant" && name == "minimal" && bind != "body") { r.Note("bind:minimal-cannot-bind-Instant"); bind = "none"; } + var unbindable = e.Wrapped == "own-record-class" && bind != "body" ? "a record class" + : e.Wrapped == "NodaTime.Instant" && name == "minimal" && bind != "body" ? "Instant in a minimal API" : null; + if (unbindable is not null) + { + // No sentence cites it: the wrapped type is not bound from text either, so neither side has an endpoint and there is no twin. + r.Check(obligation, Baseline.Twin, c => c.Inconclusive($"{name} bind {bind}: {unbindable} is not bound from text, the wrapped type neither")); + continue; + } + if (bind == "body" && ownVo) + { + // "Your own converter wins": the application's converter reads the body, not the twin's JSON. + r.Check(obligation, Baseline.Twin, c => c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), $"{name} bind body")); + continue; + } var valid = Hx.BindText(e, plainSample); var refused = e.Refused() is { } rf ? Hx.BindText(e, rf) : null; - foreach (var (which, txt) in new[] { ("valid", valid), ("refused", refused) }) + async Task<Hx.Wire> B(WebApplication host, string which, string txt, string w) { - if (txt is null || bind == "none") continue; - if (bind == "route" && (txt.Contains('/') || txt.Contains(':') || txt.Contains('?') || txt.Contains('#'))) { r.Note("route-text-unsafe"); continue; } - async Task<Hx.Wire> B(string w) + var enc = Uri.EscapeDataString(txt); + return bind switch { - var enc = Uri.EscapeDataString(txt); - return bind switch - { - "route" => await Hx.Get(app, path(bindPre + "route/" + w + "/" + enc)), - "query" => await Hx.Get(app, path(bindPre + "query/" + w + "?v=" + enc)), - "as-parameters" => await Hx.Get(app, path(bindPre + "params/" + w + "?v=" + enc)), - "header" => await Hx.Send(app, HttpMethod.Get, path(bindPre + "header/" + w), header: ("v", txt)), - "form" => await Hx.Send(app, HttpMethod.Post, path(bindPre + "form/" + w), "v=" + enc, "application/x-www-form-urlencoded"), - _ => await Hx.Send(app, HttpMethod.Post, path(bindPre + "body/" + w), - which == "valid" ? JsonSerializer.Serialize(plainSample, e.Plain, jopts) : JsonSerializer.Serialize(e.Refused(), e.Plain, jopts)), - }; + "route" => await Hx.Get(host, path(bindPre + "route/" + w + "/" + enc)), + "query" => await Hx.Get(host, path(bindPre + "query/" + w + "?v=" + enc)), + "as-parameters" => await Hx.Get(host, path(bindPre + "params/" + w + "?v=" + enc)), + "header" => await Hx.Send(host, HttpMethod.Get, path(bindPre + "header/" + w), header: ("v", txt)), + "form" => await Hx.Send(host, HttpMethod.Post, path(bindPre + "form/" + w), "v=" + enc, "application/x-www-form-urlencoded"), + _ => await Hx.Send(host, HttpMethod.Post, path(bindPre + "body/" + w), + which == "valid" ? JsonSerializer.Serialize(plainSample, e.Plain, jopts) : which == "refused" ? JsonSerializer.Serialize(e.Refused(), e.Plain, jopts) : JsonSerializer.Serialize(txt)), + }; + } + // Both sides are mapped for every binding they reach here: a 404 is the harness's routing, never a binding result. Counted as + // "plain unbindable", a plain route mapped under another name went unnoticed. + static string? Routed(string name, string bind, Hx.Wire bp, Hx.Wire bv) => + bp.Status == HttpStatusCode.NotFound || bv.Status == HttpStatusCode.NotFound + ? $"harness: {name} bind {bind} has no endpoint (plain {(int)bp.Status}, value object {(int)bv.Status})" : null; + // A text a route segment cannot carry as it is ('/', ':', '?', '#'): escaped, it arrives as other text ('%2F' stays), so the value + // bound is not the one sent. A limit of the harness, which no sentence cites: the check cannot tell. + bool Unsafe(string txt) => bind == "route" && (txt.Contains('/') || txt.Contains(':') || txt.Contains('?') || txt.Contains('#')); + string? Unplaceable(Check c, string txt) => c.Inconclusive($"{name} bind route: the text {txt} cannot be carried by a route segment as it is"); + string? f3; + if (Unsafe(valid)) f3 = r.Check(obligation, Baseline.Twin, c => Unplaceable(c, valid)); + else + { + var bp = await B(app, "valid", valid, "plain"); var bv = await B(app, "valid", valid, "vo"); + r.Log.AppendLine($"{name} bind {bind} valid text={valid}: plain {(int)bp.Status} {Cut(bp.Body)} {bp.Thrown} | vo {(int)bv.Status} {Cut(bv.Body)} {bv.Thrown}"); + f3 = Routed(name, bind, bp, bv) is { } unrouted + ? r.Check(obligation, Baseline.Twin, _ => unrouted) + : await Wire(r, e, obligation, Baseline.Twin, name + " bind " + bind, bp, bv, () => one.Ask(h => B(h, "valid", valid, "plain")), + c => Task.FromResult(Same(c, r, e, name + " bind " + bind, bp, bv))); + } + failed ??= f3; + if (refused is null) continue; + // README "Binding" (AspNetCore): a value Create rejects gets a 400, the one set of rules. + var against = Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#binding"); + if (Unsafe(refused)) { failed ??= r.Check(obligation, against, c => Unplaceable(c, refused)); continue; } + var rp = await B(app, "refused", refused, "plain"); var rv = await B(app, "refused", refused, "vo"); + r.Log.AppendLine($"{name} bind {bind} refused text={refused}: plain {(int)rp.Status} {Cut(rp.Body)} {rp.Thrown} | vo {(int)rv.Status} {Cut(rv.Body)} {rv.Thrown}"); + if (Routed(name, bind, rp, rv) is { } unrouted2) { failed ??= r.Check(obligation, against, _ => unrouted2); continue; } + // Side (1) of a refused binding: the twin on the host without TypeKit, refused a binding too, as MVC refuses a text the wrapped + // type cannot read. Where the value object's refusal could not be answered (a 500, which MVC's 400 under a source-generated + // context that does not list ValidationProblemDetails is, KB-041), the twin's refused binding is side (1): read from KB-041's + // fixture where it covers the row, else asked of the host without TypeKit. + Sides.Side? framework = null; + if (rv.Ex is { } refusal && Unreadable(e) is { } unreadable) + { + var settings = Sides.Settings(r, ("endpoint", name == "mvc" ? "mvc-api-controller" : "minimal"), ("binding", "fails")); + if (Sides.Covered(r, "KB-041", settings) is { Kind: FactAnswer.Exception } answer) + framework = new(new(name + " bind " + bind + " refused", null, Status: 500, FixtureRefusal: (string?)answer.Value["type"]), Baseline.Fact("KB-041"), $"KB-041 {answer.Id}: a binding MVC refuses, under a source-generated context"); + else if (await one.Ask(h => B(h, "unreadable", unreadable, "plain")) is { } twin) + framework = Sides.Measured(twin.Side(name + " bind " + bind + " refused"), $"the twin on a host without TypeKit, sent {unreadable}"); + } + var f4 = await Sides.Check(r, obligation, against, framework, rv.Side(name + " bind " + bind + " refused"), null, null, async c => + { + if ((int)rp.Status is not (>= 200 and < 300)) return c.Inconclusive($"{name} bind {bind}: the wrapped type refuses the value Create refuses too ({(int)rp.Status})"); + if (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class") return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "bind"); + // README "What TypeKit guarantees": StoredJsonConverterFactory reads JSON the application stored itself, without validation. + if (bind == "body" && r.Is("json.Converters", "StoredJsonConverterFactory")) return c.Inapplicable(Cite.Docs("README.md#what-typekit-guarantees"), "bind body"); + // KB-037: under MaxValidationDepth=1 MVC runs an action whose parameter it could not bind, with the parameter at its default. The + // value object must hold its default then, never the refused value; side (1), the twin sent a text it cannot read, shows the + // framework doing the same: read from KB-037's fixture where it covers the row, else asked of the host without TypeKit. + if (name == "mvc" && r.Is("mvc.MaxValidationDepth", "1") && (int)rv.Status is >= 200 and < 300) + { + if (DefaultBody(app, e.Vo) is var voDefault && !HoldsDefault(rv, voDefault)) + return $"{name} bind {bind}: validated value object bound the refused value (status {(int)rv.Status}) {Cut(rv.Body)}, not its default ({(voDefault is null ? "204" : "200 " + Cut(voDefault))})"; + if (Unreadable(e) is not { } text) + return c.Inapplicable(Cite.Fact("KB-037"), $"{name} bind {bind}: the value object holds its default; any text binds the wrapped type, so no twin shows it"); + Hx.Wire? bt; + if (Sides.Covered(r, "KB-037", Sides.Settings(r, ("text", text))) is { Kind: FactAnswer.Status } answer) + bt = new((HttpStatusCode)(int)answer.Value["status"]!, (string?)answer.Value["body"] ?? ""); + else bt = await one.Ask(h => B(h, "unreadable", text, "plain")); + if (bt is null) return c.Inconclusive($"{name} bind {bind}: the value object holds its default (KB-037); no host without TypeKit to show the wrapped type's ({one.Down})"); + r.Log.AppendLine($"{name} bind {bind} unreadable text={text}: plain {(int)bt.Status} {Cut(bt.Body)} {bt.Thrown}"); + if (HoldsDefault(bt, DefaultBody(app, e.Plain))) + return c.Inapplicable(Cite.Fact("KB-037"), $"{name} bind {bind}: both hold their default, the wrapped type for a text it cannot read"); + if ((int)bt.Status is >= 400 and < 500) + return $"{name} bind {bind}: the value object answered {(int)rv.Status} holding its default, where the wrapped type rejects a text it cannot read ({(int)bt.Status})"; + // KB-037's class from the query: the validator throws at the twin's property, and the value object, written by a + // converter of its own, is no class it walks + return c.Inconclusive($"{name} bind {bind}: the value object holds its default (KB-037); the wrapped type answered {(int)bt.Status} {Cut(bt.Body)} {bt.Thrown}"); + } + // a refused value must not bind (the one set of rules), except where the application skips validation on purpose + if ((int)rv.Status is >= 200 and < 300) return $"{name} bind {bind}: validated value object bound the refused value (status {(int)rv.Status}) {Cut(rv.Body)}"; + if ((int)rv.Status != 400) return $"{name} bind {bind}: refused value answered {(int)rv.Status}, expected 400: {Cut(rv.Body)} {rv.Thrown}"; + c.Note(NoteKeys.Compared, $"{name} bind {bind}: the refused value answered 400"); + return null; + }); + failed ??= f4; + } + // 4. schema parity + if (schema) { var f5 = await r.Check(obligation, Baseline.Twin, async c => await SchemaParity(app, one, alone, r, e, c)); failed ??= f5; } + return failed; + } + + /// <summary> + /// The schema parity alone, on the same host as <see cref="Run"/>: claim C21's check (C21OpenapiSchemaParityProbe), which BaseProbe leaves + /// out of its calibration, run once for each of the row's <paramref name="obligations"/> against the twin. C21 promises the schema ASP.NET + /// Core publishes for the underlying type, so side (1), the same host without TypeKit, is started on every row (Sides.Unconditional). + /// Every check runs; the first failure is returned. + /// </summary> + public static async Task<string?> Schema(Row r, Entry e, IEnumerable<string> obligations) + { + WebApplication? app = null; string? down = null; + try { app = await Hx.Start(r, e, new HostCfg { FaultControllers = false, MapBlanketMapping = false }); } + catch (Exception ex) when (ex is not Sides.InputsNotEquivalent) { down = "schema host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } + await using var _ = app; + await using var one = new SideOne(r, e, "plain"); + await using var alone = new SideOne(r, e, "both"); + var list = obligations.ToList(); + if (down is null && list.Any(o => Sides.Unconditional(r, o))) await one.Host(); + string? failed = null; + foreach (var obligation in list) + { + var f = await r.Check(obligation, Baseline.Twin, c => down is not null ? Task.FromResult<string?>(down) : SchemaParity(app!, one, alone, r, e, c)); + failed ??= f; + } + return failed; + } + + /// <summary> + /// Where either side is a null-or-reference (<c>oneOf [null, component]</c>), both sides there in one spelling: + /// the null as <c>x-null</c> beside the schema, wherever the side spelled it (<c>type [null, X]</c>, <c>nullable</c>, + /// <c>x-nullable</c>, a null in an enum, or the null-or itself). A shared component cannot hold the null of one + /// position, so a nullable value object that is one is null around its reference, where ASP.NET writes the + /// wrapped type's null inside its inline schema (decision 36: "as a container's element (`oneOf: [null, + /// component]`, ASP.NET's own form)"; AspNetCore README: "A nullable element (`List<Quantity?>`, + /// `Dictionary<string, Quantity?>`) is the component or null, as a nullable property is."). Whether a side + /// admits null is kept; everywhere else, inlined schemas included, the comparison stays byte for byte (rows 170, + /// 196 and 215 of C21). Returns both sides without the marks <see cref="Hx.Deep"/> put on resolved references. + /// </summary> + static (string, string, bool) NullAroundReferences(string sp, string sv) + { + var around = false; + static bool IsNull(JsonNode? a) => a is JsonObject ao && (ao["type"]?.ToJsonString() == "\"null\"" || ao["enum"]?.ToJsonString() == "[null]"); + static bool NullOrReference(JsonNode? n) => + n is JsonObject o && o["oneOf"] is JsonArray alts && alts.Count == 2 && alts.Any(IsNull) && alts.Any(a => a is JsonObject ao && ao.ContainsKey(Hx.ResolvedReference)); + static JsonNode? Unmark(JsonNode? n) => n switch + { + JsonObject o => new JsonObject(o.Where(kv => kv.Key != Hx.ResolvedReference).Select(kv => KeyValuePair.Create(kv.Key, Unmark(kv.Value)))), + JsonArray a => new JsonArray(a.Select(Unmark).ToArray()), + _ => n?.DeepClone(), + }; + static JsonNode? OneSpelling(JsonNode? n) + { + switch (n) + { + case JsonObject o when o["oneOf"] is JsonArray alts && alts.Count == 2 && alts.Any(IsNull) && alts.FirstOrDefault(a => !IsNull(a)) is JsonObject keep: + { + var c = (JsonObject)OneSpelling(keep)!; c["x-null"] = true; return Sorted(c); } - if (bind == "body" && r.Is("json.Converters", "own-for-value-object")) { r.Note("own-vo:body-binding-skipped"); continue; } - var bp = await B("plain"); var bv = await B("vo"); - // Both sides are mapped for every binding they reach here: a 404 is the harness's routing, never a binding - // result. Counted as "plain unbindable", a plain route mapped under another name went unnoticed. - if (bp.Status == HttpStatusCode.NotFound || bv.Status == HttpStatusCode.NotFound) - return $"harness: {name} bind {bind} has no endpoint (plain {(int)bp.Status}, value object {(int)bv.Status})"; - var plainOk = (int)bp.Status is >= 200 and < 300; - r.Log.AppendLine($"{name} bind {bind} {which} text={txt}: plain {(int)bp.Status} {Cut(bp.Body)} {bp.Thrown} | vo {(int)bv.Status} {Cut(bv.Body)} {bv.Thrown}"); - if (which == "valid") + case JsonObject o: { - if (!plainOk) { r.Note(name + " bind:plain-unbindable"); continue; } - if (Same(r, e, name + " bind " + bind, bp, bv) is { } f3) return f3; + var c = new JsonObject(); + foreach (var (k, v) in o) + { + if (k == Hx.ResolvedReference) continue; + if (k is "nullable" or "x-nullable") { if (v?.GetValue<bool>() == true) c["x-null"] = true; continue; } + if (k == "allOf" && v is JsonArray aa && aa.Count == 1 && aa[0] is JsonObject a0 && a0.Count == 1 && a0.ContainsKey("x-nullable")) { c["x-null"] = true; continue; } + if (k == "type" && v is JsonArray ta && ta.Any(x => x?.GetValue<string>() == "null")) + { + var f = ta.Where(x => x?.GetValue<string>() != "null").ToList(); + c["type"] = f.Count == 1 ? f[0]!.DeepClone() : new JsonArray(f.Select(x => x!.DeepClone()).ToArray()); c["x-null"] = true; continue; + } + if (k == "type" && v?.ToJsonString() == "\"null\"") { c["x-null"] = true; continue; } + if (k == "enum" && v is JsonArray en && en.Any(x => x is null)) { c["enum"] = new JsonArray(en.Where(x => x is not null).Select(x => x!.DeepClone()).ToArray()); c["x-null"] = true; continue; } + if (k == "anyOf" && v is JsonArray any && any.Any(IsNull)) { c["anyOf"] = new JsonArray(any.Where(x => !IsNull(x)).Select(OneSpelling).ToArray()); c["x-null"] = true; continue; } + c[k] = OneSpelling(v); + } + return Sorted(c); } - else + case JsonArray a: return new JsonArray(a.Select(OneSpelling).ToArray()); + default: return n?.DeepClone(); + } + } + static JsonObject Sorted(JsonObject o) => new(o.OrderBy(kv => kv.Key, StringComparer.Ordinal).Select(kv => KeyValuePair.Create(kv.Key, kv.Value?.DeepClone()))); + (JsonNode?, JsonNode?) Walk(JsonNode? p, JsonNode? v) + { + if (NullOrReference(p) || NullOrReference(v)) { around = true; return (OneSpelling(p), OneSpelling(v)); } + if (p is JsonObject po && v is JsonObject vo) + { + var cp = new JsonObject(); var cv = new JsonObject(); + foreach (var k in po.Select(kv => kv.Key).Union(vo.Select(kv => kv.Key)).Where(k => k != Hx.ResolvedReference).Order(StringComparer.Ordinal)) { - if (!plainOk) continue; - if (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class") { r.Note("bind:preserve-documented"); continue; } - if (name == "mvc" && r.Is("mvc.MaxValidationDepth", "1")) { r.Note("mvc-maxvalidationdepth1-ignores-binding-errors"); continue; } // MVC itself: any type's binding error is ignored (D2) - if (bind == "body" && r.Is("json.Converters", "StoredJsonConverterFactory")) { r.Note("stored-factory-skips-validation"); continue; } // intended - // a refused value must not bind (the one set of rules), except where the application skips validation on purpose - if ((int)bv.Status is >= 200 and < 300) return $"{name} bind {bind}: validated value object bound the refused value (status {(int)bv.Status}) {Cut(bv.Body)}"; - // MVC writes its 400 as ValidationProblemDetails, which a source-generated context alone does not list: - // ASP.NET's 500 for any refused binding under that configuration, the wrapped type's included. - if ((int)bv.Status == 500 && name == "mvc" && r.Is("json.TypeInfoResolver", "source-generated-context") - && (bv.Thrown ?? bv.Body ?? "").Contains("ValidationProblemDetails")) { r.Note("mvc-400-unwritable-under-source-gen"); continue; } - if ((int)bv.Status != 400) return $"{name} bind {bind}: refused value answered {(int)bv.Status}, expected 400: {Cut(bv.Body)} {bv.Thrown}"; + if (!po.ContainsKey(k)) { cv[k] = Unmark(vo[k]); continue; } + if (!vo.ContainsKey(k)) { cp[k] = Unmark(po[k]); continue; } + var (a, b) = Walk(po[k], vo[k]); cp[k] = a; cv[k] = b; } + return (cp, cv); } + if (p is JsonArray pa && v is JsonArray va && pa.Count == va.Count) + { + var cp = new JsonArray(); var cv = new JsonArray(); + for (var i = 0; i < pa.Count; i++) { var (a, b) = Walk(pa[i], va[i]); cp.Add(a); cv.Add(b); } + return (cp, cv); + } + return (Unmark(p), Unmark(v)); } - // 4. schema parity - if (schema) { if (await SchemaParity(app, r, e) is { } f4) return f4; } + var (np, nv) = Walk(JsonNode.Parse(sp), JsonNode.Parse(sv)); + return (np?.ToJsonString() ?? "null", nv?.ToJsonString() ?? "null", around); + } + + /// <summary> + /// The component two operations of one document both refer to at the same place (a parameter's or the body's schema), where one + /// substitutes the value object for the twin: two types under one name, told by the references' structure. + /// </summary> + static string? SharedComponent(JsonNode twin, JsonNode valueObject) + { + static IEnumerable<(string, string)> References(JsonNode op) + { + if (op["parameters"] is JsonArray ps) + for (var i = 0; i < ps.Count; i++) + if (ps[i]?["schema"]?["$ref"]?.GetValue<string>() is { } p) yield return ($"parameter {i}", p); + if (op["requestBody"]?["content"] is JsonObject content) + foreach (var (media, body) in content) + if (body?["schema"]?["$ref"]?.GetValue<string>() is { } b) yield return ($"body {media}", b); + } + var twins = References(twin).ToDictionary(x => x.Item1, x => x.Item2); + return References(valueObject).FirstOrDefault(x => twins.TryGetValue(x.Item1, out var t) && t == x.Item2).Item2; + } + + /// <summary>A schema that is the OpenAPI 2.0 null flag and nothing else: <c>{"allOf":[{"x-nullable":true}]}</c>.</summary> + static bool OnlyTheNullFlag(JsonObject o) => + o.Count == 1 && o["allOf"] is JsonArray { Count: 1 } all && all[0] is JsonObject { Count: 1 } flag && flag["x-nullable"]?.GetValueKind() == JsonValueKind.True; + + /// <summary> + /// The OpenAPI 2.0 null flag with the wrapped type's <c>format</c> beside it and nothing else, <c>{"format":"uri","allOf":[{"x-nullable":true}]}</c>: + /// a nullable component's property, which TypeKit gives the wrapped type's format (KB-026). + /// </summary> + static bool TheNullFlagBeside(JsonObject o) => + o.ContainsKey("format") && o.Count == 2 && OnlyTheNullFlag(new JsonObject { ["allOf"] = o["allOf"]?.DeepClone() }); + + /// <summary>The keys of a difference up to the property it lies in (<c>…, properties, P</c>), or null where it lies in none.</summary> + static IReadOnlyList<string>? PropertyAt(IReadOnlyList<string> keys) + { + for (var i = keys.Count - 2; i >= 0; i--) + if (keys[i] == "properties") return [.. keys.Take(i + 2)]; return null; } - static async Task<string?> SchemaParity(WebApplication app, Row r, Entry e) + /// <summary> + /// OpenAPI 2.0's nullable property of a component (KB-026): <c>allOf: [{x-nullable: true}]</c>, without the reference and so without a + /// type, beside what TypeKit kept of the wrapped type (a <c>format</c>). + /// </summary> + static bool TheNullFlagWithoutItsReference(JsonObject o) => + o["allOf"] is JsonArray { Count: 1 } all && all[0] is JsonObject { Count: 1 } flag && flag["x-nullable"]?.GetValueKind() == JsonValueKind.True + && !o.ContainsKey("$ref") && !o.ContainsKey(Hx.ResolvedReference) && !o.ContainsKey("type"); + + /// <summary> + /// A body whose schema is its format and its null alone, <c>{"schema": {"format": "int32", "allOf": [{"x-nullable": true}]}}</c>, or its null + /// alone where the type has no format, anywhere in an operation: told by its structure (KB-001). + /// </summary> + static bool OnlyItsNull(JsonNode? n) => n switch + { + JsonObject o => o["schema"] is JsonObject schema && schema.All(kv => kv.Key is "allOf" or "format") + && schema["allOf"] is JsonArray { Count: 1 } all && all[0] is JsonObject { Count: 1 } flag + && flag["x-nullable"]?.GetValueKind() == JsonValueKind.True + || o.Any(kv => OnlyItsNull(kv.Value)), + JsonArray a => a.Any(OnlyItsNull), + _ => false, + }; + + /// <summary> + /// The value object's schema (3) against the wrapped twin's (2) at each endpoint pair, three-sided (Sides): the twin's schema is held to + /// the one ASP.NET publishes on the same host without TypeKit (1), where that host writes a document; and where the value object's schema + /// differs from the twin's, side (1)'s document at the same path tells whether ASP.NET alone writes that difference. + /// </summary> + static async Task<string?> SchemaParity(WebApplication app, SideOne one, SideOne alone, Row r, Entry e, Check c) { JsonNode doc; try { doc = (await Hx.Doc(app))!; } catch (Exception ex) { - // isolate: does the document fail for the wrapped type alone? - foreach (var only in new[] { "plain", "vo" }) - { - try - { - var cfg = new HostCfg { FaultControllers = false, MapBlanketMapping = false, PayloadOnly = only }; - if (Hx.HasMvc(r)) cfg.Payload = true; - await using var a2 = await Hx.Start(r, e, cfg); - await Hx.Doc(a2); - } - catch (Exception ex2) - { - if (only == "plain") { r.Note("doc-refused-for-wrapped-too"); return null; } - return $"document fails with the value object ({ex2.GetType().Name}: {Cut(ex2.Message)}) but not with the wrapped type"; - } - } - return $"document failed ({ex.GetType().Name}: {Cut(ex.Message)}) with both, but not alone"; + // The baseline is ASP.NET alone on the same host: a document it refuses too has no schema to compare (decision 99). + return await Hx.DocRefusedByAspNetAlone(r, e, new HostCfg { FaultControllers = false, MapBlanketMapping = false }, ex); } - if (r.Is("json.Converters", "own-for-wrapped")) { r.Note("schema:own-wrapped-converter-not-described"); return null; } // docs/json.md "Describing what a converter writes": ASP.NET drops items/additionalProperties of a converter it cannot see into, the value object's schema is the underlying type's - if (r.Is("json.Converters", "own-for-value-object")) { r.Note("schema:own-vo-converter-not-described"); return null; } // docs/json.md "Describing what a converter writes": the exporter loses track of a converter it cannot see into - string? Op(string path, string method, out JsonNode? node) + // docs/json.md "Describing what a converter writes": ASP.NET drops items/additionalProperties of a converter it cannot see into, the value + // object's schema is the underlying type's; and the application's converter for the value object describes it as that converter says. + // C21 declares both on every obligation (claims.json inapplicable), so Row.Check records them before this body runs and no row of + // C21 is generated with them; these lines hold for the schema half run under another claim (ShrinkProbe's baseline). + if (r.Is("json.Converters", "own-for-wrapped")) return c.Inapplicable(Cite.NotPromised("own-converter-for-the-wrapped-type")); + if (r.Is("json.Converters", "own-for-value-object")) return c.Inapplicable(Cite.NotPromised("own-converter-for-the-value-object")); + // side (1): the document ASP.NET writes for the twin's endpoints without TypeKit, where it writes one; and, asked only where the value + // object's schema differs from the twin's, the one it writes for both sides' endpoints, for the value object's position + async Task<JsonNode?> Document(SideOne host) { - node = doc["paths"]?[path]?[method]; return node is null ? "missing " + method + " " + path : null; + if (await host.Host() is not { } h) return null; + try { return await Hx.Doc(h); } + catch (Exception x) { r.Log.AppendLine("side (1) document refused: " + x.GetType().Name + ": " + Cut(x.Message)); return null; } } + var doc1 = await Document(one); + JsonNode? Op(JsonNode d, string path, string method) => d["paths"]?[path]?[method]; // only the schemas are compared (the claim is about the schema); whether a parameter or body is required follows the .NET type's nullability JsonNode? Schemas(JsonNode? n) { @@ -192,12 +590,29 @@ static bool RefusesItsLimit(Entry e) default: return n?.DeepClone(); } } - string Sig(JsonNode op) + string Sig(JsonNode d, JsonNode op) { var o = new JsonObject(); - o["parameters"] = Schemas(Hx.Deep(doc, op["parameters"])); - o["requestBody"] = Schemas(Hx.Deep(doc, op["requestBody"])); - o["ok"] = Schemas(Hx.Deep(doc, op["responses"]?["200"])); + o["parameters"] = Schemas(Hx.Deep(d, op["parameters"], mark: true)); + o["requestBody"] = Schemas(Hx.Deep(d, op["requestBody"], mark: true)); + o["ok"] = Schemas(Hx.Deep(d, op["responses"]?["200"], mark: true)); + return o.ToJsonString(); + } + // The media types of an operation's request body and 200 response, each named by where it is + static HashSet<string> MediaTypes(JsonNode op) => + [ + .. (op["requestBody"]?["content"] as JsonObject ?? []).Select(kv => "requestBody:" + kv.Key), + .. (op["responses"]?["200"]?["content"] as JsonObject ?? []).Select(kv => "ok:" + kv.Key), + ]; + // A signature with only the request body's and the 200 response's media types given + static string UnderMediaTypes(string sig, IEnumerable<string> media) + { + var keep = media.ToHashSet(); + var o = JsonNode.Parse(sig)!.AsObject(); + foreach (var (part, name) in new[] { ("requestBody", "requestBody"), ("ok", "ok") }) + if (o[part]?["content"] is JsonObject content) + foreach (var key in content.Select(kv => kv.Key).ToList()) + if (!keep.Contains(name + ":" + key)) content.Remove(key); return o.ToJsonString(); } var checks = new List<(string, string)>(); @@ -211,7 +626,6 @@ string Sig(JsonNode op) if (Hx.HasMinimal(r)) { var bind = r["http.binding"] switch { "route" => "route", "query" => "query", "header" => "header", "form" => "form", "as-parameters" => "params", _ => "body" }; - var sfx = bind == "route" ? "/{v}" : ""; // route: /bind/route/{which}/{v} checks.Add((bind == "route" ? Hx.M(r, "/bind/route/plain/{v}") : Hx.M(r, $"/bind/{bind}/plain"), bind == "route" ? Hx.M(r, "/bind/route/vo/{v}") : Hx.M(r, $"/bind/{bind}/vo"))); } @@ -229,41 +643,156 @@ string Inputs(JsonNode op) foreach (var (pp0, vp) in checks) { var method = pp0.Contains("/schema/") || pp0.Contains("/body/") || pp0.Contains("/form/") ? "post" : "get"; - // Like with like from the start: a class value object against the wrapped type at its nullability. - var pp = e.Vo.IsValueType != e.Plain.IsValueType && Hx.PlainNull(r, e) is not null && pp0.Contains("/bind/") ? pp0.Replace("/plain", "/plainnull") : pp0; - if (pp != pp0) r.Note("schema:compared-with-nullable-wrapped"); - if (Op(pp, method, out var po) is { } m1) + // Like with like from the start: the twin is the wrapped type at the value object's nullability (shapes.cs), so a class + // value object over a struct faces the struct made nullable, at every endpoint (row 384 of C07: a record class over TimeOnly, + // optional, against TimeOnly?). + var pp = pp0; + var po = Op(doc, pp, method); var vo = Op(doc, vp, method); + if (po is null) { // Mapped for bodies only (a record class, Instant in a minimal API): neither side is there. - if (Op(vp, method, out _) is null) return $"harness: the value object's {method} {vp} is documented, its plain twin is not ({m1})"; - r.Note("schema:" + m1); continue; + if (vo is not null) return $"harness: the value object's {method} {vp} is documented, its plain twin is not (missing {method} {pp})"; + c.Note(NoteKeys.SchemaNeitherSideDocumented, $"missing {method} {pp}"); continue; } - if (Op(vp, method, out var vo) is { } m2) return m2; - if (Inputs(po!) is var ip && Inputs(vo!) is var iv && ip != iv) + if (vo is null) return $"missing {method} {vp}"; + // The twin's and the value object's shapes are two .NET types; one component name for both (an id the row's CreateSchemaReferenceId + // makes alike for both, such as an array of a generic type) holds one schema for the two, so the document cannot tell them apart. + if (SharedComponent(po, vo) is { } shared) + { c.Inconclusive($"{pp} vs {vp}: the twin's and the value object's shapes are one component, {shared}, in the document: nothing to compare"); continue; } + // (2) held to (1): the twin's schema is the one ASP.NET publishes on the same host without TypeKit, but where the twin itself + // holds one of TypeKit's types (a Patch), which no host without TypeKit describes + var po1 = doc1 is null ? null : Op(doc1, pp, method); + var twinHoldsTypeKit = pp.Contains("/schema/") && Sides.HoldsTypeKit(Twin(r, e).Declared); + if (po1 is not null && !twinHoldsTypeKit && Sig(doc1!, po1) is var s1 && Sig(doc, po) is var s2 && s1 != s2) + return $"schema of the wrapped type at {pp}: TypeKit changed the wrapped type's schema, at {FirstDifference(JsonNode.Parse(s1), JsonNode.Parse(s2), "$")} (without TypeKit, then with it)"; + if (Inputs(po) is var ip && Inputs(vo) is var iv && ip != iv) { - if (e.Vo.IsValueType != e.Plain.IsValueType && Hx.PlainNull(r, e) is null) - r.Note("schema:nullability-not-comparable"); // no nullable wrapped endpoint here (an enum or Instant under a source-generated context) + // No twin of the wrapped type at the value object's nullability here (a struct value object over a class, which no plain + // type mirrors), or a wrapped record class, which ASP.NET binds by its properties where the value object is one value: the + // two sides are not alike, and no sentence cites it, so the check cannot tell. + if (Make.AdmitsNull(e.Vo) != Make.AdmitsNull(e.Plain)) + c.Inconclusive($"{pp} vs {vp}: no twin of {e.Plain.Name} at the value object's nullability, the inputs differ: wrapped [{ip}] vs value object [{iv}]"); + else if (e.Wrapped == "own-record-class") + c.Inconclusive($"{pp} vs {vp}: the wrapped record class is bound by its properties, the value object as one value, the inputs differ: wrapped [{ip}] vs value object [{iv}]"); else r.Outcome("harness-error", $"inputs not equivalent for {pp} vs {vp}: wrapped [{ip}] vs value object [{iv}]"); continue; } - var sp = Sig(po!); var sv = Sig(vo!); - if (sp != sv && (r["shape.position"] == "nullable-property" || pp != pp0)) + var sp = Sig(doc, po); var sv = Sig(doc, vo); + // MVC lists a body's and a response's media types by what its formatters can read and write of the .NET type, and the twin + // and the value object are two types (row 38 of C21: XmlSerializerInputFormatter reads a record class value object, not the + // Uri it wraps). Where the lists differ and side (1), ASP.NET alone with both sides' endpoints, lists each side's the same, + // the formatters decided it, not TypeKit: the schemas are compared under the media types both list. Otherwise as they are. + if (MediaTypes(po) is var mp && MediaTypes(vo) is var mv && !mp.SetEquals(mv) + && await Document(alone) is { } both0 && Op(both0, pp, method) is { } pAlone && Op(both0, vp, method) is { } vAlone + && MediaTypes(pAlone).SetEquals(mp) && MediaTypes(vAlone).SetEquals(mv)) + { + r.Log.AppendLine($"{pp} vs {vp}: media types the formatters list differ alike without TypeKit, compared under [{string.Join(", ", mp.Intersect(mv))}]"); + sp = UnderMediaTypes(sp, mp.Intersect(mv)); sv = UnderMediaTypes(sv, mp.Intersect(mv)); + } + if (NullAroundReferences(sp, sv) is var (np, nv, around)) { sp = np; sv = nv; if (around) c.Note(NoteKeys.SchemaNullAroundReferenceNormalised); } + if (sp != sv && Make.AdmitsNull(e.Vo) && Make.AdmitsNull(e.Plain) && e.Vo.IsValueType != e.Plain.IsValueType) { - // Both sides nullable alike; OpenAPI 3.x spells nullability two ways (oneOf [null, X] or type [null, X]). + // Both sides nullable alike, one a class and the other a struct made nullable (a class value object and its nullable twin, or + // a nullable struct value object and a wrapped class); OpenAPI 3.x spells nullability two ways (oneOf [null, X] or type [null, X]). sp = Hx.NoNull(JsonNode.Parse(sp))!.ToJsonString(); sv = Hx.NoNull(JsonNode.Parse(sv))!.ToJsonString(); - r.Note("schema:nullable-spellings-normalised"); + c.Note(NoteKeys.SchemaNullableSpellingsNormalised); } - if (sp != sv && e.Vo.IsValueType != e.Plain.IsValueType && pp == pp0) + if (sp != sv && Make.AdmitsNull(e.Vo) != Make.AdmitsNull(e.Plain)) { - // A class value object inside a declared shape is nullable by being a class (ADR 0036: a nullable - // value object admits null in OpenAPI); the markers go, the rest of the schema must agree. + // A struct value object over a class, whose twin is nullable by being a class where the value object is not (no plain + // type mirrors it); the markers go, the rest of the schema must agree. sp = Hx.NoNull(JsonNode.Parse(sp))!.ToJsonString(); sv = Hx.NoNull(JsonNode.Parse(sv))!.ToJsonString(); - r.Note("schema:class-vo-nullability-normalised"); + c.Note(NoteKeys.SchemaClassVoNullabilityNormalised); } if (sp != sv) { - if (e.Kind == "closed-set") { r.Note("schema:closed-set-differs"); continue; } - return $"schema differs for {pp} vs {vp}:\n wrapped {Cut(sp)}\n value object {Cut(sv)}"; + if (e.Kind == "closed-set") { c.Inapplicable(Cite.NotPromised("a-closed-set-is-a-string-enum"), "schema"); continue; } + // A form binds the wrapped record class by its properties and the value object otherwise, as the parameters above: the two + // sides' fields are not alike, and no sentence cites it, so the check cannot tell (row 62 of C21). + if (e.Wrapped == "own-record-class" && pp.Contains("/form/") && method == "post") + { c.Inconclusive($"{pp} vs {vp}: the wrapped record class is bound from a form by its properties, the value object otherwise: the fields differ"); continue; } + var d = Difference(JsonNode.Parse(sp), JsonNode.Parse(sv), "$"); + // Decision 36: a nullable value object admits null wherever it appears, where ASP.NET may write the twin's nullable type without + // it (the items of a list of double? under AllowNamedFloatingPointLiterals, row 216 of C21; the twin is held to side (1) above). + // Only that null differs: the value object admits it where the twin does not, and the schemas are otherwise the same. + if (Make.AdmitsNull(e.Vo) && d.Keys is [.., "x-null"] && d.P is null && d.V?.GetValueKind() == JsonValueKind.True + && JsonNode.DeepEquals(Hx.NoNull(JsonNode.Parse(sp)), Hx.NoNull(JsonNode.Parse(sv)))) + { c.Inapplicable(Cite.NotPromised("a-nullable-value-object-admits-null"), d.Path); continue; } + var openApi2 = r.Is("openapi.OpenApiVersion", "OpenApi2_0"); + // Side (1) at the same path: the twin's and the value object's operations in the document ASP.NET writes without TypeKit. + var twin1 = po1 is null ? null : JsonNode.Parse(Sig(doc1!, po1)); + var both1 = openApi2 && d.P is null && d.V is not null ? await Document(alone) : null; + var vo1 = both1 is null ? null : Op(both1, vp, method) is { } o1 ? JsonNode.Parse(Sig(both1, o1)) : null; + var vo3 = JsonNode.Parse(Sig(doc, vo)); + // KB-026: OpenAPI 2.0 writes a nullable property of any component as allOf [{x-nullable: true}], without the reference, and a + // value object keeps a component of its own (not-promised openapi-2-nullable-component-loses-its-type; row 38 of C21). Side (1) + // shows it: without TypeKit, ASP.NET writes the value object's position at the same path the same, where the twin has nothing. + if (openApi2 && d.P is null && d.V is not null && At(vo1, d.Keys) is { } same1 && JsonNode.DeepEquals(same1, At(vo3, d.Keys))) + { c.Inapplicable(Cite.NotPromised("openapi-2-nullable-component-loses-its-type"), d.Path); continue; } + // The same, where the spellings of the null were normalised above, which takes the null flag out of the value object's + // schema and leaves it without what the 2.0 writer dropped with the reference: its type, its pattern (rows 386, 178, 181 + // and 243 of C21: a nullable struct value object over Uri as a required-init property, and class value objects whose + // property admits null as a type parameter's or set through the constructor). Told by its structure: the value object's + // property is the null flag, alone or beside the wrapped type's format, and the twin's, which admits null, has that format. + if (openApi2 && PropertyAt(d.Keys) is { } property + && At(vo3, property) is JsonObject flagged3 && (TheNullFlagBeside(flagged3) || OnlyTheNullFlag(flagged3)) + && At(JsonNode.Parse(Sig(doc, po)), property) is JsonObject typed2 + && (typed2["x-nullable"]?.GetValueKind() == JsonValueKind.True + // or the twin is a component itself (a wrapped record class, row 444 of C21), whose reference ASP.NET's own rule + // leaves without the null the value object admits (decision 36: a-nullable-value-object-admits-null) + || typed2.ContainsKey(Hx.ResolvedReference) && Make.AdmitsNull(e.Vo)) + && JsonNode.DeepEquals(typed2["format"], flagged3["format"])) + { c.Inapplicable(Cite.NotPromised("openapi-2-nullable-component-loses-its-type"), d.Path); continue; } + // AspNetCore README "What the document says": a nullable element is the component or null, as a nullable property is, which + // OpenAPI 2.0 writes as allOf [{x-nullable: true}] without the reference (not-promised openapi-2-nullable-element-loses-its-type; + // row 358 of C21, a dictionary's value). ASP.NET alone drops a converter-backed element, so side (1) has nothing there; told by + // its structure, the value object's element is the null flag alone where the twin's admits null. + if (openApi2 && d.Keys is [.., "items" or "additionalProperties", "allOf"] && d.P is null + && At(vo3, d.Keys.SkipLast(1)) is JsonObject element3 && OnlyTheNullFlag(element3) + && At(JsonNode.Parse(Sig(doc, po)), d.Keys.SkipLast(1)) is JsonObject element2 && element2["x-nullable"]?.GetValueKind() == JsonValueKind.True) + { c.Inapplicable(Cite.NotPromised("openapi-2-nullable-element-loses-its-type"), d.Path); continue; } + // The same loss told by the row's axes, where side (1) cannot show it: a nullable value object that is a component is a + // nullable property wherever the serializer's contract says so (a get-only or a type parameter's property among them), where + // ASP.NET alone adds the null only for a settable annotated one. Its property is then the null flag without the reference, + // where the twin's keeps its type beside x-nullable. + if (openApi2 && Make.AdmitsNull(e.Vo) && !r.Is("openapi.CreateSchemaReferenceId", "null-inlines-all") && d.V is null + && At(vo3, d.Keys.SkipLast(1)) is JsonObject lost && TheNullFlagWithoutItsReference(lost) + && At(JsonNode.Parse(Sig(doc, po)), d.Keys.SkipLast(1)) is JsonObject kept && kept["x-nullable"]?.GetValueKind() == JsonValueKind.True) + { c.Inapplicable(Cite.NotPromised("openapi-2-nullable-component-loses-its-type"), d.Path); continue; } + // KB-001: OpenAPI 2.0 writes a nullable body of the wrapped value type as its format and allOf [{x-nullable: true}] alone (the + // null alone where it has no format, an enum), where a class value object's body in a minimal API is not nullable and keeps its + // schema (not-promised openapi-2-nullable-body-keeps-only-its-null; row 257, and rows 113 and 282 over TimeOnly and char). Side (1) + // shows it: without TypeKit, the twin's schema there is that format and that null alone. + // AspNetCore README "What the document says" (KB-026), seen from the twin: OpenAPI 2.0 writes a nullable property of any component, + // the twin's nullable enum among them, as allOf [{x-nullable: true}] without the reference, where the value object keeps its type. + // Side (1) shows it: without TypeKit, the twin's property there is that null alone. + if (openApi2 && d.P is null && d.V is not null && Make.AdmitsNull(e.Plain) && At(twin1, d.Keys.SkipLast(1)) is JsonObject t1 && OnlyTheNullFlag(t1)) + { c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), d.Path); continue; } + if (openApi2 && !e.Vo.IsValueType && Nullable.GetUnderlyingType(e.Plain) is not null && OnlyItsNull(twin1) && OnlyItsNull(JsonNode.Parse(Sig(doc, po)))) + { c.Inapplicable(Cite.NotPromised("openapi-2-nullable-body-keeps-only-its-null"), pp); continue; } + // KB-027: the component ASP.NET makes for an enum written by name is the schema of the first use it meets, so a wrapped E? + // parameter lists null through E's component where E was first met nullable; the value object's parameter is the enum's + // content without it wherever the enum is a component (not-promised enum-component-is-its-first-use; row 285 of C21). Side (1) + // shows it: without TypeKit, the twin's parameter lists the same null, through the same resolved component. + // The same component met first as a nullable use lists that null for a property too, the twin's non-nullable one included + // (not-promised enum-component-lists-the-null-of-its-first-use; row 196 of C21, a polymorphic derived type's property). + if (d.Keys is [.., "enum"] && d.P is JsonArray we && d.V is JsonArray ve && we.Count == ve.Count + 1 && we[^1] is null + && JsonNode.DeepEquals(new JsonArray(we.Take(ve.Count).Select(x => x?.DeepClone()).ToArray()), ve) + && At(JsonNode.Parse(Sig(doc, po)), d.Keys.SkipLast(1)) is JsonObject held && held.ContainsKey(Hx.ResolvedReference) + && At(twin1, d.Keys) is { } listed1 && JsonNode.DeepEquals(listed1, d.P)) + { + if (d.Keys is ["parameters", ..]) c.Inapplicable(Cite.NotPromised("enum-component-is-its-first-use"), vp); + else c.Inapplicable(Cite.NotPromised("enum-component-lists-the-null-of-its-first-use"), vp); + continue; + } + // AspNetCore README "A described type": ASP.NET cannot see through a converter it did not write, one a library registers for + // its own type among them, and drops a list's items and a dictionary's additionalProperties of that type, where the value + // object's element is put back (not-promised a-librarys-converter-for-the-wrapped-type; row 422 of C21, a list of Instant?). + // Side (1) shows it: without TypeKit, the twin's container has no element schema either. + if (e.Wrapped == "NodaTime.Instant" && d.Keys is [.., "items" or "additionalProperties"] && d.P is null && d.V is not null + && twin1 is not null && At(twin1, d.Keys.SkipLast(1)) is JsonObject container1 && !container1.ContainsKey(d.Keys[^1])) + { c.Inapplicable(Cite.NotPromised("a-librarys-converter-for-the-wrapped-type"), d.Path); continue; } + return $"schema differs for {pp} vs {vp} at {FirstDifference(JsonNode.Parse(sp), JsonNode.Parse(sv), "$")}:\n wrapped {Cut(sp)}\n value object {Cut(sv)}"; } } return null; diff --git a/tools/probe/corpus/harness/HttpHost.cs b/tools/probe/corpus/harness/HttpHost.cs index 61ed728b..d37238fc 100644 --- a/tools/probe/corpus/harness/HttpHost.cs +++ b/tools/probe/corpus/harness/HttpHost.cs @@ -82,8 +82,8 @@ public void Apply(ControllerModel c) { if (!c.ControllerType.IsGenericType || c.ControllerType.GetGenericTypeDefinition() != typeof(MvcBind<>)) return; var arg = c.ControllerType.GenericTypeArguments[0]; - // By the entry's own types: under http.nullability=nullable the plain side is already T?, and is "plain". - var which = arg == vo ? "vo" : arg == plain ? "plain" : "plainnull"; + // By the entry's own types: the plain side is the twin, at the value object's nullability. + var which = arg == vo ? "vo" : arg == plain ? "plain" : throw new InvalidOperationException($"MvcBind<{arg.Name}> is neither side's"); foreach (var a in c.Actions) { var (kind, post) = a.ActionName switch { "RouteA" => ("route", false), "QueryA" => ("query", false), "HeaderA" => ("header", false), "BodyA" => ("body", true), "FormA" => ("form", true), _ => ("params", false) }; @@ -128,8 +128,15 @@ public sealed class HostCfg public Type[]? Controllers; // null: the fault controllers of the host public bool FaultControllers = true; public bool Payload = true; // payload echo / in / binding / schema endpoints - public string PayloadOnly = "both"; // "both", "vo" or "plain" (to isolate a document refusal) + public string PayloadOnly = "both"; // "both", "vo" or "plain": the endpoints of one side only, minimal and MVC (side (1)'s twin alone, a document refusal isolated) public bool MapBlanketMapping = true; + public bool TypeKitOpenApi = true; // false: the document as ASP.NET alone writes it, the baseline of a refused one + /// <summary> + /// The three-sided differential's side (1) (Sides): no AddTypeKit(), none of TypeKit's JSON converters on either set of options, and no + /// fault responses; the wrapped twin's endpoints answer as ASP.NET and System.Text.Json have them without TypeKit. + /// </summary> + public bool WithoutTypeKit; + public HostCfg Copy() => (HostCfg)MemberwiseClone(); } public static class Hx @@ -191,15 +198,14 @@ public static async Task<WebApplication> Start(Row r, Entry e, HostCfg c) builder.Logging.ClearProviders(); if (c.Logs is not null) builder.Logging.AddProvider(c.Logs); var plain = e.Valid(); var vo = Make.Vo(e, plain); - var payload = new Payload( - Pos.Build(r["shape.position"], (r.Get("shape.containing") ?? "class"), e, true, vo), - Pos.Build(r["shape.position"], (r.Get("shape.containing") ?? "class"), e, false, plain)); + var (twin, held) = Sides.Twins(r["shape.position"], r.Get("shape.containing") ?? "class", e, plain, vo); + var payload = new Payload(held, twin); builder.Services.AddSingleton(payload); - builder.Services.ConfigureHttpJsonOptions(o => JsonRow.Apply(r, e, o.SerializerOptions)); - builder.Services.AddOpenApi(x => { ApplyOpenApi(x, r); x.AddTypeKit(); }); - builder.Services.Configure<FaultResponseOptions>(x => x.QualifiedFaultNames = c.Qualified); + builder.Services.ConfigureHttpJsonOptions(o => { JsonRow.Apply(r, e, o.SerializerOptions); if (c.WithoutTypeKit) Sides.DropTypeKit(o.SerializerOptions); }); + builder.Services.AddOpenApi(x => { ApplyOpenApi(x, r); if (c.TypeKitOpenApi && !c.WithoutTypeKit) x.AddTypeKit(); }); + if (!c.WithoutTypeKit) builder.Services.Configure<FaultResponseOptions>(x => x.QualifiedFaultNames = c.Qualified); c.Services?.Invoke(builder.Services); - if (c.MapBlanketMapping) + if (c.MapBlanketMapping && !c.WithoutTypeKit) { builder.Services.AddFaultResponses<SFault>(f => f switch { SFault.First => 409, SFault.Second => 410, _ => throw new ArgumentOutOfRangeException(nameof(f)) }); if (c.Mapping == "by-kind") builder.Services.AddFaultResponses(); @@ -214,9 +220,8 @@ public static async Task<WebApplication> Start(Row r, Entry e, HostCfg c) if (c.Payload) { controllers.Add(typeof(MvcPayloadController)); - controllers.Add(typeof(MvcBind<>).MakeGenericType(e.Vo)); - controllers.Add(typeof(MvcBind<>).MakeGenericType(e.Plain)); - if (Hx.PlainNull(r, e) is { } pn) controllers.Add(typeof(MvcBind<>).MakeGenericType(pn)); + if (c.PayloadOnly != "plain") controllers.Add(typeof(MvcBind<>).MakeGenericType(e.Vo)); + if (c.PayloadOnly != "vo") controllers.Add(typeof(MvcBind<>).MakeGenericType(e.Plain)); } var pfx = ""; builder.Services.AddControllers(o => @@ -225,7 +230,7 @@ public static async Task<WebApplication> Start(Row r, Entry e, HostCfg c) if (r["http.host"] == "controller-without-ApiController-visible") o.Conventions.Add(new VisibleToApiExplorer()); ApplyMvc(o, r); }) - .AddJsonOptions(o => JsonRow.Apply(r, e, o.JsonSerializerOptions)) + .AddJsonOptions(o => { JsonRow.Apply(r, e, o.JsonSerializerOptions); if (c.WithoutTypeKit) Sides.DropTypeKit(o.JsonSerializerOptions); }) .ConfigureApplicationPartManager(p => { p.ApplicationParts.Clear(); p.FeatureProviders.Add(new OnlyControllers([.. controllers])); }); } var app = builder.Build(); @@ -261,24 +266,8 @@ static void MapPayload(IEndpointRouteBuilder b, Payload p, Row r, Entry e, strin .Invoke(null, [b, which, e.Wrapped is "own-record-class" or "NodaTime.Instant"]); typeof(Hx).GetMethod(nameof(MapSchema), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(h.Declared).Invoke(null, [b, which]); } - if (only is "both" or "plain" && PlainNull(r, e) is { } pn) - typeof(Hx).GetMethod(nameof(MapBind), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(pn) - .Invoke(null, [b, "plainnull", e.Wrapped is "own-record-class" or "NodaTime.Instant"]); } - /// <summary> - /// The wrapped type at the value object's nullability: a class value object's `T v` parameter is - /// maybe-null, so ASP.NET makes it optional and nullable, and it must be compared with - /// `Nullable<wrapped>`, not with the required wrapped value type. (G1, dismissed 2026-10-15: under - /// OpenAPI 2.0 ASP.NET writes any nullable body without `pattern`, `int?` included.) - /// </summary> - /// The probe's source-generated context lists `Nullable<wrapped>` for every wrapped value type but the - /// enums and Instant; for those the comparison falls back to stripping nullability markers (noted on the row). - public static Type? PlainNull(Row r, Entry e) => - !e.Vo.IsValueType && e.Plain.IsValueType && Nullable.GetUnderlyingType(e.Plain) is null - && !(r.Is("json.TypeInfoResolver", "source-generated-context") && (e.Plain.IsEnum || e.Wrapped == "NodaTime.Instant")) - ? typeof(Nullable<>).MakeGenericType(e.Plain) : null; - sealed record AsP<T>([FromQuery(Name = "v")] T V); static void MapBind<T>(IEndpointRouteBuilder b, string which, bool bodyOnly) { @@ -295,7 +284,12 @@ static void MapSchema<T>(IEndpointRouteBuilder b, string which) => b.MapPost($"/schema/{which}", ([FromBody] T body) => Results.Ok(body)); // ---- client side - public sealed record Wire(HttpStatusCode Status, string Body, string? Thrown = null); + /// <summary>An answer on the wire: its status and body, or the exception the server threw (<see cref="Ex"/>, a 500), named in <see cref="Thrown"/>.</summary> + public sealed record Wire(HttpStatusCode Status, string Body, string? Thrown = null, Exception? Ex = null) + { + /// <summary>The answer as the three-sided differential compares it (Sides): the status, the body, and the exception's type and stage.</summary> + public Sides.Answer Side(string operation) => new(operation, Ex is null ? Body : null, Ex, (int)Status); + } public static async Task<Wire> Send(WebApplication app, HttpMethod m, string path, string? body = null, string? contentType = null, (string, string)? header = null) { var req = new HttpRequestMessage(m, path); @@ -307,11 +301,55 @@ public static async Task<Wire> Send(WebApplication app, HttpMethod m, string pat var resp = await app.GetTestClient().SendAsync(req); return new(resp.StatusCode, await resp.Content.ReadAsStringAsync()); } - catch (Exception ex) { return new(HttpStatusCode.InternalServerError, "", ex.GetType().Name + ": " + ex.Message); } + catch (Exception ex) { return new(HttpStatusCode.InternalServerError, "", ex.GetType().Name + ": " + ex.Message, ex); } } public static Task<Wire> Get(WebApplication app, string path) => Send(app, HttpMethod.Get, path); public static async Task<JsonNode?> Doc(WebApplication app) => JsonNode.Parse(await app.GetTestClient().GetStringAsync("/openapi/v1.json")); + /// <summary> + /// The document failed with AddTypeKit(). The same host without it is side (1) of the three (Sides): where ASP.NET alone refuses the + /// document alike, by exception type and stage and never by message (<see cref="DocAlike"/>), the document part has nothing to check + /// (<see cref="DocRefused"/>); where it writes the document, or refuses it otherwise, the failure is returned (F2 hid behind a baseline + /// that kept AddTypeKit() on both sides). <paramref name="comparedBefore"/> says whether the check compared an answer before the document. + /// </summary> + public static async Task<string?> DocRefusedByAspNetAlone(Row r, Entry e, HostCfg c, Exception withTypeKit, bool comparedBefore = false) + { + static string Say(Exception x) => x.GetType().Name + ": " + (x.Message.Length > 200 ? x.Message[..200] + "..." : x.Message); + var alone = c.Copy(); alone.TypeKitOpenApi = false; + try + { + await using var app = await Start(r, e, alone); + await Doc(app); + } + catch (Exception ex) when (DocAlike(ex, withTypeKit)) + { + DocRefused(r, ex, comparedBefore); + return null; + } + catch (Exception ex) { return $"document: AddTypeKit() fails ({Say(withTypeKit)}), ASP.NET alone fails otherwise ({Say(ex)})"; } + return $"document: AddTypeKit() fails ({Say(withTypeKit)}), ASP.NET alone writes it"; + } + + /// <summary> + /// ASP.NET alone (side (1)) and AddTypeKit() (side (3)) refuse the document alike: the same exception type at the same stage (a + /// JsonException's Path), as <see cref="Sides.Alike"/> compares two refusals. No message is compared: the two documents differ in length + /// by what AddTypeKit() wrote, so one refusal is at another line and byte of each (rows 31 and 140 of C21), and a refusal's words decide + /// no row (decision 99, WS2's three-sided differential). + /// </summary> + public static bool DocAlike(Exception one, Exception three) => + Sides.Alike(new Sides.Answer("document", null, one), new Sides.Answer("document", null, three)); + + /// <summary> + /// The document refused alike by ASP.NET alone: its part has nothing to check. A check that compared nothing before it is the framework's + /// refusal, an expected refusal that earns nothing (framework-refuses); one that compared an answer on the wire first is judged by that + /// answer (doc:refused-by-aspnet-alone, a pass: decision 99's "judged by its other checks"). + /// </summary> + public static void DocRefused(Row r, Exception one, bool comparedBefore) + { + if (comparedBefore) r.Note(NoteKeys.DocRefusedByAspnetAlone, one.GetType().Name); + else r.Note(NoteKeys.FrameworkRefuses, $"document refused by ASP.NET alone too ({one.GetType().Name}), side (1) measured on the same host without AddTypeKit()"); + } + public static string BindText(Entry e, object plain) => e.Wrapped switch { "DateTime" => ((DateTime)plain).ToString("O"), "DateTimeOffset" => ((DateTimeOffset)plain).ToString("O"), @@ -333,9 +371,12 @@ public static async Task<Wire> Send(WebApplication app, HttpMethod m, string pat var c = new JsonObject(); foreach (var kv in o) { - if (kv.Key is "nullable" or "x-nullable" or "required") continue; + if (kv.Key is "nullable" or "x-nullable" or "x-null" or "required") continue; if (kv.Key == "type" && kv.Value is JsonArray ta) { var f = ta.Where(x => x?.GetValue<string>() != "null").ToList(); c["type"] = f.Count == 1 ? f[0]!.DeepClone() : new JsonArray(f.Select(x => x!.DeepClone()).ToArray()); continue; } if (kv.Key == "allOf" && kv.Value is JsonArray aa && aa.Count == 1 && aa[0] is JsonObject a0 && a0.Count == 1 && a0.ContainsKey("x-nullable")) continue; + // OpenAPI 3.0 writes a type of several with null as anyOf [{type: integer}, {type: string}, {enum: [null]}] (row 260 of C21) + if (kv.Key == "anyOf" && kv.Value is JsonArray any && any.Any(IsNull)) { c["anyOf"] = new JsonArray(any.Where(x => !IsNull(x)).Select(NoNull).ToArray()); continue; } + if (kv.Key == "enum" && kv.Value is JsonArray en && en.Any(x => x is null)) { c["enum"] = new JsonArray(en.Where(x => x is not null).Select(x => x!.DeepClone()).ToArray()); continue; } c[kv.Key] = NoNull(kv.Value); } return c; @@ -344,7 +385,10 @@ public static async Task<Wire> Send(WebApplication app, HttpMethod m, string pat } } - public static JsonNode? Deep(JsonNode doc, JsonNode? n, HashSet<string>? seen = null) + /// <summary>The key <see cref="Deep"/> puts on a schema it resolved from a reference, when asked to mark them.</summary> + public const string ResolvedReference = "x-probe-resolved-reference"; + + public static JsonNode? Deep(JsonNode doc, JsonNode? n, HashSet<string>? seen = null, bool mark = false) { seen ??= []; switch (n) @@ -355,14 +399,40 @@ public static async Task<Wire> Send(WebApplication app, HttpMethod m, string pat var key = rf.GetValue<string>(); if (!seen.Add(key)) return JsonValue.Create("<cycle>"); var target = doc; foreach (var seg in key.TrimStart('#', '/').Split('/')) target = target?[seg]; - var res = Deep(doc, target, seen); seen.Remove(key); return res; + var res = Deep(doc, target, seen, mark); seen.Remove(key); + if (mark && res is JsonObject resolved) resolved[ResolvedReference] = true; + return res; } var c = new JsonObject(); foreach (var kv in o.OrderBy(k => k.Key, StringComparer.Ordinal)) - if (kv.Key is not ("title" or "description" or "example" or "examples")) c[kv.Key] = Deep(doc, kv.Value, seen); + if (kv.Key is not ("title" or "description" or "example" or "examples")) + c[kv.Key] = kv.Key == "discriminator" ? Discriminator(o, kv.Value) : Deep(doc, kv.Value, seen, mark); return c; - case JsonArray a: var ca = new JsonArray(); foreach (var x in a) ca.Add(Deep(doc, x, seen)); return ca; + case JsonArray a: var ca = new JsonArray(); foreach (var x in a) ca.Add(Deep(doc, x, seen, mark)); return ca; default: return n?.DeepClone(); } } + + /// <summary> + /// A discriminator's mapping names a component per value, and a component's name is made of the type's, which + /// differs between the two sides by construction (row 347 of C07: <c>PB<TimeSpan></c> against + /// <c>PB<V></c>). Each value names instead the branch of the same schema's anyOf or oneOf it maps to, whose + /// content <see cref="Deep"/> compares; a target that is no branch stays as it is, and so still differs. + /// </summary> + static JsonNode? Discriminator(JsonObject schema, JsonNode? discriminator) + { + if (discriminator is not JsonObject d || d["mapping"] is not JsonObject mapping) return discriminator?.DeepClone(); + var branches = (schema["anyOf"] ?? schema["oneOf"]) as JsonArray; + var c = (JsonObject)d.DeepClone(); + var named = new JsonObject(); + foreach (var (value, target) in mapping.OrderBy(m => m.Key, StringComparer.Ordinal)) + { + var index = -1; + for (var i = 0; target is JsonValue && i < (branches?.Count ?? 0); i++) + if (branches![i]?["$ref"]?.GetValue<string>() == target.GetValue<string>()) { index = i; break; } + named[value] = index >= 0 ? $"branch {index}" : target?.DeepClone(); + } + c["mapping"] = named; + return c; + } } diff --git a/tools/probe/corpus/harness/JsonFamily.cs b/tools/probe/corpus/harness/JsonFamily.cs new file mode 100644 index 00000000..8a1ef837 --- /dev/null +++ b/tools/probe/corpus/harness/JsonFamily.cs @@ -0,0 +1,89 @@ +// Shared code of the JSON family's claim checks (C01 decimal spelling, C10 Preserve guard, L06 off the wire): +// serializing a held shape, reading its tokens, rewriting one token, and reading what a document holds back. +using System.Text; +using System.Text.Json; +using Probes.Shapes; +namespace Probes.Json; + +/// <summary>One token of a JSON document: its kind, its raw text (a string's without quotes), its value as text, and its byte span.</summary> +public sealed record JTok(JsonTokenType Type, string Raw, string? Text, int Start, int End); + +public static class Jx +{ + public static (string? Json, Exception? Ex) Ser(Held h, JsonSerializerOptions o) + { try { return (JsonSerializer.Serialize(h.Value, h.Declared, o), null); } catch (Exception ex) { return (null, ex); } } + + public static (object? Value, Exception? Ex) De(string text, Type declared, JsonSerializerOptions o) + { try { return (JsonSerializer.Deserialize(text, declared, o), null); } catch (Exception ex) { return (null, ex); } } + + /// <summary>Every token of a document, with the byte span it takes (a string's and a property name's quotes included).</summary> + public static List<JTok> Tokens(string json) + { + var bytes = Encoding.UTF8.GetBytes(json); + var rd = new Utf8JsonReader(bytes, new JsonReaderOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }); + var list = new List<JTok>(); + while (rd.Read()) + { + var start = (int)rd.TokenStartIndex; + switch (rd.TokenType) + { + case JsonTokenType.Number: + list.Add(new(rd.TokenType, Encoding.UTF8.GetString(rd.ValueSpan), Encoding.UTF8.GetString(rd.ValueSpan), start, start + rd.ValueSpan.Length)); break; + case JsonTokenType.String or JsonTokenType.PropertyName: + list.Add(new(rd.TokenType, Encoding.UTF8.GetString(rd.ValueSpan), rd.GetString(), start, start + rd.ValueSpan.Length + 2)); break; + default: + list.Add(new(rd.TokenType, "", null, start, start)); break; + } + } + return list; + } + + /// <summary>The document with the tokens <paramref name="at"/> replaced by <paramref name="with"/> (raw JSON text).</summary> + public static string Rewrite(string json, IEnumerable<JTok> at, Func<JTok, string> with) + { + var bytes = Encoding.UTF8.GetBytes(json); + var sb = new StringBuilder(); var pos = 0; + foreach (var t in at.OrderBy(t => t.Start)) + { + sb.Append(Encoding.UTF8.GetString(bytes, pos, t.Start - pos)); sb.Append(with(t)); pos = t.End; + } + sb.Append(Encoding.UTF8.GetString(bytes, pos, bytes.Length - pos)); + return sb.ToString(); + } + + /// <summary>Options that write what a value holds as the serializer writes it by default (a decimal with its scale, every field).</summary> + public static readonly JsonSerializerOptions Plain = new() { IncludeFields = true }; + + public static string Cut(string? s) => s is null ? "(null)" : s.Length > 220 ? s[..220] + "..." : s; + public static string Name(Type t) => t.IsGenericType ? t.Name[..t.Name.IndexOf('`')] + "<" + string.Join(", ", t.GetGenericArguments().Select(Name)) + ">" : t.Name; + /// <summary>A refusal's message with the type the other side holds named instead: the serializer names the shape it refused.</summary> + public static string Swap(string message, Type from, Type to) => message.Replace(from.ToString(), to.ToString()); + /// <summary>What a containing type holds: its property P or its field F.</summary> + public static object? Member(Type declared, object? holder) => + holder is null ? null : declared.GetProperty("P") is { } p ? p.GetValue(holder) : declared.GetField("F") is { } f ? f.GetValue(holder) : null; + public static string Ex(Exception e) => e.GetType().Name + ": " + Cut(e.Message); + + /// <summary>A copy of the options that writes every member: the ignore conditions reset, so a read has the member to read.</summary> + public static JsonSerializerOptions Full(JsonSerializerOptions o) => new(o) + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never, IgnoreReadOnlyProperties = false, IgnoreReadOnlyFields = false, + // A number written as a string reads back only with AllowReadingFromString; the document is input, so it says its other numbers plainly. + NumberHandling = o.NumberHandling & ~System.Text.Json.Serialization.JsonNumberHandling.WriteAsString, + }; +} + +/// <summary> +/// The row's json.* axes, applied by JsonRow: under json.TypeInfoResolver=source-generated-context the resolver is ProbeContext alone +/// (Shapes.g.cs), which lists every shape a row holds and its twin, so the setting runs as the row names it. A shape of the claim's own +/// the context does not list (L06's Option or Result) is the generator's gap: a harness error. +/// </summary> +public static class JCtx +{ + public static JsonSerializerOptions Apply(Row r, Entry e, params Type[] shapes) + { + var o = JsonRow.Apply(r, e).Options!; + var missing = JsonRow.Unlisted(r, shapes).Select(Jx.Name).ToList(); + if (missing.Count > 0) throw new InvalidOperationException("ProbeContext does not list (run tools/probe/generators/shapes.cs): " + string.Join(", ", missing)); + return o; + } +} diff --git a/tools/probe/corpus/harness/JsonFamilyShapes.g.cs b/tools/probe/corpus/harness/JsonFamilyShapes.g.cs new file mode 100644 index 00000000..5cb98433 --- /dev/null +++ b/tools/probe/corpus/harness/JsonFamilyShapes.g.cs @@ -0,0 +1,493 @@ +// <auto-generated/> by tools/probe/generators/json-family.cs stand-ins: L06's polymorphic stand-ins over Option and Result. Do not edit. +#nullable disable +#pragma warning disable CS8618, CS1591, CMTK0005, CMTK0006 +using CodoMetis.TypeKit; +using Probes.Shapes; +namespace Probes.Json; + +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_string_O), "d")] public abstract class L06PB_V_record_struct_string_O { } +public sealed class L06PD_V_record_struct_string_O : L06PB_V_record_struct_string_O { public Option<V_record_struct_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_string_RTE), "d")] public abstract class L06PB_V_record_struct_string_RTE { } +public sealed class L06PD_V_record_struct_string_RTE : L06PB_V_record_struct_string_RTE { public Result<V_record_struct_string, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_string_RE), "d")] public abstract class L06PB_V_record_struct_string_RE { } +public sealed class L06PD_V_record_struct_string_RE : L06PB_V_record_struct_string_RE { public Result<V_record_struct_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Guid_O), "d")] public abstract class L06PB_V_record_struct_Guid_O { } +public sealed class L06PD_V_record_struct_Guid_O : L06PB_V_record_struct_Guid_O { public Option<V_record_struct_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Guid_RTE), "d")] public abstract class L06PB_V_record_struct_Guid_RTE { } +public sealed class L06PD_V_record_struct_Guid_RTE : L06PB_V_record_struct_Guid_RTE { public Result<V_record_struct_Guid, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Guid_RE), "d")] public abstract class L06PB_V_record_struct_Guid_RE { } +public sealed class L06PD_V_record_struct_Guid_RE : L06PB_V_record_struct_Guid_RE { public Result<V_record_struct_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_int_O), "d")] public abstract class L06PB_V_record_struct_int_O { } +public sealed class L06PD_V_record_struct_int_O : L06PB_V_record_struct_int_O { public Option<V_record_struct_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_int_RTE), "d")] public abstract class L06PB_V_record_struct_int_RTE { } +public sealed class L06PD_V_record_struct_int_RTE : L06PB_V_record_struct_int_RTE { public Result<V_record_struct_int, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_int_RE), "d")] public abstract class L06PB_V_record_struct_int_RE { } +public sealed class L06PD_V_record_struct_int_RE : L06PB_V_record_struct_int_RE { public Result<V_record_struct_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_long_O), "d")] public abstract class L06PB_V_record_struct_long_O { } +public sealed class L06PD_V_record_struct_long_O : L06PB_V_record_struct_long_O { public Option<V_record_struct_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_long_RTE), "d")] public abstract class L06PB_V_record_struct_long_RTE { } +public sealed class L06PD_V_record_struct_long_RTE : L06PB_V_record_struct_long_RTE { public Result<V_record_struct_long, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_long_RE), "d")] public abstract class L06PB_V_record_struct_long_RE { } +public sealed class L06PD_V_record_struct_long_RE : L06PB_V_record_struct_long_RE { public Result<V_record_struct_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_byte_O), "d")] public abstract class L06PB_V_record_struct_byte_O { } +public sealed class L06PD_V_record_struct_byte_O : L06PB_V_record_struct_byte_O { public Option<V_record_struct_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_byte_RTE), "d")] public abstract class L06PB_V_record_struct_byte_RTE { } +public sealed class L06PD_V_record_struct_byte_RTE : L06PB_V_record_struct_byte_RTE { public Result<V_record_struct_byte, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_byte_RE), "d")] public abstract class L06PB_V_record_struct_byte_RE { } +public sealed class L06PD_V_record_struct_byte_RE : L06PB_V_record_struct_byte_RE { public Result<V_record_struct_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_ulong_O), "d")] public abstract class L06PB_V_record_struct_ulong_O { } +public sealed class L06PD_V_record_struct_ulong_O : L06PB_V_record_struct_ulong_O { public Option<V_record_struct_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_ulong_RTE), "d")] public abstract class L06PB_V_record_struct_ulong_RTE { } +public sealed class L06PD_V_record_struct_ulong_RTE : L06PB_V_record_struct_ulong_RTE { public Result<V_record_struct_ulong, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_ulong_RE), "d")] public abstract class L06PB_V_record_struct_ulong_RE { } +public sealed class L06PD_V_record_struct_ulong_RE : L06PB_V_record_struct_ulong_RE { public Result<V_record_struct_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_decimal_O), "d")] public abstract class L06PB_V_record_struct_decimal_O { } +public sealed class L06PD_V_record_struct_decimal_O : L06PB_V_record_struct_decimal_O { public Option<V_record_struct_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_decimal_RTE), "d")] public abstract class L06PB_V_record_struct_decimal_RTE { } +public sealed class L06PD_V_record_struct_decimal_RTE : L06PB_V_record_struct_decimal_RTE { public Result<V_record_struct_decimal, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_decimal_RE), "d")] public abstract class L06PB_V_record_struct_decimal_RE { } +public sealed class L06PD_V_record_struct_decimal_RE : L06PB_V_record_struct_decimal_RE { public Result<V_record_struct_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_double_O), "d")] public abstract class L06PB_V_record_struct_double_O { } +public sealed class L06PD_V_record_struct_double_O : L06PB_V_record_struct_double_O { public Option<V_record_struct_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_double_RTE), "d")] public abstract class L06PB_V_record_struct_double_RTE { } +public sealed class L06PD_V_record_struct_double_RTE : L06PB_V_record_struct_double_RTE { public Result<V_record_struct_double, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_double_RE), "d")] public abstract class L06PB_V_record_struct_double_RE { } +public sealed class L06PD_V_record_struct_double_RE : L06PB_V_record_struct_double_RE { public Result<V_record_struct_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_bool_O), "d")] public abstract class L06PB_V_record_struct_bool_O { } +public sealed class L06PD_V_record_struct_bool_O : L06PB_V_record_struct_bool_O { public Option<V_record_struct_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_bool_RTE), "d")] public abstract class L06PB_V_record_struct_bool_RTE { } +public sealed class L06PD_V_record_struct_bool_RTE : L06PB_V_record_struct_bool_RTE { public Result<V_record_struct_bool, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_bool_RE), "d")] public abstract class L06PB_V_record_struct_bool_RE { } +public sealed class L06PD_V_record_struct_bool_RE : L06PB_V_record_struct_bool_RE { public Result<V_record_struct_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_char_O), "d")] public abstract class L06PB_V_record_struct_char_O { } +public sealed class L06PD_V_record_struct_char_O : L06PB_V_record_struct_char_O { public Option<V_record_struct_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_char_RTE), "d")] public abstract class L06PB_V_record_struct_char_RTE { } +public sealed class L06PD_V_record_struct_char_RTE : L06PB_V_record_struct_char_RTE { public Result<V_record_struct_char, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_char_RE), "d")] public abstract class L06PB_V_record_struct_char_RE { } +public sealed class L06PD_V_record_struct_char_RE : L06PB_V_record_struct_char_RE { public Result<V_record_struct_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTime_O), "d")] public abstract class L06PB_V_record_struct_DateTime_O { } +public sealed class L06PD_V_record_struct_DateTime_O : L06PB_V_record_struct_DateTime_O { public Option<V_record_struct_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTime_RTE), "d")] public abstract class L06PB_V_record_struct_DateTime_RTE { } +public sealed class L06PD_V_record_struct_DateTime_RTE : L06PB_V_record_struct_DateTime_RTE { public Result<V_record_struct_DateTime, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTime_RE), "d")] public abstract class L06PB_V_record_struct_DateTime_RE { } +public sealed class L06PD_V_record_struct_DateTime_RE : L06PB_V_record_struct_DateTime_RE { public Result<V_record_struct_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTimeOffset_O), "d")] public abstract class L06PB_V_record_struct_DateTimeOffset_O { } +public sealed class L06PD_V_record_struct_DateTimeOffset_O : L06PB_V_record_struct_DateTimeOffset_O { public Option<V_record_struct_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTimeOffset_RTE), "d")] public abstract class L06PB_V_record_struct_DateTimeOffset_RTE { } +public sealed class L06PD_V_record_struct_DateTimeOffset_RTE : L06PB_V_record_struct_DateTimeOffset_RTE { public Result<V_record_struct_DateTimeOffset, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateTimeOffset_RE), "d")] public abstract class L06PB_V_record_struct_DateTimeOffset_RE { } +public sealed class L06PD_V_record_struct_DateTimeOffset_RE : L06PB_V_record_struct_DateTimeOffset_RE { public Result<V_record_struct_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateOnly_O), "d")] public abstract class L06PB_V_record_struct_DateOnly_O { } +public sealed class L06PD_V_record_struct_DateOnly_O : L06PB_V_record_struct_DateOnly_O { public Option<V_record_struct_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateOnly_RTE), "d")] public abstract class L06PB_V_record_struct_DateOnly_RTE { } +public sealed class L06PD_V_record_struct_DateOnly_RTE : L06PB_V_record_struct_DateOnly_RTE { public Result<V_record_struct_DateOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_DateOnly_RE), "d")] public abstract class L06PB_V_record_struct_DateOnly_RE { } +public sealed class L06PD_V_record_struct_DateOnly_RE : L06PB_V_record_struct_DateOnly_RE { public Result<V_record_struct_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeOnly_O), "d")] public abstract class L06PB_V_record_struct_TimeOnly_O { } +public sealed class L06PD_V_record_struct_TimeOnly_O : L06PB_V_record_struct_TimeOnly_O { public Option<V_record_struct_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeOnly_RTE), "d")] public abstract class L06PB_V_record_struct_TimeOnly_RTE { } +public sealed class L06PD_V_record_struct_TimeOnly_RTE : L06PB_V_record_struct_TimeOnly_RTE { public Result<V_record_struct_TimeOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeOnly_RE), "d")] public abstract class L06PB_V_record_struct_TimeOnly_RE { } +public sealed class L06PD_V_record_struct_TimeOnly_RE : L06PB_V_record_struct_TimeOnly_RE { public Result<V_record_struct_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeSpan_O), "d")] public abstract class L06PB_V_record_struct_TimeSpan_O { } +public sealed class L06PD_V_record_struct_TimeSpan_O : L06PB_V_record_struct_TimeSpan_O { public Option<V_record_struct_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeSpan_RTE), "d")] public abstract class L06PB_V_record_struct_TimeSpan_RTE { } +public sealed class L06PD_V_record_struct_TimeSpan_RTE : L06PB_V_record_struct_TimeSpan_RTE { public Result<V_record_struct_TimeSpan, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_TimeSpan_RE), "d")] public abstract class L06PB_V_record_struct_TimeSpan_RE { } +public sealed class L06PD_V_record_struct_TimeSpan_RE : L06PB_V_record_struct_TimeSpan_RE { public Result<V_record_struct_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_enum_O), "d")] public abstract class L06PB_V_record_struct_enum_O { } +public sealed class L06PD_V_record_struct_enum_O : L06PB_V_record_struct_enum_O { public Option<V_record_struct_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_enum_RTE), "d")] public abstract class L06PB_V_record_struct_enum_RTE { } +public sealed class L06PD_V_record_struct_enum_RTE : L06PB_V_record_struct_enum_RTE { public Result<V_record_struct_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_enum_RE), "d")] public abstract class L06PB_V_record_struct_enum_RE { } +public sealed class L06PD_V_record_struct_enum_RE : L06PB_V_record_struct_enum_RE { public Result<V_record_struct_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_flags_enum_O), "d")] public abstract class L06PB_V_record_struct_flags_enum_O { } +public sealed class L06PD_V_record_struct_flags_enum_O : L06PB_V_record_struct_flags_enum_O { public Option<V_record_struct_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_flags_enum_RTE), "d")] public abstract class L06PB_V_record_struct_flags_enum_RTE { } +public sealed class L06PD_V_record_struct_flags_enum_RTE : L06PB_V_record_struct_flags_enum_RTE { public Result<V_record_struct_flags_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_flags_enum_RE), "d")] public abstract class L06PB_V_record_struct_flags_enum_RE { } +public sealed class L06PD_V_record_struct_flags_enum_RE : L06PB_V_record_struct_flags_enum_RE { public Result<V_record_struct_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Uri_O), "d")] public abstract class L06PB_V_record_struct_Uri_O { } +public sealed class L06PD_V_record_struct_Uri_O : L06PB_V_record_struct_Uri_O { public Option<V_record_struct_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Uri_RTE), "d")] public abstract class L06PB_V_record_struct_Uri_RTE { } +public sealed class L06PD_V_record_struct_Uri_RTE : L06PB_V_record_struct_Uri_RTE { public Result<V_record_struct_Uri, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_Uri_RE), "d")] public abstract class L06PB_V_record_struct_Uri_RE { } +public sealed class L06PD_V_record_struct_Uri_RE : L06PB_V_record_struct_Uri_RE { public Result<V_record_struct_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_NodaTime_Instant_O), "d")] public abstract class L06PB_V_record_struct_NodaTime_Instant_O { } +public sealed class L06PD_V_record_struct_NodaTime_Instant_O : L06PB_V_record_struct_NodaTime_Instant_O { public Option<V_record_struct_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_NodaTime_Instant_RTE), "d")] public abstract class L06PB_V_record_struct_NodaTime_Instant_RTE { } +public sealed class L06PD_V_record_struct_NodaTime_Instant_RTE : L06PB_V_record_struct_NodaTime_Instant_RTE { public Result<V_record_struct_NodaTime_Instant, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_NodaTime_Instant_RE), "d")] public abstract class L06PB_V_record_struct_NodaTime_Instant_RE { } +public sealed class L06PD_V_record_struct_NodaTime_Instant_RE : L06PB_V_record_struct_NodaTime_Instant_RE { public Result<V_record_struct_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_own_record_class_O), "d")] public abstract class L06PB_V_record_struct_own_record_class_O { } +public sealed class L06PD_V_record_struct_own_record_class_O : L06PB_V_record_struct_own_record_class_O { public Option<V_record_struct_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_own_record_class_RTE), "d")] public abstract class L06PB_V_record_struct_own_record_class_RTE { } +public sealed class L06PD_V_record_struct_own_record_class_RTE : L06PB_V_record_struct_own_record_class_RTE { public Result<V_record_struct_own_record_class, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_record_struct_own_record_class_RE), "d")] public abstract class L06PB_V_record_struct_own_record_class_RE { } +public sealed class L06PD_V_record_struct_own_record_class_RE : L06PB_V_record_struct_own_record_class_RE { public Result<V_record_struct_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_string_O), "d")] public abstract class L06PB_V_sealed_record_class_string_O { } +public sealed class L06PD_V_sealed_record_class_string_O : L06PB_V_sealed_record_class_string_O { public Option<V_sealed_record_class_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_string_RTE), "d")] public abstract class L06PB_V_sealed_record_class_string_RTE { } +public sealed class L06PD_V_sealed_record_class_string_RTE : L06PB_V_sealed_record_class_string_RTE { public Result<V_sealed_record_class_string, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_string_RE), "d")] public abstract class L06PB_V_sealed_record_class_string_RE { } +public sealed class L06PD_V_sealed_record_class_string_RE : L06PB_V_sealed_record_class_string_RE { public Result<V_sealed_record_class_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Guid_O), "d")] public abstract class L06PB_V_sealed_record_class_Guid_O { } +public sealed class L06PD_V_sealed_record_class_Guid_O : L06PB_V_sealed_record_class_Guid_O { public Option<V_sealed_record_class_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Guid_RTE), "d")] public abstract class L06PB_V_sealed_record_class_Guid_RTE { } +public sealed class L06PD_V_sealed_record_class_Guid_RTE : L06PB_V_sealed_record_class_Guid_RTE { public Result<V_sealed_record_class_Guid, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Guid_RE), "d")] public abstract class L06PB_V_sealed_record_class_Guid_RE { } +public sealed class L06PD_V_sealed_record_class_Guid_RE : L06PB_V_sealed_record_class_Guid_RE { public Result<V_sealed_record_class_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_int_O), "d")] public abstract class L06PB_V_sealed_record_class_int_O { } +public sealed class L06PD_V_sealed_record_class_int_O : L06PB_V_sealed_record_class_int_O { public Option<V_sealed_record_class_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_int_RTE), "d")] public abstract class L06PB_V_sealed_record_class_int_RTE { } +public sealed class L06PD_V_sealed_record_class_int_RTE : L06PB_V_sealed_record_class_int_RTE { public Result<V_sealed_record_class_int, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_int_RE), "d")] public abstract class L06PB_V_sealed_record_class_int_RE { } +public sealed class L06PD_V_sealed_record_class_int_RE : L06PB_V_sealed_record_class_int_RE { public Result<V_sealed_record_class_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_long_O), "d")] public abstract class L06PB_V_sealed_record_class_long_O { } +public sealed class L06PD_V_sealed_record_class_long_O : L06PB_V_sealed_record_class_long_O { public Option<V_sealed_record_class_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_long_RTE), "d")] public abstract class L06PB_V_sealed_record_class_long_RTE { } +public sealed class L06PD_V_sealed_record_class_long_RTE : L06PB_V_sealed_record_class_long_RTE { public Result<V_sealed_record_class_long, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_long_RE), "d")] public abstract class L06PB_V_sealed_record_class_long_RE { } +public sealed class L06PD_V_sealed_record_class_long_RE : L06PB_V_sealed_record_class_long_RE { public Result<V_sealed_record_class_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_byte_O), "d")] public abstract class L06PB_V_sealed_record_class_byte_O { } +public sealed class L06PD_V_sealed_record_class_byte_O : L06PB_V_sealed_record_class_byte_O { public Option<V_sealed_record_class_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_byte_RTE), "d")] public abstract class L06PB_V_sealed_record_class_byte_RTE { } +public sealed class L06PD_V_sealed_record_class_byte_RTE : L06PB_V_sealed_record_class_byte_RTE { public Result<V_sealed_record_class_byte, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_byte_RE), "d")] public abstract class L06PB_V_sealed_record_class_byte_RE { } +public sealed class L06PD_V_sealed_record_class_byte_RE : L06PB_V_sealed_record_class_byte_RE { public Result<V_sealed_record_class_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_ulong_O), "d")] public abstract class L06PB_V_sealed_record_class_ulong_O { } +public sealed class L06PD_V_sealed_record_class_ulong_O : L06PB_V_sealed_record_class_ulong_O { public Option<V_sealed_record_class_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_ulong_RTE), "d")] public abstract class L06PB_V_sealed_record_class_ulong_RTE { } +public sealed class L06PD_V_sealed_record_class_ulong_RTE : L06PB_V_sealed_record_class_ulong_RTE { public Result<V_sealed_record_class_ulong, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_ulong_RE), "d")] public abstract class L06PB_V_sealed_record_class_ulong_RE { } +public sealed class L06PD_V_sealed_record_class_ulong_RE : L06PB_V_sealed_record_class_ulong_RE { public Result<V_sealed_record_class_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_decimal_O), "d")] public abstract class L06PB_V_sealed_record_class_decimal_O { } +public sealed class L06PD_V_sealed_record_class_decimal_O : L06PB_V_sealed_record_class_decimal_O { public Option<V_sealed_record_class_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_decimal_RTE), "d")] public abstract class L06PB_V_sealed_record_class_decimal_RTE { } +public sealed class L06PD_V_sealed_record_class_decimal_RTE : L06PB_V_sealed_record_class_decimal_RTE { public Result<V_sealed_record_class_decimal, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_decimal_RE), "d")] public abstract class L06PB_V_sealed_record_class_decimal_RE { } +public sealed class L06PD_V_sealed_record_class_decimal_RE : L06PB_V_sealed_record_class_decimal_RE { public Result<V_sealed_record_class_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_double_O), "d")] public abstract class L06PB_V_sealed_record_class_double_O { } +public sealed class L06PD_V_sealed_record_class_double_O : L06PB_V_sealed_record_class_double_O { public Option<V_sealed_record_class_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_double_RTE), "d")] public abstract class L06PB_V_sealed_record_class_double_RTE { } +public sealed class L06PD_V_sealed_record_class_double_RTE : L06PB_V_sealed_record_class_double_RTE { public Result<V_sealed_record_class_double, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_double_RE), "d")] public abstract class L06PB_V_sealed_record_class_double_RE { } +public sealed class L06PD_V_sealed_record_class_double_RE : L06PB_V_sealed_record_class_double_RE { public Result<V_sealed_record_class_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_bool_O), "d")] public abstract class L06PB_V_sealed_record_class_bool_O { } +public sealed class L06PD_V_sealed_record_class_bool_O : L06PB_V_sealed_record_class_bool_O { public Option<V_sealed_record_class_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_bool_RTE), "d")] public abstract class L06PB_V_sealed_record_class_bool_RTE { } +public sealed class L06PD_V_sealed_record_class_bool_RTE : L06PB_V_sealed_record_class_bool_RTE { public Result<V_sealed_record_class_bool, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_bool_RE), "d")] public abstract class L06PB_V_sealed_record_class_bool_RE { } +public sealed class L06PD_V_sealed_record_class_bool_RE : L06PB_V_sealed_record_class_bool_RE { public Result<V_sealed_record_class_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_char_O), "d")] public abstract class L06PB_V_sealed_record_class_char_O { } +public sealed class L06PD_V_sealed_record_class_char_O : L06PB_V_sealed_record_class_char_O { public Option<V_sealed_record_class_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_char_RTE), "d")] public abstract class L06PB_V_sealed_record_class_char_RTE { } +public sealed class L06PD_V_sealed_record_class_char_RTE : L06PB_V_sealed_record_class_char_RTE { public Result<V_sealed_record_class_char, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_char_RE), "d")] public abstract class L06PB_V_sealed_record_class_char_RE { } +public sealed class L06PD_V_sealed_record_class_char_RE : L06PB_V_sealed_record_class_char_RE { public Result<V_sealed_record_class_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTime_O), "d")] public abstract class L06PB_V_sealed_record_class_DateTime_O { } +public sealed class L06PD_V_sealed_record_class_DateTime_O : L06PB_V_sealed_record_class_DateTime_O { public Option<V_sealed_record_class_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTime_RTE), "d")] public abstract class L06PB_V_sealed_record_class_DateTime_RTE { } +public sealed class L06PD_V_sealed_record_class_DateTime_RTE : L06PB_V_sealed_record_class_DateTime_RTE { public Result<V_sealed_record_class_DateTime, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTime_RE), "d")] public abstract class L06PB_V_sealed_record_class_DateTime_RE { } +public sealed class L06PD_V_sealed_record_class_DateTime_RE : L06PB_V_sealed_record_class_DateTime_RE { public Result<V_sealed_record_class_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTimeOffset_O), "d")] public abstract class L06PB_V_sealed_record_class_DateTimeOffset_O { } +public sealed class L06PD_V_sealed_record_class_DateTimeOffset_O : L06PB_V_sealed_record_class_DateTimeOffset_O { public Option<V_sealed_record_class_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTimeOffset_RTE), "d")] public abstract class L06PB_V_sealed_record_class_DateTimeOffset_RTE { } +public sealed class L06PD_V_sealed_record_class_DateTimeOffset_RTE : L06PB_V_sealed_record_class_DateTimeOffset_RTE { public Result<V_sealed_record_class_DateTimeOffset, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateTimeOffset_RE), "d")] public abstract class L06PB_V_sealed_record_class_DateTimeOffset_RE { } +public sealed class L06PD_V_sealed_record_class_DateTimeOffset_RE : L06PB_V_sealed_record_class_DateTimeOffset_RE { public Result<V_sealed_record_class_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateOnly_O), "d")] public abstract class L06PB_V_sealed_record_class_DateOnly_O { } +public sealed class L06PD_V_sealed_record_class_DateOnly_O : L06PB_V_sealed_record_class_DateOnly_O { public Option<V_sealed_record_class_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateOnly_RTE), "d")] public abstract class L06PB_V_sealed_record_class_DateOnly_RTE { } +public sealed class L06PD_V_sealed_record_class_DateOnly_RTE : L06PB_V_sealed_record_class_DateOnly_RTE { public Result<V_sealed_record_class_DateOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_DateOnly_RE), "d")] public abstract class L06PB_V_sealed_record_class_DateOnly_RE { } +public sealed class L06PD_V_sealed_record_class_DateOnly_RE : L06PB_V_sealed_record_class_DateOnly_RE { public Result<V_sealed_record_class_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeOnly_O), "d")] public abstract class L06PB_V_sealed_record_class_TimeOnly_O { } +public sealed class L06PD_V_sealed_record_class_TimeOnly_O : L06PB_V_sealed_record_class_TimeOnly_O { public Option<V_sealed_record_class_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeOnly_RTE), "d")] public abstract class L06PB_V_sealed_record_class_TimeOnly_RTE { } +public sealed class L06PD_V_sealed_record_class_TimeOnly_RTE : L06PB_V_sealed_record_class_TimeOnly_RTE { public Result<V_sealed_record_class_TimeOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeOnly_RE), "d")] public abstract class L06PB_V_sealed_record_class_TimeOnly_RE { } +public sealed class L06PD_V_sealed_record_class_TimeOnly_RE : L06PB_V_sealed_record_class_TimeOnly_RE { public Result<V_sealed_record_class_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeSpan_O), "d")] public abstract class L06PB_V_sealed_record_class_TimeSpan_O { } +public sealed class L06PD_V_sealed_record_class_TimeSpan_O : L06PB_V_sealed_record_class_TimeSpan_O { public Option<V_sealed_record_class_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeSpan_RTE), "d")] public abstract class L06PB_V_sealed_record_class_TimeSpan_RTE { } +public sealed class L06PD_V_sealed_record_class_TimeSpan_RTE : L06PB_V_sealed_record_class_TimeSpan_RTE { public Result<V_sealed_record_class_TimeSpan, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_TimeSpan_RE), "d")] public abstract class L06PB_V_sealed_record_class_TimeSpan_RE { } +public sealed class L06PD_V_sealed_record_class_TimeSpan_RE : L06PB_V_sealed_record_class_TimeSpan_RE { public Result<V_sealed_record_class_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_enum_O), "d")] public abstract class L06PB_V_sealed_record_class_enum_O { } +public sealed class L06PD_V_sealed_record_class_enum_O : L06PB_V_sealed_record_class_enum_O { public Option<V_sealed_record_class_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_enum_RTE), "d")] public abstract class L06PB_V_sealed_record_class_enum_RTE { } +public sealed class L06PD_V_sealed_record_class_enum_RTE : L06PB_V_sealed_record_class_enum_RTE { public Result<V_sealed_record_class_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_enum_RE), "d")] public abstract class L06PB_V_sealed_record_class_enum_RE { } +public sealed class L06PD_V_sealed_record_class_enum_RE : L06PB_V_sealed_record_class_enum_RE { public Result<V_sealed_record_class_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_flags_enum_O), "d")] public abstract class L06PB_V_sealed_record_class_flags_enum_O { } +public sealed class L06PD_V_sealed_record_class_flags_enum_O : L06PB_V_sealed_record_class_flags_enum_O { public Option<V_sealed_record_class_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_flags_enum_RTE), "d")] public abstract class L06PB_V_sealed_record_class_flags_enum_RTE { } +public sealed class L06PD_V_sealed_record_class_flags_enum_RTE : L06PB_V_sealed_record_class_flags_enum_RTE { public Result<V_sealed_record_class_flags_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_flags_enum_RE), "d")] public abstract class L06PB_V_sealed_record_class_flags_enum_RE { } +public sealed class L06PD_V_sealed_record_class_flags_enum_RE : L06PB_V_sealed_record_class_flags_enum_RE { public Result<V_sealed_record_class_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Uri_O), "d")] public abstract class L06PB_V_sealed_record_class_Uri_O { } +public sealed class L06PD_V_sealed_record_class_Uri_O : L06PB_V_sealed_record_class_Uri_O { public Option<V_sealed_record_class_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Uri_RTE), "d")] public abstract class L06PB_V_sealed_record_class_Uri_RTE { } +public sealed class L06PD_V_sealed_record_class_Uri_RTE : L06PB_V_sealed_record_class_Uri_RTE { public Result<V_sealed_record_class_Uri, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_Uri_RE), "d")] public abstract class L06PB_V_sealed_record_class_Uri_RE { } +public sealed class L06PD_V_sealed_record_class_Uri_RE : L06PB_V_sealed_record_class_Uri_RE { public Result<V_sealed_record_class_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_NodaTime_Instant_O), "d")] public abstract class L06PB_V_sealed_record_class_NodaTime_Instant_O { } +public sealed class L06PD_V_sealed_record_class_NodaTime_Instant_O : L06PB_V_sealed_record_class_NodaTime_Instant_O { public Option<V_sealed_record_class_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_NodaTime_Instant_RTE), "d")] public abstract class L06PB_V_sealed_record_class_NodaTime_Instant_RTE { } +public sealed class L06PD_V_sealed_record_class_NodaTime_Instant_RTE : L06PB_V_sealed_record_class_NodaTime_Instant_RTE { public Result<V_sealed_record_class_NodaTime_Instant, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_NodaTime_Instant_RE), "d")] public abstract class L06PB_V_sealed_record_class_NodaTime_Instant_RE { } +public sealed class L06PD_V_sealed_record_class_NodaTime_Instant_RE : L06PB_V_sealed_record_class_NodaTime_Instant_RE { public Result<V_sealed_record_class_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_own_record_class_O), "d")] public abstract class L06PB_V_sealed_record_class_own_record_class_O { } +public sealed class L06PD_V_sealed_record_class_own_record_class_O : L06PB_V_sealed_record_class_own_record_class_O { public Option<V_sealed_record_class_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_own_record_class_RTE), "d")] public abstract class L06PB_V_sealed_record_class_own_record_class_RTE { } +public sealed class L06PD_V_sealed_record_class_own_record_class_RTE : L06PB_V_sealed_record_class_own_record_class_RTE { public Result<V_sealed_record_class_own_record_class, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_sealed_record_class_own_record_class_RE), "d")] public abstract class L06PB_V_sealed_record_class_own_record_class_RE { } +public sealed class L06PD_V_sealed_record_class_own_record_class_RE : L06PB_V_sealed_record_class_own_record_class_RE { public Result<V_sealed_record_class_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_string_O), "d")] public abstract class L06PB_V_validated_struct_string_O { } +public sealed class L06PD_V_validated_struct_string_O : L06PB_V_validated_struct_string_O { public Option<V_validated_struct_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_string_RTE), "d")] public abstract class L06PB_V_validated_struct_string_RTE { } +public sealed class L06PD_V_validated_struct_string_RTE : L06PB_V_validated_struct_string_RTE { public Result<V_validated_struct_string, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_string_RE), "d")] public abstract class L06PB_V_validated_struct_string_RE { } +public sealed class L06PD_V_validated_struct_string_RE : L06PB_V_validated_struct_string_RE { public Result<V_validated_struct_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Guid_O), "d")] public abstract class L06PB_V_validated_struct_Guid_O { } +public sealed class L06PD_V_validated_struct_Guid_O : L06PB_V_validated_struct_Guid_O { public Option<V_validated_struct_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Guid_RTE), "d")] public abstract class L06PB_V_validated_struct_Guid_RTE { } +public sealed class L06PD_V_validated_struct_Guid_RTE : L06PB_V_validated_struct_Guid_RTE { public Result<V_validated_struct_Guid, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Guid_RE), "d")] public abstract class L06PB_V_validated_struct_Guid_RE { } +public sealed class L06PD_V_validated_struct_Guid_RE : L06PB_V_validated_struct_Guid_RE { public Result<V_validated_struct_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_int_O), "d")] public abstract class L06PB_V_validated_struct_int_O { } +public sealed class L06PD_V_validated_struct_int_O : L06PB_V_validated_struct_int_O { public Option<V_validated_struct_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_int_RTE), "d")] public abstract class L06PB_V_validated_struct_int_RTE { } +public sealed class L06PD_V_validated_struct_int_RTE : L06PB_V_validated_struct_int_RTE { public Result<V_validated_struct_int, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_int_RE), "d")] public abstract class L06PB_V_validated_struct_int_RE { } +public sealed class L06PD_V_validated_struct_int_RE : L06PB_V_validated_struct_int_RE { public Result<V_validated_struct_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_long_O), "d")] public abstract class L06PB_V_validated_struct_long_O { } +public sealed class L06PD_V_validated_struct_long_O : L06PB_V_validated_struct_long_O { public Option<V_validated_struct_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_long_RTE), "d")] public abstract class L06PB_V_validated_struct_long_RTE { } +public sealed class L06PD_V_validated_struct_long_RTE : L06PB_V_validated_struct_long_RTE { public Result<V_validated_struct_long, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_long_RE), "d")] public abstract class L06PB_V_validated_struct_long_RE { } +public sealed class L06PD_V_validated_struct_long_RE : L06PB_V_validated_struct_long_RE { public Result<V_validated_struct_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_byte_O), "d")] public abstract class L06PB_V_validated_struct_byte_O { } +public sealed class L06PD_V_validated_struct_byte_O : L06PB_V_validated_struct_byte_O { public Option<V_validated_struct_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_byte_RTE), "d")] public abstract class L06PB_V_validated_struct_byte_RTE { } +public sealed class L06PD_V_validated_struct_byte_RTE : L06PB_V_validated_struct_byte_RTE { public Result<V_validated_struct_byte, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_byte_RE), "d")] public abstract class L06PB_V_validated_struct_byte_RE { } +public sealed class L06PD_V_validated_struct_byte_RE : L06PB_V_validated_struct_byte_RE { public Result<V_validated_struct_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_ulong_O), "d")] public abstract class L06PB_V_validated_struct_ulong_O { } +public sealed class L06PD_V_validated_struct_ulong_O : L06PB_V_validated_struct_ulong_O { public Option<V_validated_struct_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_ulong_RTE), "d")] public abstract class L06PB_V_validated_struct_ulong_RTE { } +public sealed class L06PD_V_validated_struct_ulong_RTE : L06PB_V_validated_struct_ulong_RTE { public Result<V_validated_struct_ulong, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_ulong_RE), "d")] public abstract class L06PB_V_validated_struct_ulong_RE { } +public sealed class L06PD_V_validated_struct_ulong_RE : L06PB_V_validated_struct_ulong_RE { public Result<V_validated_struct_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_decimal_O), "d")] public abstract class L06PB_V_validated_struct_decimal_O { } +public sealed class L06PD_V_validated_struct_decimal_O : L06PB_V_validated_struct_decimal_O { public Option<V_validated_struct_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_decimal_RTE), "d")] public abstract class L06PB_V_validated_struct_decimal_RTE { } +public sealed class L06PD_V_validated_struct_decimal_RTE : L06PB_V_validated_struct_decimal_RTE { public Result<V_validated_struct_decimal, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_decimal_RE), "d")] public abstract class L06PB_V_validated_struct_decimal_RE { } +public sealed class L06PD_V_validated_struct_decimal_RE : L06PB_V_validated_struct_decimal_RE { public Result<V_validated_struct_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_double_O), "d")] public abstract class L06PB_V_validated_struct_double_O { } +public sealed class L06PD_V_validated_struct_double_O : L06PB_V_validated_struct_double_O { public Option<V_validated_struct_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_double_RTE), "d")] public abstract class L06PB_V_validated_struct_double_RTE { } +public sealed class L06PD_V_validated_struct_double_RTE : L06PB_V_validated_struct_double_RTE { public Result<V_validated_struct_double, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_double_RE), "d")] public abstract class L06PB_V_validated_struct_double_RE { } +public sealed class L06PD_V_validated_struct_double_RE : L06PB_V_validated_struct_double_RE { public Result<V_validated_struct_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_bool_O), "d")] public abstract class L06PB_V_validated_struct_bool_O { } +public sealed class L06PD_V_validated_struct_bool_O : L06PB_V_validated_struct_bool_O { public Option<V_validated_struct_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_bool_RTE), "d")] public abstract class L06PB_V_validated_struct_bool_RTE { } +public sealed class L06PD_V_validated_struct_bool_RTE : L06PB_V_validated_struct_bool_RTE { public Result<V_validated_struct_bool, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_bool_RE), "d")] public abstract class L06PB_V_validated_struct_bool_RE { } +public sealed class L06PD_V_validated_struct_bool_RE : L06PB_V_validated_struct_bool_RE { public Result<V_validated_struct_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_char_O), "d")] public abstract class L06PB_V_validated_struct_char_O { } +public sealed class L06PD_V_validated_struct_char_O : L06PB_V_validated_struct_char_O { public Option<V_validated_struct_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_char_RTE), "d")] public abstract class L06PB_V_validated_struct_char_RTE { } +public sealed class L06PD_V_validated_struct_char_RTE : L06PB_V_validated_struct_char_RTE { public Result<V_validated_struct_char, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_char_RE), "d")] public abstract class L06PB_V_validated_struct_char_RE { } +public sealed class L06PD_V_validated_struct_char_RE : L06PB_V_validated_struct_char_RE { public Result<V_validated_struct_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTime_O), "d")] public abstract class L06PB_V_validated_struct_DateTime_O { } +public sealed class L06PD_V_validated_struct_DateTime_O : L06PB_V_validated_struct_DateTime_O { public Option<V_validated_struct_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTime_RTE), "d")] public abstract class L06PB_V_validated_struct_DateTime_RTE { } +public sealed class L06PD_V_validated_struct_DateTime_RTE : L06PB_V_validated_struct_DateTime_RTE { public Result<V_validated_struct_DateTime, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTime_RE), "d")] public abstract class L06PB_V_validated_struct_DateTime_RE { } +public sealed class L06PD_V_validated_struct_DateTime_RE : L06PB_V_validated_struct_DateTime_RE { public Result<V_validated_struct_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTimeOffset_O), "d")] public abstract class L06PB_V_validated_struct_DateTimeOffset_O { } +public sealed class L06PD_V_validated_struct_DateTimeOffset_O : L06PB_V_validated_struct_DateTimeOffset_O { public Option<V_validated_struct_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTimeOffset_RTE), "d")] public abstract class L06PB_V_validated_struct_DateTimeOffset_RTE { } +public sealed class L06PD_V_validated_struct_DateTimeOffset_RTE : L06PB_V_validated_struct_DateTimeOffset_RTE { public Result<V_validated_struct_DateTimeOffset, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateTimeOffset_RE), "d")] public abstract class L06PB_V_validated_struct_DateTimeOffset_RE { } +public sealed class L06PD_V_validated_struct_DateTimeOffset_RE : L06PB_V_validated_struct_DateTimeOffset_RE { public Result<V_validated_struct_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateOnly_O), "d")] public abstract class L06PB_V_validated_struct_DateOnly_O { } +public sealed class L06PD_V_validated_struct_DateOnly_O : L06PB_V_validated_struct_DateOnly_O { public Option<V_validated_struct_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateOnly_RTE), "d")] public abstract class L06PB_V_validated_struct_DateOnly_RTE { } +public sealed class L06PD_V_validated_struct_DateOnly_RTE : L06PB_V_validated_struct_DateOnly_RTE { public Result<V_validated_struct_DateOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_DateOnly_RE), "d")] public abstract class L06PB_V_validated_struct_DateOnly_RE { } +public sealed class L06PD_V_validated_struct_DateOnly_RE : L06PB_V_validated_struct_DateOnly_RE { public Result<V_validated_struct_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeOnly_O), "d")] public abstract class L06PB_V_validated_struct_TimeOnly_O { } +public sealed class L06PD_V_validated_struct_TimeOnly_O : L06PB_V_validated_struct_TimeOnly_O { public Option<V_validated_struct_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeOnly_RTE), "d")] public abstract class L06PB_V_validated_struct_TimeOnly_RTE { } +public sealed class L06PD_V_validated_struct_TimeOnly_RTE : L06PB_V_validated_struct_TimeOnly_RTE { public Result<V_validated_struct_TimeOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeOnly_RE), "d")] public abstract class L06PB_V_validated_struct_TimeOnly_RE { } +public sealed class L06PD_V_validated_struct_TimeOnly_RE : L06PB_V_validated_struct_TimeOnly_RE { public Result<V_validated_struct_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeSpan_O), "d")] public abstract class L06PB_V_validated_struct_TimeSpan_O { } +public sealed class L06PD_V_validated_struct_TimeSpan_O : L06PB_V_validated_struct_TimeSpan_O { public Option<V_validated_struct_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeSpan_RTE), "d")] public abstract class L06PB_V_validated_struct_TimeSpan_RTE { } +public sealed class L06PD_V_validated_struct_TimeSpan_RTE : L06PB_V_validated_struct_TimeSpan_RTE { public Result<V_validated_struct_TimeSpan, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_TimeSpan_RE), "d")] public abstract class L06PB_V_validated_struct_TimeSpan_RE { } +public sealed class L06PD_V_validated_struct_TimeSpan_RE : L06PB_V_validated_struct_TimeSpan_RE { public Result<V_validated_struct_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_enum_O), "d")] public abstract class L06PB_V_validated_struct_enum_O { } +public sealed class L06PD_V_validated_struct_enum_O : L06PB_V_validated_struct_enum_O { public Option<V_validated_struct_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_enum_RTE), "d")] public abstract class L06PB_V_validated_struct_enum_RTE { } +public sealed class L06PD_V_validated_struct_enum_RTE : L06PB_V_validated_struct_enum_RTE { public Result<V_validated_struct_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_enum_RE), "d")] public abstract class L06PB_V_validated_struct_enum_RE { } +public sealed class L06PD_V_validated_struct_enum_RE : L06PB_V_validated_struct_enum_RE { public Result<V_validated_struct_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_flags_enum_O), "d")] public abstract class L06PB_V_validated_struct_flags_enum_O { } +public sealed class L06PD_V_validated_struct_flags_enum_O : L06PB_V_validated_struct_flags_enum_O { public Option<V_validated_struct_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_flags_enum_RTE), "d")] public abstract class L06PB_V_validated_struct_flags_enum_RTE { } +public sealed class L06PD_V_validated_struct_flags_enum_RTE : L06PB_V_validated_struct_flags_enum_RTE { public Result<V_validated_struct_flags_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_flags_enum_RE), "d")] public abstract class L06PB_V_validated_struct_flags_enum_RE { } +public sealed class L06PD_V_validated_struct_flags_enum_RE : L06PB_V_validated_struct_flags_enum_RE { public Result<V_validated_struct_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Uri_O), "d")] public abstract class L06PB_V_validated_struct_Uri_O { } +public sealed class L06PD_V_validated_struct_Uri_O : L06PB_V_validated_struct_Uri_O { public Option<V_validated_struct_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Uri_RTE), "d")] public abstract class L06PB_V_validated_struct_Uri_RTE { } +public sealed class L06PD_V_validated_struct_Uri_RTE : L06PB_V_validated_struct_Uri_RTE { public Result<V_validated_struct_Uri, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_Uri_RE), "d")] public abstract class L06PB_V_validated_struct_Uri_RE { } +public sealed class L06PD_V_validated_struct_Uri_RE : L06PB_V_validated_struct_Uri_RE { public Result<V_validated_struct_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_NodaTime_Instant_O), "d")] public abstract class L06PB_V_validated_struct_NodaTime_Instant_O { } +public sealed class L06PD_V_validated_struct_NodaTime_Instant_O : L06PB_V_validated_struct_NodaTime_Instant_O { public Option<V_validated_struct_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_NodaTime_Instant_RTE), "d")] public abstract class L06PB_V_validated_struct_NodaTime_Instant_RTE { } +public sealed class L06PD_V_validated_struct_NodaTime_Instant_RTE : L06PB_V_validated_struct_NodaTime_Instant_RTE { public Result<V_validated_struct_NodaTime_Instant, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_NodaTime_Instant_RE), "d")] public abstract class L06PB_V_validated_struct_NodaTime_Instant_RE { } +public sealed class L06PD_V_validated_struct_NodaTime_Instant_RE : L06PB_V_validated_struct_NodaTime_Instant_RE { public Result<V_validated_struct_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_own_record_class_O), "d")] public abstract class L06PB_V_validated_struct_own_record_class_O { } +public sealed class L06PD_V_validated_struct_own_record_class_O : L06PB_V_validated_struct_own_record_class_O { public Option<V_validated_struct_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_own_record_class_RTE), "d")] public abstract class L06PB_V_validated_struct_own_record_class_RTE { } +public sealed class L06PD_V_validated_struct_own_record_class_RTE : L06PB_V_validated_struct_own_record_class_RTE { public Result<V_validated_struct_own_record_class, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_struct_own_record_class_RE), "d")] public abstract class L06PB_V_validated_struct_own_record_class_RE { } +public sealed class L06PD_V_validated_struct_own_record_class_RE : L06PB_V_validated_struct_own_record_class_RE { public Result<V_validated_struct_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_string_O), "d")] public abstract class L06PB_V_validated_class_string_O { } +public sealed class L06PD_V_validated_class_string_O : L06PB_V_validated_class_string_O { public Option<V_validated_class_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_string_RTE), "d")] public abstract class L06PB_V_validated_class_string_RTE { } +public sealed class L06PD_V_validated_class_string_RTE : L06PB_V_validated_class_string_RTE { public Result<V_validated_class_string, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_string_RE), "d")] public abstract class L06PB_V_validated_class_string_RE { } +public sealed class L06PD_V_validated_class_string_RE : L06PB_V_validated_class_string_RE { public Result<V_validated_class_string> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Guid_O), "d")] public abstract class L06PB_V_validated_class_Guid_O { } +public sealed class L06PD_V_validated_class_Guid_O : L06PB_V_validated_class_Guid_O { public Option<V_validated_class_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Guid_RTE), "d")] public abstract class L06PB_V_validated_class_Guid_RTE { } +public sealed class L06PD_V_validated_class_Guid_RTE : L06PB_V_validated_class_Guid_RTE { public Result<V_validated_class_Guid, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Guid_RE), "d")] public abstract class L06PB_V_validated_class_Guid_RE { } +public sealed class L06PD_V_validated_class_Guid_RE : L06PB_V_validated_class_Guid_RE { public Result<V_validated_class_Guid> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_int_O), "d")] public abstract class L06PB_V_validated_class_int_O { } +public sealed class L06PD_V_validated_class_int_O : L06PB_V_validated_class_int_O { public Option<V_validated_class_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_int_RTE), "d")] public abstract class L06PB_V_validated_class_int_RTE { } +public sealed class L06PD_V_validated_class_int_RTE : L06PB_V_validated_class_int_RTE { public Result<V_validated_class_int, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_int_RE), "d")] public abstract class L06PB_V_validated_class_int_RE { } +public sealed class L06PD_V_validated_class_int_RE : L06PB_V_validated_class_int_RE { public Result<V_validated_class_int> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_long_O), "d")] public abstract class L06PB_V_validated_class_long_O { } +public sealed class L06PD_V_validated_class_long_O : L06PB_V_validated_class_long_O { public Option<V_validated_class_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_long_RTE), "d")] public abstract class L06PB_V_validated_class_long_RTE { } +public sealed class L06PD_V_validated_class_long_RTE : L06PB_V_validated_class_long_RTE { public Result<V_validated_class_long, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_long_RE), "d")] public abstract class L06PB_V_validated_class_long_RE { } +public sealed class L06PD_V_validated_class_long_RE : L06PB_V_validated_class_long_RE { public Result<V_validated_class_long> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_byte_O), "d")] public abstract class L06PB_V_validated_class_byte_O { } +public sealed class L06PD_V_validated_class_byte_O : L06PB_V_validated_class_byte_O { public Option<V_validated_class_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_byte_RTE), "d")] public abstract class L06PB_V_validated_class_byte_RTE { } +public sealed class L06PD_V_validated_class_byte_RTE : L06PB_V_validated_class_byte_RTE { public Result<V_validated_class_byte, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_byte_RE), "d")] public abstract class L06PB_V_validated_class_byte_RE { } +public sealed class L06PD_V_validated_class_byte_RE : L06PB_V_validated_class_byte_RE { public Result<V_validated_class_byte> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_ulong_O), "d")] public abstract class L06PB_V_validated_class_ulong_O { } +public sealed class L06PD_V_validated_class_ulong_O : L06PB_V_validated_class_ulong_O { public Option<V_validated_class_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_ulong_RTE), "d")] public abstract class L06PB_V_validated_class_ulong_RTE { } +public sealed class L06PD_V_validated_class_ulong_RTE : L06PB_V_validated_class_ulong_RTE { public Result<V_validated_class_ulong, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_ulong_RE), "d")] public abstract class L06PB_V_validated_class_ulong_RE { } +public sealed class L06PD_V_validated_class_ulong_RE : L06PB_V_validated_class_ulong_RE { public Result<V_validated_class_ulong> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_decimal_O), "d")] public abstract class L06PB_V_validated_class_decimal_O { } +public sealed class L06PD_V_validated_class_decimal_O : L06PB_V_validated_class_decimal_O { public Option<V_validated_class_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_decimal_RTE), "d")] public abstract class L06PB_V_validated_class_decimal_RTE { } +public sealed class L06PD_V_validated_class_decimal_RTE : L06PB_V_validated_class_decimal_RTE { public Result<V_validated_class_decimal, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_decimal_RE), "d")] public abstract class L06PB_V_validated_class_decimal_RE { } +public sealed class L06PD_V_validated_class_decimal_RE : L06PB_V_validated_class_decimal_RE { public Result<V_validated_class_decimal> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_double_O), "d")] public abstract class L06PB_V_validated_class_double_O { } +public sealed class L06PD_V_validated_class_double_O : L06PB_V_validated_class_double_O { public Option<V_validated_class_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_double_RTE), "d")] public abstract class L06PB_V_validated_class_double_RTE { } +public sealed class L06PD_V_validated_class_double_RTE : L06PB_V_validated_class_double_RTE { public Result<V_validated_class_double, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_double_RE), "d")] public abstract class L06PB_V_validated_class_double_RE { } +public sealed class L06PD_V_validated_class_double_RE : L06PB_V_validated_class_double_RE { public Result<V_validated_class_double> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_bool_O), "d")] public abstract class L06PB_V_validated_class_bool_O { } +public sealed class L06PD_V_validated_class_bool_O : L06PB_V_validated_class_bool_O { public Option<V_validated_class_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_bool_RTE), "d")] public abstract class L06PB_V_validated_class_bool_RTE { } +public sealed class L06PD_V_validated_class_bool_RTE : L06PB_V_validated_class_bool_RTE { public Result<V_validated_class_bool, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_bool_RE), "d")] public abstract class L06PB_V_validated_class_bool_RE { } +public sealed class L06PD_V_validated_class_bool_RE : L06PB_V_validated_class_bool_RE { public Result<V_validated_class_bool> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_char_O), "d")] public abstract class L06PB_V_validated_class_char_O { } +public sealed class L06PD_V_validated_class_char_O : L06PB_V_validated_class_char_O { public Option<V_validated_class_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_char_RTE), "d")] public abstract class L06PB_V_validated_class_char_RTE { } +public sealed class L06PD_V_validated_class_char_RTE : L06PB_V_validated_class_char_RTE { public Result<V_validated_class_char, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_char_RE), "d")] public abstract class L06PB_V_validated_class_char_RE { } +public sealed class L06PD_V_validated_class_char_RE : L06PB_V_validated_class_char_RE { public Result<V_validated_class_char> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTime_O), "d")] public abstract class L06PB_V_validated_class_DateTime_O { } +public sealed class L06PD_V_validated_class_DateTime_O : L06PB_V_validated_class_DateTime_O { public Option<V_validated_class_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTime_RTE), "d")] public abstract class L06PB_V_validated_class_DateTime_RTE { } +public sealed class L06PD_V_validated_class_DateTime_RTE : L06PB_V_validated_class_DateTime_RTE { public Result<V_validated_class_DateTime, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTime_RE), "d")] public abstract class L06PB_V_validated_class_DateTime_RE { } +public sealed class L06PD_V_validated_class_DateTime_RE : L06PB_V_validated_class_DateTime_RE { public Result<V_validated_class_DateTime> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTimeOffset_O), "d")] public abstract class L06PB_V_validated_class_DateTimeOffset_O { } +public sealed class L06PD_V_validated_class_DateTimeOffset_O : L06PB_V_validated_class_DateTimeOffset_O { public Option<V_validated_class_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTimeOffset_RTE), "d")] public abstract class L06PB_V_validated_class_DateTimeOffset_RTE { } +public sealed class L06PD_V_validated_class_DateTimeOffset_RTE : L06PB_V_validated_class_DateTimeOffset_RTE { public Result<V_validated_class_DateTimeOffset, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateTimeOffset_RE), "d")] public abstract class L06PB_V_validated_class_DateTimeOffset_RE { } +public sealed class L06PD_V_validated_class_DateTimeOffset_RE : L06PB_V_validated_class_DateTimeOffset_RE { public Result<V_validated_class_DateTimeOffset> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateOnly_O), "d")] public abstract class L06PB_V_validated_class_DateOnly_O { } +public sealed class L06PD_V_validated_class_DateOnly_O : L06PB_V_validated_class_DateOnly_O { public Option<V_validated_class_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateOnly_RTE), "d")] public abstract class L06PB_V_validated_class_DateOnly_RTE { } +public sealed class L06PD_V_validated_class_DateOnly_RTE : L06PB_V_validated_class_DateOnly_RTE { public Result<V_validated_class_DateOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_DateOnly_RE), "d")] public abstract class L06PB_V_validated_class_DateOnly_RE { } +public sealed class L06PD_V_validated_class_DateOnly_RE : L06PB_V_validated_class_DateOnly_RE { public Result<V_validated_class_DateOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeOnly_O), "d")] public abstract class L06PB_V_validated_class_TimeOnly_O { } +public sealed class L06PD_V_validated_class_TimeOnly_O : L06PB_V_validated_class_TimeOnly_O { public Option<V_validated_class_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeOnly_RTE), "d")] public abstract class L06PB_V_validated_class_TimeOnly_RTE { } +public sealed class L06PD_V_validated_class_TimeOnly_RTE : L06PB_V_validated_class_TimeOnly_RTE { public Result<V_validated_class_TimeOnly, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeOnly_RE), "d")] public abstract class L06PB_V_validated_class_TimeOnly_RE { } +public sealed class L06PD_V_validated_class_TimeOnly_RE : L06PB_V_validated_class_TimeOnly_RE { public Result<V_validated_class_TimeOnly> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeSpan_O), "d")] public abstract class L06PB_V_validated_class_TimeSpan_O { } +public sealed class L06PD_V_validated_class_TimeSpan_O : L06PB_V_validated_class_TimeSpan_O { public Option<V_validated_class_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeSpan_RTE), "d")] public abstract class L06PB_V_validated_class_TimeSpan_RTE { } +public sealed class L06PD_V_validated_class_TimeSpan_RTE : L06PB_V_validated_class_TimeSpan_RTE { public Result<V_validated_class_TimeSpan, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_TimeSpan_RE), "d")] public abstract class L06PB_V_validated_class_TimeSpan_RE { } +public sealed class L06PD_V_validated_class_TimeSpan_RE : L06PB_V_validated_class_TimeSpan_RE { public Result<V_validated_class_TimeSpan> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_enum_O), "d")] public abstract class L06PB_V_validated_class_enum_O { } +public sealed class L06PD_V_validated_class_enum_O : L06PB_V_validated_class_enum_O { public Option<V_validated_class_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_enum_RTE), "d")] public abstract class L06PB_V_validated_class_enum_RTE { } +public sealed class L06PD_V_validated_class_enum_RTE : L06PB_V_validated_class_enum_RTE { public Result<V_validated_class_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_enum_RE), "d")] public abstract class L06PB_V_validated_class_enum_RE { } +public sealed class L06PD_V_validated_class_enum_RE : L06PB_V_validated_class_enum_RE { public Result<V_validated_class_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_flags_enum_O), "d")] public abstract class L06PB_V_validated_class_flags_enum_O { } +public sealed class L06PD_V_validated_class_flags_enum_O : L06PB_V_validated_class_flags_enum_O { public Option<V_validated_class_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_flags_enum_RTE), "d")] public abstract class L06PB_V_validated_class_flags_enum_RTE { } +public sealed class L06PD_V_validated_class_flags_enum_RTE : L06PB_V_validated_class_flags_enum_RTE { public Result<V_validated_class_flags_enum, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_flags_enum_RE), "d")] public abstract class L06PB_V_validated_class_flags_enum_RE { } +public sealed class L06PD_V_validated_class_flags_enum_RE : L06PB_V_validated_class_flags_enum_RE { public Result<V_validated_class_flags_enum> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Uri_O), "d")] public abstract class L06PB_V_validated_class_Uri_O { } +public sealed class L06PD_V_validated_class_Uri_O : L06PB_V_validated_class_Uri_O { public Option<V_validated_class_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Uri_RTE), "d")] public abstract class L06PB_V_validated_class_Uri_RTE { } +public sealed class L06PD_V_validated_class_Uri_RTE : L06PB_V_validated_class_Uri_RTE { public Result<V_validated_class_Uri, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_Uri_RE), "d")] public abstract class L06PB_V_validated_class_Uri_RE { } +public sealed class L06PD_V_validated_class_Uri_RE : L06PB_V_validated_class_Uri_RE { public Result<V_validated_class_Uri> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_NodaTime_Instant_O), "d")] public abstract class L06PB_V_validated_class_NodaTime_Instant_O { } +public sealed class L06PD_V_validated_class_NodaTime_Instant_O : L06PB_V_validated_class_NodaTime_Instant_O { public Option<V_validated_class_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_NodaTime_Instant_RTE), "d")] public abstract class L06PB_V_validated_class_NodaTime_Instant_RTE { } +public sealed class L06PD_V_validated_class_NodaTime_Instant_RTE : L06PB_V_validated_class_NodaTime_Instant_RTE { public Result<V_validated_class_NodaTime_Instant, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_NodaTime_Instant_RE), "d")] public abstract class L06PB_V_validated_class_NodaTime_Instant_RE { } +public sealed class L06PD_V_validated_class_NodaTime_Instant_RE : L06PB_V_validated_class_NodaTime_Instant_RE { public Result<V_validated_class_NodaTime_Instant> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_own_record_class_O), "d")] public abstract class L06PB_V_validated_class_own_record_class_O { } +public sealed class L06PD_V_validated_class_own_record_class_O : L06PB_V_validated_class_own_record_class_O { public Option<V_validated_class_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_own_record_class_RTE), "d")] public abstract class L06PB_V_validated_class_own_record_class_RTE { } +public sealed class L06PD_V_validated_class_own_record_class_RTE : L06PB_V_validated_class_own_record_class_RTE { public Result<V_validated_class_own_record_class, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_V_validated_class_own_record_class_RE), "d")] public abstract class L06PB_V_validated_class_own_record_class_RE { } +public sealed class L06PD_V_validated_class_own_record_class_RE : L06PB_V_validated_class_own_record_class_RE { public Result<V_validated_class_own_record_class> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_PSet_O), "d")] public abstract class L06PB_PSet_O { } +public sealed class L06PD_PSet_O : L06PB_PSet_O { public Option<PSet> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_PSet_RTE), "d")] public abstract class L06PB_PSet_RTE { } +public sealed class L06PD_PSet_RTE : L06PB_PSet_RTE { public Result<PSet, ShapeFault> Value { get; set; } } +[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof(L06PD_PSet_RE), "d")] public abstract class L06PB_PSet_RE { } +public sealed class L06PD_PSet_RE : L06PB_PSet_RE { public Result<PSet> Value { get; set; } } diff --git a/tools/probe/corpus/harness/JsonRow.cs b/tools/probe/corpus/harness/JsonRow.cs index 081ad4d5..f39be575 100644 --- a/tools/probe/corpus/harness/JsonRow.cs +++ b/tools/probe/corpus/harness/JsonRow.cs @@ -18,6 +18,8 @@ public static class Make /// endpoints declare, while the value object is still made by <c>From(T)</c> and read through its <c>Value</c>. /// </summary> public static Type Held(Type t) => Nullable.GetUnderlyingType(t) ?? t; + /// <summary>Whether a type admits null: a class, or a struct made nullable. A value object and its twin admit it alike (Equivalence).</summary> + public static bool AdmitsNull(Type t) => !t.IsValueType || Nullable.GetUnderlyingType(t) is not null; /// <summary>Creates the value object over a plain sample (From / FromKnownGood), with optional parameters filled.</summary> public static object Vo(Entry e, object plain) @@ -101,7 +103,7 @@ public static Applied Apply(Row r, Entry e, JsonSerializerOptions? into = null) r.Substituted(kv.Key, "default", "STJ refused it: " + ex.GetType().Name); } } - r.Note("dropped[" + string.Join(",", dropped) + "]"); + r.Note(NoteKeys.Dropped, string.Join(",", dropped)); return new(o, dropped.Count == 0 ? null : string.Join(",", dropped)); } @@ -112,19 +114,35 @@ public static Applied Apply(Row r, Entry e, JsonSerializerOptions? into = null) _ => throw new ArgumentException("unknown policy " + v) }; - static bool ListedInContext(Row r, Entry e) + /// <summary> + /// json.TypeInfoResolver=source-generated-context is strict: the options resolve through ProbeContext alone, which lists every shape a row + /// holds and its twin (shapes.cs), with no reflection behind it; json.TypeInfoResolverChain=context-then-default is the configuration with + /// the reflection fallback, the context then reflection, beside it or on its own. A row's shape the context does not list is the generator's gap, a harness error: the serializer's refusal of + /// an unlisted type would read as the framework's. + /// </summary> + static void Listed(Row r, Entry e) { - try - { - var plain = e.Valid(); var vo = Make.Vo(e, plain); - var pos = r["shape.position"]; var con = (r.Get("shape.containing") ?? "class"); - var hp = Pos.Build(pos, con, e, false, plain); var hv = Pos.Build(pos, con, e, true, vo); - var ok = ProbeContext.Default.GetTypeInfo(hp.Declared) is not null && ProbeContext.Default.GetTypeInfo(hv.Declared) is not null; - r.Note(ok ? "ctx-strict" : "ctx-fallback"); - if (!ok) r.Substituted("json.TypeInfoResolver", "source-generated-context+reflection-fallback", "the shape is not listed in the context"); - return ok; - } - catch { return false; } + var plain = e.Valid(); var vo = Make.Vo(e, plain); + var pos = r["shape.position"]; var con = (r.Get("shape.containing") ?? "class"); + var (hp, hv) = Sides.Twins(pos, con, e, plain, vo); + var missing = Missing(hp.Declared, hv.Declared).Select(t => t.ToString()).ToList(); + if (missing.Count > 0) { r.Outcome("harness-error", $"ProbeContext does not list {string.Join(", ", missing)}: run tools/probe/generators/shapes.cs"); return; } + r.Note(NoteKeys.CtxStrict); + } + + /// <summary>The <paramref name="shapes"/> ProbeContext does not list, under json.TypeInfoResolver=source-generated-context: the generator's gap, which the caller reports as a harness error.</summary> + public static IEnumerable<Type> Unlisted(Row r, params Type[] shapes) => + r.Is("json.TypeInfoResolver", "source-generated-context") ? Missing(shapes) : []; + + /// <summary> + /// The types ProbeContext has no metadata for, asked through the resolver's interface, which builds no contract: building one is the + /// serializer's step, where a context refuses an Option or Result made nullable (L06's not-promised entry), not the listing's. + /// </summary> + static IEnumerable<Type> Missing(params Type[] shapes) + { + // the context answers what a type's metadata asks the options for (the struct a Nullable holds) too + var probe = new JsonSerializerOptions { TypeInfoResolver = ProbeContext.Default }; + return shapes.Where(t => ((IJsonTypeInfoResolver)ProbeContext.Default).GetTypeInfo(t, probe) is null).ToList(); } static void Set(Row row, JsonSerializerOptions o, string name, string v, Entry e) @@ -147,11 +165,15 @@ static void Set(Row row, JsonSerializerOptions o, string name, string v, Entry e } return; case "TypeInfoResolver": - if (v == "source-generated-context") o.TypeInfoResolver = ListedInContext(row, e) ? ProbeContext.Default : JsonTypeInfoResolver.Combine(ProbeContext.Default, new DefaultJsonTypeInfoResolver()); + if (v == "source-generated-context") { o.TypeInfoResolver = ProbeContext.Default; Listed(row, e); } else o.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ti => { foreach (var p in ti.Properties) p.Name += "_x"; } } }; return; case "TypeInfoResolverChain": - o.TypeInfoResolverChain.Add(ProbeContext.Default); o.TypeInfoResolverChain.Add(new DefaultJsonTypeInfoResolver()); return; + // The context, then reflection for what it does not list: under json.TypeInfoResolver=source-generated-context the chain + // already starts with the context, which System.Text.Json refuses to insert a second time (the resolver into its own + // chain), so the reflection fallback follows it. That pair is the context with the fallback, the strict context's other side. + if (!o.TypeInfoResolverChain.Contains(ProbeContext.Default)) o.TypeInfoResolverChain.Add(ProbeContext.Default); + o.TypeInfoResolverChain.Add(new DefaultJsonTypeInfoResolver()); return; } var prop = typeof(JsonSerializerOptions).GetProperty(name) ?? throw new ArgumentException("no json option " + name); object value = prop.PropertyType == typeof(bool) ? bool.Parse(v) : prop.PropertyType.IsEnum ? Enum.Parse(prop.PropertyType, v) : throw new ArgumentException($"no hand value for {name}"); diff --git a/tools/probe/corpus/harness/KeyShapes.g.cs b/tools/probe/corpus/harness/KeyShapes.g.cs new file mode 100644 index 00000000..0a98b904 --- /dev/null +++ b/tools/probe/corpus/harness/KeyShapes.g.cs @@ -0,0 +1,128 @@ +// <auto-generated/> by tools/probe/generators/ef.cs keys; the EF key probes' shape catalogue (shape.kind x shape.wrapped). Do not edit. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000 +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using Probes.Shapes; +namespace Probes.Ef; +public readonly partial record struct K_record_struct_Guid : IValue<Guid>; +public readonly partial record struct K_record_struct_byte : IValue<byte>; +public readonly partial record struct K_record_struct_char : IValue<char>; +public readonly partial record struct K_record_struct_int : IValue<int>; +public readonly partial record struct K_record_struct_long : IValue<long>; +public readonly partial record struct K_record_struct_string : IValue<string>; +public readonly partial record struct K_record_struct_ulong : IValue<ulong>; +public sealed partial record class K_sealed_record_class_Guid : IValue<Guid>; +public sealed partial record class K_sealed_record_class_byte : IValue<byte>; +public sealed partial record class K_sealed_record_class_char : IValue<char>; +public sealed partial record class K_sealed_record_class_int : IValue<int>; +public sealed partial record class K_sealed_record_class_long : IValue<long>; +public sealed partial record class K_sealed_record_class_string : IValue<string>; +public sealed partial record class K_sealed_record_class_ulong : IValue<ulong>; +public sealed partial record class K_validated_class_Guid : IValidatedValue<K_validated_class_Guid, Guid, ShapeFault> +{ + public static Result<K_validated_class_Guid, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new K_validated_class_Guid(value); +} +public sealed partial record class K_validated_class_byte : IValidatedValue<K_validated_class_byte, byte, ShapeFault> +{ + public static Result<K_validated_class_byte, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new K_validated_class_byte(value); +} +public sealed partial record class K_validated_class_char : IValidatedValue<K_validated_class_char, char, ShapeFault> +{ + public static Result<K_validated_class_char, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new K_validated_class_char(value); +} +public sealed partial record class K_validated_class_int : IValidatedValue<K_validated_class_int, int, ShapeFault> +{ + public static Result<K_validated_class_int, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new K_validated_class_int(value); +} +public sealed partial record class K_validated_class_long : IValidatedValue<K_validated_class_long, long, ShapeFault> +{ + public static Result<K_validated_class_long, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new K_validated_class_long(value); +} +public sealed partial record class K_validated_class_string : IValidatedValue<K_validated_class_string, string, ShapeFault> +{ + public static Result<K_validated_class_string, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new K_validated_class_string(value); +} +public sealed partial record class K_validated_class_ulong : IValidatedValue<K_validated_class_ulong, ulong, ShapeFault> +{ + public static Result<K_validated_class_ulong, ShapeFault> Create(ulong value) => + EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new K_validated_class_ulong(value); +} +public readonly partial record struct K_validated_struct_Guid : IValidatedValue<K_validated_struct_Guid, Guid, ShapeFault> +{ + public static Result<K_validated_struct_Guid, ShapeFault> Create(Guid value) => + EqualityComparer<Guid>.Default.Equals(value, Guid.Empty) ? Result.Error(ShapeFault.Refused) : new K_validated_struct_Guid(value); +} +public readonly partial record struct K_validated_struct_byte : IValidatedValue<K_validated_struct_byte, byte, ShapeFault> +{ + public static Result<K_validated_struct_byte, ShapeFault> Create(byte value) => + EqualityComparer<byte>.Default.Equals(value, (byte)255) ? Result.Error(ShapeFault.Refused) : new K_validated_struct_byte(value); +} +public readonly partial record struct K_validated_struct_char : IValidatedValue<K_validated_struct_char, char, ShapeFault> +{ + public static Result<K_validated_struct_char, ShapeFault> Create(char value) => + EqualityComparer<char>.Default.Equals(value, 'x') ? Result.Error(ShapeFault.Refused) : new K_validated_struct_char(value); +} +public readonly partial record struct K_validated_struct_int : IValidatedValue<K_validated_struct_int, int, ShapeFault> +{ + public static Result<K_validated_struct_int, ShapeFault> Create(int value) => + EqualityComparer<int>.Default.Equals(value, -1) ? Result.Error(ShapeFault.Refused) : new K_validated_struct_int(value); +} +public readonly partial record struct K_validated_struct_long : IValidatedValue<K_validated_struct_long, long, ShapeFault> +{ + public static Result<K_validated_struct_long, ShapeFault> Create(long value) => + EqualityComparer<long>.Default.Equals(value, -1L) ? Result.Error(ShapeFault.Refused) : new K_validated_struct_long(value); +} +public readonly partial record struct K_validated_struct_string : IValidatedValue<K_validated_struct_string, string, ShapeFault> +{ + public static Result<K_validated_struct_string, ShapeFault> Create(string value) => + EqualityComparer<string>.Default.Equals(value, "SECRET-7f3a") ? Result.Error(ShapeFault.Refused) : new K_validated_struct_string(value); +} +public readonly partial record struct K_validated_struct_ulong : IValidatedValue<K_validated_struct_ulong, ulong, ShapeFault> +{ + public static Result<K_validated_struct_ulong, ShapeFault> Create(ulong value) => + EqualityComparer<ulong>.Default.Equals(value, 0UL) ? Result.Error(ShapeFault.Refused) : new K_validated_struct_ulong(value); +} +public static partial class KeyRegistry +{ + public static readonly List<EfEntry> All = Build(); + static List<EfEntry> Build() { var e = new List<EfEntry>(); Fill(e); return e; } + public static EfEntry Of(Probes.Shapes.Row r) { var k = r["shape.kind"]; var w = r["shape.wrapped"]; return All.First(x => x.Kind == k && x.Wrapped == w); } + public static void Fill(List<EfEntry> e) + { + e.Add(new EfEntry("record-struct", "Guid", "top-level", typeof(K_record_struct_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("record-struct", "byte", "top-level", typeof(K_record_struct_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("record-struct", "char", "top-level", typeof(K_record_struct_char), typeof(char), () => 'a')); + e.Add(new EfEntry("record-struct", "int", "top-level", typeof(K_record_struct_int), typeof(int), () => 42)); + e.Add(new EfEntry("record-struct", "long", "top-level", typeof(K_record_struct_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("record-struct", "string", "top-level", typeof(K_record_struct_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("record-struct", "ulong", "top-level", typeof(K_record_struct_ulong), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("sealed-record-class", "Guid", "top-level", typeof(K_sealed_record_class_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("sealed-record-class", "byte", "top-level", typeof(K_sealed_record_class_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("sealed-record-class", "char", "top-level", typeof(K_sealed_record_class_char), typeof(char), () => 'a')); + e.Add(new EfEntry("sealed-record-class", "int", "top-level", typeof(K_sealed_record_class_int), typeof(int), () => 42)); + e.Add(new EfEntry("sealed-record-class", "long", "top-level", typeof(K_sealed_record_class_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("sealed-record-class", "string", "top-level", typeof(K_sealed_record_class_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("sealed-record-class", "ulong", "top-level", typeof(K_sealed_record_class_ulong), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("validated-class", "Guid", "top-level", typeof(K_validated_class_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-class", "byte", "top-level", typeof(K_validated_class_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-class", "char", "top-level", typeof(K_validated_class_char), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-class", "int", "top-level", typeof(K_validated_class_int), typeof(int), () => 42)); + e.Add(new EfEntry("validated-class", "long", "top-level", typeof(K_validated_class_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-class", "string", "top-level", typeof(K_validated_class_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-class", "ulong", "top-level", typeof(K_validated_class_ulong), typeof(ulong), () => 4200000000UL)); + e.Add(new EfEntry("validated-struct", "Guid", "top-level", typeof(K_validated_struct_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"))); + e.Add(new EfEntry("validated-struct", "byte", "top-level", typeof(K_validated_struct_byte), typeof(byte), () => (byte)7)); + e.Add(new EfEntry("validated-struct", "char", "top-level", typeof(K_validated_struct_char), typeof(char), () => 'a')); + e.Add(new EfEntry("validated-struct", "int", "top-level", typeof(K_validated_struct_int), typeof(int), () => 42)); + e.Add(new EfEntry("validated-struct", "long", "top-level", typeof(K_validated_struct_long), typeof(long), () => 4200000000L)); + e.Add(new EfEntry("validated-struct", "string", "top-level", typeof(K_validated_struct_string), typeof(string), () => "alpha")); + e.Add(new EfEntry("validated-struct", "ulong", "top-level", typeof(K_validated_struct_ulong), typeof(ulong), () => 4200000000UL)); + } +} diff --git a/tools/probe/corpus/harness/L01JsonBytesProbe.cs b/tools/probe/corpus/harness/L01JsonBytesProbe.cs index 2f6e8291..4eb89c77 100644 --- a/tools/probe/corpus/harness/L01JsonBytesProbe.cs +++ b/tools/probe/corpus/harness/L01JsonBytesProbe.cs @@ -7,10 +7,9 @@ namespace Probes; public sealed class L01JsonBytesProbe { - const string Claim = "L01-json-bytes"; - public static TheoryData<string, int> Data() => Rows.Data(Claim); + public static TheoryData<string, int> Data() => Rows.Data("L01JsonBytesProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run(claim, idx, async r => + public Task Row(string probe, int idx) => Rec.Run(probe, idx, async r => { var e = Registry.Of(r); var a = JsonRow.Apply(r, e); diff --git a/tools/probe/corpus/harness/L02NoQuoteProbe.cs b/tools/probe/corpus/harness/L02NoQuoteProbe.cs index 6f4323c7..11f88a8b 100644 --- a/tools/probe/corpus/harness/L02NoQuoteProbe.cs +++ b/tools/probe/corpus/harness/L02NoQuoteProbe.cs @@ -4,7 +4,7 @@ namespace Probes; public sealed class L02NoQuoteProbe { - public static TheoryData<string, int> Data() => Rows.Data("L02-no-quote"); + public static TheoryData<string, int> Data() => Rows.Data("L02NoQuoteProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run(claim, idx, r => Task.FromResult(TextClaims.NoQuote(r, Registry.Of(r)))); + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(TextClaims.NoQuote(r, Registry.Of(r)))); } diff --git a/tools/probe/corpus/harness/L03OneRuleProbe.cs b/tools/probe/corpus/harness/L03OneRuleProbe.cs index e8eed7e2..e8e1fd24 100644 --- a/tools/probe/corpus/harness/L03OneRuleProbe.cs +++ b/tools/probe/corpus/harness/L03OneRuleProbe.cs @@ -4,7 +4,7 @@ namespace Probes; public sealed class L03OneRuleProbe { - public static TheoryData<string, int> Data() => Rows.Data("L03-one-rule"); + public static TheoryData<string, int> Data() => Rows.Data("L03OneRuleProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run(claim, idx, r => Task.FromResult(TextClaims.OneRule(r, Registry.Of(r)))); + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(TextClaims.OneRule(r, Registry.Of(r)))); } diff --git a/tools/probe/corpus/harness/L04RoundTripProbe.cs b/tools/probe/corpus/harness/L04RoundTripProbe.cs index 32cc04fb..2ae7ad0c 100644 --- a/tools/probe/corpus/harness/L04RoundTripProbe.cs +++ b/tools/probe/corpus/harness/L04RoundTripProbe.cs @@ -4,7 +4,7 @@ namespace Probes; public sealed class L04RoundTripProbe { - public static TheoryData<string, int> Data() => Rows.Data("L04-round-trip"); + public static TheoryData<string, int> Data() => Rows.Data("L04RoundTripProbe"); [Theory, MemberData(nameof(Data))] - public Task Row(string claim, int idx) => Rec.Run(claim, idx, r => Task.FromResult(TextClaims.RoundTrip(r, Registry.Of(r)))); + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(TextClaims.RoundTrip(r, Registry.Of(r)))); } diff --git a/tools/probe/corpus/harness/L05Shapes.g.cs b/tools/probe/corpus/harness/L05Shapes.g.cs new file mode 100644 index 00000000..f3f576ad --- /dev/null +++ b/tools/probe/corpus/harness/L05Shapes.g.cs @@ -0,0 +1,575 @@ +// <auto-generated/> by tools/probe/generators/json-family.cs from the rows of L05-equality. Do not edit. +#nullable enable +#pragma warning disable CS1591, CMTK0005, CMTK1023 // CMTK1023: a base record with an explicit IEquatable is the shape under test +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using CodoMetis.TypeKit.ClosedSets; +using Probes.Shapes; +namespace Probes.Json; + +public interface IL05Tag { string Tag { get; } } +public abstract record L05B1; +public abstract record L05B2a; +public abstract record L05B2b : L05B2a; +public abstract record L05B3a; +public abstract record L05B3b : L05B3a; +public abstract record L05B3c : L05B3b; +// a deeper base whose own IEquatable says "never equal": the value object must not use it +public abstract record L05E0 : IEquatable<L05E0> { bool IEquatable<L05E0>.Equals(L05E0? other) => false; } +public abstract record L05E1 : L05E0; +public abstract class L05CB1; +public abstract class L05CB2a; +public abstract class L05CB2b : L05CB2a; +public abstract class L05CB3a; +public abstract class L05CB3b : L05CB3a; +public abstract class L05CB3c : L05CB3b; +public abstract class L05CE0 : IEquatable<L05CE0> { bool IEquatable<L05CE0>.Equals(L05CE0? other) => ReferenceEquals(this, other); } +public abstract class L05CE1 : L05CE0; + +public partial class L05Oc0 { public sealed partial class L05_0_closed_set__ : L05CB1, IClosedSet<L05_0_closed_set__> { public static readonly L05_0_closed_set__ Alpha = new(); public static readonly L05_0_closed_set__ Beta = new(); } } +public partial class L05Ot1 { public partial class Inner { public sealed partial class L05_1_closed_set__ : L05CB1, IClosedSet<L05_1_closed_set__> { public static readonly L05_1_closed_set__ Alpha = new(); public static readonly L05_1_closed_set__ Beta = new(); } } } +public partial class L05Oc2 { public sealed partial class L05_2_closed_set__ : L05CB2b, IClosedSet<L05_2_closed_set__> { public static readonly L05_2_closed_set__ Alpha = new(); public static readonly L05_2_closed_set__ Beta = new(); } } +public partial struct L05Os3 { public sealed partial class L05_3_closed_set__ : L05CB2b, IClosedSet<L05_3_closed_set__> { public static readonly L05_3_closed_set__ Alpha = new(); public static readonly L05_3_closed_set__ Beta = new(); } } +public sealed partial class L05_4_closed_set__ : L05CB2b, IClosedSet<L05_4_closed_set__> { public static readonly L05_4_closed_set__ Alpha = new(); public static readonly L05_4_closed_set__ Beta = new(); } +public partial class L05Oc5 { public sealed partial class L05_5_closed_set__ : L05CB3c, IClosedSet<L05_5_closed_set__> { public static readonly L05_5_closed_set__ Alpha = new(); public static readonly L05_5_closed_set__ Beta = new(); } } +public partial struct L05Os6 { public sealed partial class L05_6_closed_set__ : L05CB3c, IClosedSet<L05_6_closed_set__> { public static readonly L05_6_closed_set__ Alpha = new(); public static readonly L05_6_closed_set__ Beta = new(); } } +public partial class L05Ot7 { public partial class Inner { public sealed partial class L05_7_closed_set__ : L05CB3c, IClosedSet<L05_7_closed_set__> { public static readonly L05_7_closed_set__ Alpha = new(); public static readonly L05_7_closed_set__ Beta = new(); } } } +public sealed partial class L05_8_closed_set__ : L05CB3c, IClosedSet<L05_8_closed_set__> { public static readonly L05_8_closed_set__ Alpha = new(); public static readonly L05_8_closed_set__ Beta = new(); } +public partial class L05Oc9 { public sealed partial class L05_9_closed_set__ : L05CE1, IClosedSet<L05_9_closed_set__> { public static readonly L05_9_closed_set__ Alpha = new(); public static readonly L05_9_closed_set__ Beta = new(); } } +public partial struct L05Os10 { public sealed partial class L05_10_closed_set__ : L05CE1, IClosedSet<L05_10_closed_set__> { public static readonly L05_10_closed_set__ Alpha = new(); public static readonly L05_10_closed_set__ Beta = new(); } } +public partial class L05Ot11 { public partial class Inner { public sealed partial class L05_11_closed_set__ : L05CE1, IClosedSet<L05_11_closed_set__> { public static readonly L05_11_closed_set__ Alpha = new(); public static readonly L05_11_closed_set__ Beta = new(); } } } +public sealed partial class L05_12_closed_set__ : L05CE1, IClosedSet<L05_12_closed_set__> { public static readonly L05_12_closed_set__ Alpha = new(); public static readonly L05_12_closed_set__ Beta = new(); } +public partial class L05Oc13 { public sealed partial class L05_13_closed_set__ : IL05Tag, IClosedSet<L05_13_closed_set__> { public static readonly L05_13_closed_set__ Alpha = new(); public static readonly L05_13_closed_set__ Beta = new(); string IL05Tag.Tag => "t"; } } +public sealed partial class L05_14_closed_set__ : IL05Tag, IClosedSet<L05_14_closed_set__> { public static readonly L05_14_closed_set__ Alpha = new(); public static readonly L05_14_closed_set__ Beta = new(); string IL05Tag.Tag => "t"; } +public partial struct L05Os15 { public sealed partial class L05_15_closed_set__ : IClosedSet<L05_15_closed_set__> { public static readonly L05_15_closed_set__ Alpha = new(); public static readonly L05_15_closed_set__ Beta = new(); } } +public partial class L05Oc16 { public readonly partial record struct L05_16_record_struct_DateOnly : IL05Tag, IValue<DateOnly> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os17 { public readonly partial record struct L05_17_record_struct_DateOnly : IL05Tag, IValue<DateOnly> { string IL05Tag.Tag => "t"; } } +public partial class L05Ot18 { public partial class Inner { public readonly partial record struct L05_18_record_struct_DateOnly : IL05Tag, IValue<DateOnly> { string IL05Tag.Tag => "t"; } } } +public partial class L05Oc19 { public readonly partial record struct L05_19_record_struct_DateTime : IValue<DateTime> { } } +public partial struct L05Os20 { public readonly partial record struct L05_20_record_struct_DateTimeOffset : IValue<DateTimeOffset> { } } +public partial struct L05Os21 { public readonly partial record struct L05_21_record_struct_Guid : IL05Tag, IValue<Guid> { string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_22_record_struct_Guid : IL05Tag, IValue<Guid> { string IL05Tag.Tag => "t"; } +public partial class L05Oc23 { public readonly partial record struct L05_23_record_struct_NodaTime_Instant : IL05Tag, IValue<global::NodaTime.Instant> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os24 { public readonly partial record struct L05_24_record_struct_NodaTime_Instant : IL05Tag, IValue<global::NodaTime.Instant> { string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_25_record_struct_NodaTime_Instant : IL05Tag, IValue<global::NodaTime.Instant> { string IL05Tag.Tag => "t"; } +public partial class L05Ot26 { public partial class Inner { public readonly partial record struct L05_26_record_struct_TimeOnly : IL05Tag, IValue<TimeOnly> { string IL05Tag.Tag => "t"; } } } +public readonly partial record struct L05_27_record_struct_TimeOnly : IL05Tag, IValue<TimeOnly> { string IL05Tag.Tag => "t"; } +public partial struct L05Os28 { public readonly partial record struct L05_28_record_struct_TimeOnly : IValue<TimeOnly> { } } +public partial class L05Oc29 { public readonly partial record struct L05_29_record_struct_TimeSpan : IL05Tag, IValue<TimeSpan> { string IL05Tag.Tag => "t"; } } +public partial class L05Oc30 { public readonly partial record struct L05_30_record_struct_TimeSpan : IValue<TimeSpan> { } } +public readonly partial record struct L05_31_record_struct_Uri : IL05Tag, IValue<Uri> { string IL05Tag.Tag => "t"; } +public readonly partial record struct L05_32_record_struct_Uri : IValue<Uri> { } +public partial struct L05Os33 { public readonly partial record struct L05_33_record_struct_bool : IL05Tag, IValue<bool> { string IL05Tag.Tag => "t"; } } +public partial class L05Ot34 { public partial class Inner { public readonly partial record struct L05_34_record_struct_bool : IL05Tag, IValue<bool> { string IL05Tag.Tag => "t"; } } } +public readonly partial record struct L05_35_record_struct_bool : IL05Tag, IValue<bool> { string IL05Tag.Tag => "t"; } +public readonly partial record struct L05_36_record_struct_byte : IL05Tag, IValue<byte> { string IL05Tag.Tag => "t"; } +public partial class L05Oc37 { public readonly partial record struct L05_37_record_struct_byte : IValue<byte> { } } +public readonly partial record struct L05_38_record_struct_char : IL05Tag, IValue<char> { string IL05Tag.Tag => "t"; } +public partial struct L05Os39 { public readonly partial record struct L05_39_record_struct_char : IValue<char> { } } +public partial class L05Oc40 { public readonly partial record struct L05_40_record_struct_decimal : IL05Tag, IValue<decimal> { string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_41_record_struct_decimal : IL05Tag, IValue<decimal> { string IL05Tag.Tag => "t"; } +public partial class L05Oc42 { public readonly partial record struct L05_42_record_struct_decimal : IValue<decimal> { } } +public partial class L05Oc43 { public readonly partial record struct L05_43_record_struct_double : IL05Tag, IValue<double> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os44 { public readonly partial record struct L05_44_record_struct_double : IL05Tag, IValue<double> { string IL05Tag.Tag => "t"; } } +public partial class L05Ot45 { public partial class Inner { public readonly partial record struct L05_45_record_struct_double : IL05Tag, IValue<double> { string IL05Tag.Tag => "t"; } } } +public partial class L05Ot46 { public partial class Inner { public readonly partial record struct L05_46_record_struct_double : IValue<double> { } } } +public partial class L05Oc47 { public readonly partial record struct L05_47_record_struct_enum : IL05Tag, IValue<PEnum> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os48 { public readonly partial record struct L05_48_record_struct_enum : IL05Tag, IValue<PEnum> { string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_49_record_struct_flags_enum : IL05Tag, IValue<PFlags> { string IL05Tag.Tag => "t"; } +public readonly partial record struct L05_50_record_struct_flags_enum : IValue<PFlags> { } +public partial struct L05Os51 { public readonly partial record struct L05_51_record_struct_int : IL05Tag, IValue<int> { string IL05Tag.Tag => "t"; } } +public partial class L05Ot52 { public partial class Inner { public readonly partial record struct L05_52_record_struct_int : IValue<int> { } } } +public partial class L05Ot53 { public partial class Inner { public readonly partial record struct L05_53_record_struct_long : IValue<long> { } } } +public readonly partial record struct L05_54_record_struct_long : IValue<long> { } +public partial class L05Ot55 { public partial class Inner { public readonly partial record struct L05_55_record_struct_own_record_class : IL05Tag, IValue<PRec> { string IL05Tag.Tag => "t"; } } } +public partial struct L05Os56 { public readonly partial record struct L05_56_record_struct_string : IL05Tag, IValue<string> { string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_57_record_struct_string : IL05Tag, IValue<string> { string IL05Tag.Tag => "t"; } +public partial class L05Oc58 { public readonly partial record struct L05_58_record_struct_string : IValue<string> { } } +public partial class L05Oc59 { public readonly partial record struct L05_59_record_struct_ulong : IL05Tag, IValue<ulong> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os60 { public readonly partial record struct L05_60_record_struct_ulong : IL05Tag, IValue<ulong> { string IL05Tag.Tag => "t"; } } +public partial struct L05Os61 { public sealed partial record L05_61_sealed_record_class_DateOnly : L05B2b, IValue<DateOnly> { } } +public sealed partial record L05_62_sealed_record_class_DateOnly : L05B3c, IValue<DateOnly> { } +public partial struct L05Os63 { public sealed partial record L05_63_sealed_record_class_DateOnly : IValue<DateOnly> { } } +public sealed partial record L05_64_sealed_record_class_DateOnly : IValue<DateOnly> { } +public sealed partial record L05_65_sealed_record_class_DateTime : L05B1, IValue<DateTime> { } +public partial class L05Oc66 { public sealed partial record L05_66_sealed_record_class_DateTime : L05B2b, IValue<DateTime> { } } +public partial struct L05Os67 { public sealed partial record L05_67_sealed_record_class_DateTime : L05B3c, IValue<DateTime> { } } +public partial class L05Ot68 { public partial class Inner { public sealed partial record L05_68_sealed_record_class_DateTime : L05B3c, IValue<DateTime> { } } } +public sealed partial record L05_69_sealed_record_class_DateTime : L05B3c, IValue<DateTime> { } +public partial struct L05Os70 { public sealed partial record L05_70_sealed_record_class_DateTime : L05E1, IValue<DateTime> { } } +public partial class L05Oc71 { public sealed partial record L05_71_sealed_record_class_DateTimeOffset : L05B1, IValue<DateTimeOffset> { } } +public sealed partial record L05_72_sealed_record_class_DateTimeOffset : L05B2b, IValue<DateTimeOffset> { } +public partial class L05Oc73 { public sealed partial record L05_73_sealed_record_class_DateTimeOffset : L05B3c, IValue<DateTimeOffset> { } } +public partial class L05Ot74 { public partial class Inner { public sealed partial record L05_74_sealed_record_class_DateTimeOffset : L05B3c, IValue<DateTimeOffset> { } } } +public sealed partial record L05_75_sealed_record_class_DateTimeOffset : L05B3c, IValue<DateTimeOffset> { } +public partial class L05Oc76 { public sealed partial record L05_76_sealed_record_class_DateTimeOffset : L05E1, IValue<DateTimeOffset> { } } +public sealed partial record L05_77_sealed_record_class_DateTimeOffset : L05E1, IValue<DateTimeOffset> { } +public partial class L05Oc78 { public sealed partial record L05_78_sealed_record_class_Guid : L05B1, IValue<Guid> { } } +public partial struct L05Os79 { public sealed partial record L05_79_sealed_record_class_Guid : L05B1, IValue<Guid> { } } +public sealed partial record L05_80_sealed_record_class_Guid : L05B2b, IValue<Guid> { } +public sealed partial record L05_81_sealed_record_class_Guid : L05B3c, IValue<Guid> { } +public partial class L05Oc82 { public sealed partial record L05_82_sealed_record_class_Guid : L05E1, IValue<Guid> { } } +public partial class L05Ot83 { public partial class Inner { public sealed partial record L05_83_sealed_record_class_Guid : L05E1, IValue<Guid> { } } } +public partial class L05Ot84 { public partial class Inner { public sealed partial record L05_84_sealed_record_class_NodaTime_Instant : L05B1, IValue<global::NodaTime.Instant> { } } } +public partial struct L05Os85 { public sealed partial record L05_85_sealed_record_class_NodaTime_Instant : L05E1, IValue<global::NodaTime.Instant> { } } +public partial struct L05Os86 { public sealed partial record L05_86_sealed_record_class_NodaTime_Instant : IValue<global::NodaTime.Instant> { } } +public partial struct L05Os87 { public sealed partial record L05_87_sealed_record_class_TimeOnly : L05B3c, IValue<TimeOnly> { } } +public partial class L05Ot88 { public partial class Inner { public sealed partial record L05_88_sealed_record_class_TimeOnly : L05B3c, IValue<TimeOnly> { } } } +public partial class L05Oc89 { public sealed partial record L05_89_sealed_record_class_TimeSpan : L05B1, IValue<TimeSpan> { } } +public partial class L05Ot90 { public partial class Inner { public sealed partial record L05_90_sealed_record_class_TimeSpan : L05B1, IValue<TimeSpan> { } } } +public sealed partial record L05_91_sealed_record_class_TimeSpan : L05B2b, IValue<TimeSpan> { } +public sealed partial record L05_92_sealed_record_class_TimeSpan : L05B3c, IValue<TimeSpan> { } +public partial struct L05Os93 { public sealed partial record L05_93_sealed_record_class_TimeSpan : IL05Tag, IValue<TimeSpan> { string IL05Tag.Tag => "t"; } } +public partial class L05Oc94 { public sealed partial record L05_94_sealed_record_class_TimeSpan : IValue<TimeSpan> { } } +public partial class L05Oc95 { public sealed partial record L05_95_sealed_record_class_Uri : L05B1, IValue<Uri> { } } +public partial struct L05Os96 { public sealed partial record L05_96_sealed_record_class_Uri : L05B2b, IValue<Uri> { } } +public partial struct L05Os97 { public sealed partial record L05_97_sealed_record_class_Uri : L05B3c, IValue<Uri> { } } +public partial class L05Ot98 { public partial class Inner { public sealed partial record L05_98_sealed_record_class_Uri : L05E1, IValue<Uri> { } } } +public partial class L05Oc99 { public sealed partial record L05_99_sealed_record_class_bool : L05B2b, IValue<bool> { } } +public partial class L05Ot100 { public partial class Inner { public sealed partial record L05_100_sealed_record_class_bool : L05B2b, IValue<bool> { } } } +public partial struct L05Os101 { public sealed partial record L05_101_sealed_record_class_bool : L05B3c, IValue<bool> { } } +public partial class L05Ot102 { public partial class Inner { public sealed partial record L05_102_sealed_record_class_bool : L05E1, IValue<bool> { } } } +public partial class L05Ot103 { public partial class Inner { public sealed partial record L05_103_sealed_record_class_bool : IL05Tag, IValue<bool> { string IL05Tag.Tag => "t"; } } } +public partial class L05Ot104 { public partial class Inner { public sealed partial record L05_104_sealed_record_class_byte : L05B1, IValue<byte> { } } } +public partial struct L05Os105 { public sealed partial record L05_105_sealed_record_class_byte : L05B2b, IValue<byte> { } } +public partial class L05Oc106 { public sealed partial record L05_106_sealed_record_class_byte : L05B3c, IValue<byte> { } } +public sealed partial record L05_107_sealed_record_class_byte : L05B3c, IValue<byte> { } +public partial class L05Ot108 { public partial class Inner { public sealed partial record L05_108_sealed_record_class_byte : IL05Tag, IValue<byte> { string IL05Tag.Tag => "t"; } } } +public sealed partial record L05_109_sealed_record_class_byte : IL05Tag, IValue<byte> { string IL05Tag.Tag => "t"; } +public sealed partial record L05_110_sealed_record_class_byte : IValue<byte> { } +public partial class L05Ot111 { public partial class Inner { public sealed partial record L05_111_sealed_record_class_char : L05B1, IValue<char> { } } } +public sealed partial record L05_112_sealed_record_class_char : L05B2b, IValue<char> { } +public sealed partial record L05_113_sealed_record_class_char : L05E1, IValue<char> { } +public partial class L05Ot114 { public partial class Inner { public sealed partial record L05_114_sealed_record_class_char : IL05Tag, IValue<char> { string IL05Tag.Tag => "t"; } } } +public partial class L05Oc115 { public sealed partial record L05_115_sealed_record_class_decimal : L05B1, IValue<decimal> { } } +public partial struct L05Os116 { public sealed partial record L05_116_sealed_record_class_decimal : L05B3c, IValue<decimal> { } } +public partial class L05Oc117 { public sealed partial record L05_117_sealed_record_class_decimal : L05E1, IValue<decimal> { } } +public sealed partial record L05_118_sealed_record_class_decimal : L05E1, IValue<decimal> { } +public partial class L05Ot119 { public partial class Inner { public sealed partial record L05_119_sealed_record_class_double : L05B1, IValue<double> { } } } +public sealed partial record L05_120_sealed_record_class_double : L05B2b, IValue<double> { } +public partial class L05Oc121 { public sealed partial record L05_121_sealed_record_class_double : L05B3c, IValue<double> { } } +public partial class L05Oc122 { public sealed partial record L05_122_sealed_record_class_double : L05E1, IValue<double> { } } +public partial struct L05Os123 { public sealed partial record L05_123_sealed_record_class_enum : L05B1, IValue<PEnum> { } } +public partial class L05Ot124 { public partial class Inner { public sealed partial record L05_124_sealed_record_class_enum : L05B2b, IValue<PEnum> { } } } +public partial class L05Ot125 { public partial class Inner { public sealed partial record L05_125_sealed_record_class_enum : L05B3c, IValue<PEnum> { } } } +public partial class L05Ot126 { public partial class Inner { public sealed partial record L05_126_sealed_record_class_enum : L05E1, IValue<PEnum> { } } } +public partial struct L05Os127 { public sealed partial record L05_127_sealed_record_class_enum : IL05Tag, IValue<PEnum> { string IL05Tag.Tag => "t"; } } +public partial class L05Oc128 { public sealed partial record L05_128_sealed_record_class_enum : IValue<PEnum> { } } +public partial class L05Oc129 { public sealed partial record L05_129_sealed_record_class_flags_enum : L05B1, IValue<PFlags> { } } +public partial class L05Ot130 { public partial class Inner { public sealed partial record L05_130_sealed_record_class_flags_enum : L05B1, IValue<PFlags> { } } } +public sealed partial record L05_131_sealed_record_class_flags_enum : L05B2b, IValue<PFlags> { } +public sealed partial record L05_132_sealed_record_class_flags_enum : L05B3c, IValue<PFlags> { } +public partial class L05Ot133 { public partial class Inner { public sealed partial record L05_133_sealed_record_class_flags_enum : L05E1, IValue<PFlags> { } } } +public partial struct L05Os134 { public sealed partial record L05_134_sealed_record_class_flags_enum : IL05Tag, IValue<PFlags> { string IL05Tag.Tag => "t"; } } +public sealed partial record L05_135_sealed_record_class_int : L05B2b, IValue<int> { } +public partial struct L05Os136 { public sealed partial record L05_136_sealed_record_class_int : L05E1, IValue<int> { } } +public partial class L05Ot137 { public partial class Inner { public sealed partial record L05_137_sealed_record_class_long : L05B2b, IValue<long> { } } } +public partial class L05Ot138 { public partial class Inner { public sealed partial record L05_138_sealed_record_class_long : L05B3c, IValue<long> { } } } +public partial struct L05Os139 { public sealed partial record L05_139_sealed_record_class_long : L05E1, IValue<long> { } } +public partial class L05Ot140 { public partial class Inner { public sealed partial record L05_140_sealed_record_class_own_record_class : L05B2b, IValue<PRec> { } } } +public partial struct L05Os141 { public sealed partial record L05_141_sealed_record_class_own_record_class : L05E1, IValue<PRec> { } } +public partial class L05Ot142 { public partial class Inner { public sealed partial record L05_142_sealed_record_class_own_record_class : L05E1, IValue<PRec> { } } } +public partial struct L05Os143 { public sealed partial record L05_143_sealed_record_class_string : L05B1, IValue<string> { } } +public sealed partial record L05_144_sealed_record_class_string : L05B3c, IValue<string> { } +public partial class L05Ot145 { public partial class Inner { public sealed partial record L05_145_sealed_record_class_string : L05E1, IValue<string> { } } } +public partial class L05Ot146 { public partial class Inner { public sealed partial record L05_146_sealed_record_class_ulong : L05B3c, IValue<ulong> { } } } +public partial class L05Ot147 { public partial class Inner { public sealed partial record L05_147_sealed_record_class_ulong : L05E1, IValue<ulong> { } } } +public sealed partial record L05_148_sealed_record_class_ulong : L05E1, IValue<ulong> { } +public partial struct L05Os149 { public sealed partial record L05_149_validated_class_DateOnly : L05B1, IValidatedValue<L05_149_validated_class_DateOnly, DateOnly, ShapeFault> { public static Result<L05_149_validated_class_DateOnly, ShapeFault> Create(DateOnly value) => new L05_149_validated_class_DateOnly(value); } } +public sealed partial record L05_150_validated_class_DateOnly : L05E1, IValidatedValue<L05_150_validated_class_DateOnly, DateOnly, ShapeFault> { public static Result<L05_150_validated_class_DateOnly, ShapeFault> Create(DateOnly value) => new L05_150_validated_class_DateOnly(value); } +public sealed partial record L05_151_validated_class_DateOnly : IL05Tag, IValidatedValue<L05_151_validated_class_DateOnly, DateOnly, ShapeFault> { public static Result<L05_151_validated_class_DateOnly, ShapeFault> Create(DateOnly value) => new L05_151_validated_class_DateOnly(value); string IL05Tag.Tag => "t"; } +public partial class L05Ot152 { public partial class Inner { public sealed partial record L05_152_validated_class_DateOnly : IValidatedValue<L05_152_validated_class_DateOnly, DateOnly, ShapeFault> { public static Result<L05_152_validated_class_DateOnly, ShapeFault> Create(DateOnly value) => new L05_152_validated_class_DateOnly(value); } } } +public partial struct L05Os153 { public sealed partial record L05_153_validated_class_DateTime : L05B1, IValidatedValue<L05_153_validated_class_DateTime, DateTime, ShapeFault> { public static Result<L05_153_validated_class_DateTime, ShapeFault> Create(DateTime value) => new L05_153_validated_class_DateTime(value); } } +public partial class L05Ot154 { public partial class Inner { public sealed partial record L05_154_validated_class_DateTime : L05B1, IValidatedValue<L05_154_validated_class_DateTime, DateTime, ShapeFault> { public static Result<L05_154_validated_class_DateTime, ShapeFault> Create(DateTime value) => new L05_154_validated_class_DateTime(value); } } } +public sealed partial record L05_155_validated_class_DateTime : L05B2b, IValidatedValue<L05_155_validated_class_DateTime, DateTime, ShapeFault> { public static Result<L05_155_validated_class_DateTime, ShapeFault> Create(DateTime value) => new L05_155_validated_class_DateTime(value); } +public partial class L05Oc156 { public sealed partial record L05_156_validated_class_DateTime : L05B3c, IValidatedValue<L05_156_validated_class_DateTime, DateTime, ShapeFault> { public static Result<L05_156_validated_class_DateTime, ShapeFault> Create(DateTime value) => new L05_156_validated_class_DateTime(value); } } +public partial struct L05Os157 { public sealed partial record L05_157_validated_class_DateTime : L05E1, IValidatedValue<L05_157_validated_class_DateTime, DateTime, ShapeFault> { public static Result<L05_157_validated_class_DateTime, ShapeFault> Create(DateTime value) => new L05_157_validated_class_DateTime(value); } } +public partial class L05Oc158 { public sealed partial record L05_158_validated_class_DateTimeOffset : IL05Tag, IValidatedValue<L05_158_validated_class_DateTimeOffset, DateTimeOffset, ShapeFault> { public static Result<L05_158_validated_class_DateTimeOffset, ShapeFault> Create(DateTimeOffset value) => new L05_158_validated_class_DateTimeOffset(value); string IL05Tag.Tag => "t"; } } +public partial class L05Oc159 { public sealed partial record L05_159_validated_class_Guid : L05B2b, IValidatedValue<L05_159_validated_class_Guid, Guid, ShapeFault> { public static Result<L05_159_validated_class_Guid, ShapeFault> Create(Guid value) => new L05_159_validated_class_Guid(value); } } +public partial class L05Ot160 { public partial class Inner { public sealed partial record L05_160_validated_class_Guid : L05B2b, IValidatedValue<L05_160_validated_class_Guid, Guid, ShapeFault> { public static Result<L05_160_validated_class_Guid, ShapeFault> Create(Guid value) => new L05_160_validated_class_Guid(value); } } } +public partial class L05Oc161 { public sealed partial record L05_161_validated_class_Guid : IL05Tag, IValidatedValue<L05_161_validated_class_Guid, Guid, ShapeFault> { public static Result<L05_161_validated_class_Guid, ShapeFault> Create(Guid value) => new L05_161_validated_class_Guid(value); string IL05Tag.Tag => "t"; } } +public partial class L05Ot162 { public partial class Inner { public sealed partial record L05_162_validated_class_NodaTime_Instant : L05B2b, IValidatedValue<L05_162_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_162_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_162_validated_class_NodaTime_Instant(value); } } } +public partial class L05Oc163 { public sealed partial record L05_163_validated_class_NodaTime_Instant : L05B3c, IValidatedValue<L05_163_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_163_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_163_validated_class_NodaTime_Instant(value); } } +public partial struct L05Os164 { public sealed partial record L05_164_validated_class_NodaTime_Instant : L05B3c, IValidatedValue<L05_164_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_164_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_164_validated_class_NodaTime_Instant(value); } } +public partial class L05Ot165 { public partial class Inner { public sealed partial record L05_165_validated_class_NodaTime_Instant : L05B3c, IValidatedValue<L05_165_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_165_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_165_validated_class_NodaTime_Instant(value); } } } +public partial class L05Oc166 { public sealed partial record L05_166_validated_class_NodaTime_Instant : L05E1, IValidatedValue<L05_166_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_166_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_166_validated_class_NodaTime_Instant(value); } } +public sealed partial record L05_167_validated_class_NodaTime_Instant : L05E1, IValidatedValue<L05_167_validated_class_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_167_validated_class_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_167_validated_class_NodaTime_Instant(value); } +public partial class L05Oc168 { public sealed partial record L05_168_validated_class_TimeOnly : L05B1, IValidatedValue<L05_168_validated_class_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_168_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_168_validated_class_TimeOnly(value); } } +public partial struct L05Os169 { public sealed partial record L05_169_validated_class_TimeOnly : L05B1, IValidatedValue<L05_169_validated_class_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_169_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_169_validated_class_TimeOnly(value); } } +public sealed partial record L05_170_validated_class_TimeOnly : L05B2b, IValidatedValue<L05_170_validated_class_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_170_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_170_validated_class_TimeOnly(value); } +public partial class L05Oc171 { public sealed partial record L05_171_validated_class_TimeOnly : L05E1, IValidatedValue<L05_171_validated_class_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_171_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_171_validated_class_TimeOnly(value); } } +public sealed partial record L05_172_validated_class_TimeOnly : IValidatedValue<L05_172_validated_class_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_172_validated_class_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_172_validated_class_TimeOnly(value); } +public sealed partial record L05_173_validated_class_TimeSpan : L05B1, IValidatedValue<L05_173_validated_class_TimeSpan, TimeSpan, ShapeFault> { public static Result<L05_173_validated_class_TimeSpan, ShapeFault> Create(TimeSpan value) => new L05_173_validated_class_TimeSpan(value); } +public partial class L05Oc174 { public sealed partial record L05_174_validated_class_TimeSpan : L05B2b, IValidatedValue<L05_174_validated_class_TimeSpan, TimeSpan, ShapeFault> { public static Result<L05_174_validated_class_TimeSpan, ShapeFault> Create(TimeSpan value) => new L05_174_validated_class_TimeSpan(value); } } +public partial struct L05Os175 { public sealed partial record L05_175_validated_class_TimeSpan : L05B2b, IValidatedValue<L05_175_validated_class_TimeSpan, TimeSpan, ShapeFault> { public static Result<L05_175_validated_class_TimeSpan, ShapeFault> Create(TimeSpan value) => new L05_175_validated_class_TimeSpan(value); } } +public partial class L05Ot176 { public partial class Inner { public sealed partial record L05_176_validated_class_TimeSpan : L05E1, IValidatedValue<L05_176_validated_class_TimeSpan, TimeSpan, ShapeFault> { public static Result<L05_176_validated_class_TimeSpan, ShapeFault> Create(TimeSpan value) => new L05_176_validated_class_TimeSpan(value); } } } +public partial struct L05Os177 { public sealed partial record L05_177_validated_class_Uri : L05B1, IValidatedValue<L05_177_validated_class_Uri, Uri, ShapeFault> { public static Result<L05_177_validated_class_Uri, ShapeFault> Create(Uri value) => new L05_177_validated_class_Uri(value); } } +public partial class L05Ot178 { public partial class Inner { public sealed partial record L05_178_validated_class_Uri : L05B3c, IValidatedValue<L05_178_validated_class_Uri, Uri, ShapeFault> { public static Result<L05_178_validated_class_Uri, ShapeFault> Create(Uri value) => new L05_178_validated_class_Uri(value); } } } +public sealed partial record L05_179_validated_class_Uri : IL05Tag, IValidatedValue<L05_179_validated_class_Uri, Uri, ShapeFault> { public static Result<L05_179_validated_class_Uri, ShapeFault> Create(Uri value) => new L05_179_validated_class_Uri(value); string IL05Tag.Tag => "t"; } +public partial class L05Oc180 { public sealed partial record L05_180_validated_class_Uri : IValidatedValue<L05_180_validated_class_Uri, Uri, ShapeFault> { public static Result<L05_180_validated_class_Uri, ShapeFault> Create(Uri value) => new L05_180_validated_class_Uri(value); } } +public partial struct L05Os181 { public sealed partial record L05_181_validated_class_bool : L05B1, IValidatedValue<L05_181_validated_class_bool, bool, ShapeFault> { public static Result<L05_181_validated_class_bool, ShapeFault> Create(bool value) => new L05_181_validated_class_bool(value); } } +public partial class L05Oc182 { public sealed partial record L05_182_validated_class_bool : L05E1, IValidatedValue<L05_182_validated_class_bool, bool, ShapeFault> { public static Result<L05_182_validated_class_bool, ShapeFault> Create(bool value) => new L05_182_validated_class_bool(value); } } +public partial struct L05Os183 { public sealed partial record L05_183_validated_class_bool : IL05Tag, IValidatedValue<L05_183_validated_class_bool, bool, ShapeFault> { public static Result<L05_183_validated_class_bool, ShapeFault> Create(bool value) => new L05_183_validated_class_bool(value); string IL05Tag.Tag => "t"; } } +public partial struct L05Os184 { public sealed partial record L05_184_validated_class_byte : L05B2b, IValidatedValue<L05_184_validated_class_byte, byte, ShapeFault> { public static Result<L05_184_validated_class_byte, ShapeFault> Create(byte value) => new L05_184_validated_class_byte(value); } } +public partial class L05Oc185 { public sealed partial record L05_185_validated_class_byte : L05E1, IValidatedValue<L05_185_validated_class_byte, byte, ShapeFault> { public static Result<L05_185_validated_class_byte, ShapeFault> Create(byte value) => new L05_185_validated_class_byte(value); } } +public partial class L05Ot186 { public partial class Inner { public sealed partial record L05_186_validated_class_byte : L05E1, IValidatedValue<L05_186_validated_class_byte, byte, ShapeFault> { public static Result<L05_186_validated_class_byte, ShapeFault> Create(byte value) => new L05_186_validated_class_byte(value); } } } +public partial struct L05Os187 { public sealed partial record L05_187_validated_class_byte : IL05Tag, IValidatedValue<L05_187_validated_class_byte, byte, ShapeFault> { public static Result<L05_187_validated_class_byte, ShapeFault> Create(byte value) => new L05_187_validated_class_byte(value); string IL05Tag.Tag => "t"; } } +public partial class L05Oc188 { public sealed partial record L05_188_validated_class_char : L05B2b, IValidatedValue<L05_188_validated_class_char, char, ShapeFault> { public static Result<L05_188_validated_class_char, ShapeFault> Create(char value) => new L05_188_validated_class_char(value); } } +public partial class L05Oc189 { public sealed partial record L05_189_validated_class_char : L05B3c, IValidatedValue<L05_189_validated_class_char, char, ShapeFault> { public static Result<L05_189_validated_class_char, ShapeFault> Create(char value) => new L05_189_validated_class_char(value); } } +public partial class L05Ot190 { public partial class Inner { public sealed partial record L05_190_validated_class_char : L05B3c, IValidatedValue<L05_190_validated_class_char, char, ShapeFault> { public static Result<L05_190_validated_class_char, ShapeFault> Create(char value) => new L05_190_validated_class_char(value); } } } +public partial class L05Ot191 { public partial class Inner { public sealed partial record L05_191_validated_class_char : L05E1, IValidatedValue<L05_191_validated_class_char, char, ShapeFault> { public static Result<L05_191_validated_class_char, ShapeFault> Create(char value) => new L05_191_validated_class_char(value); } } } +public sealed partial record L05_192_validated_class_char : L05E1, IValidatedValue<L05_192_validated_class_char, char, ShapeFault> { public static Result<L05_192_validated_class_char, ShapeFault> Create(char value) => new L05_192_validated_class_char(value); } +public sealed partial record L05_193_validated_class_decimal : L05B1, IValidatedValue<L05_193_validated_class_decimal, decimal, ShapeFault> { public static Result<L05_193_validated_class_decimal, ShapeFault> Create(decimal value) => new L05_193_validated_class_decimal(value); } +public partial class L05Ot194 { public partial class Inner { public sealed partial record L05_194_validated_class_decimal : L05B2b, IValidatedValue<L05_194_validated_class_decimal, decimal, ShapeFault> { public static Result<L05_194_validated_class_decimal, ShapeFault> Create(decimal value) => new L05_194_validated_class_decimal(value); } } } +public sealed partial record L05_195_validated_class_decimal : L05B2b, IValidatedValue<L05_195_validated_class_decimal, decimal, ShapeFault> { public static Result<L05_195_validated_class_decimal, ShapeFault> Create(decimal value) => new L05_195_validated_class_decimal(value); } +public partial struct L05Os196 { public sealed partial record L05_196_validated_class_decimal : IValidatedValue<L05_196_validated_class_decimal, decimal, ShapeFault> { public static Result<L05_196_validated_class_decimal, ShapeFault> Create(decimal value) => new L05_196_validated_class_decimal(value); } } +public sealed partial record L05_197_validated_class_decimal : IValidatedValue<L05_197_validated_class_decimal, decimal, ShapeFault> { public static Result<L05_197_validated_class_decimal, ShapeFault> Create(decimal value) => new L05_197_validated_class_decimal(value); } +public sealed partial record L05_198_validated_class_double : L05B2b, IValidatedValue<L05_198_validated_class_double, double, ShapeFault> { public static Result<L05_198_validated_class_double, ShapeFault> Create(double value) => new L05_198_validated_class_double(value); } +public partial struct L05Os199 { public sealed partial record L05_199_validated_class_double : L05B3c, IValidatedValue<L05_199_validated_class_double, double, ShapeFault> { public static Result<L05_199_validated_class_double, ShapeFault> Create(double value) => new L05_199_validated_class_double(value); } } +public partial struct L05Os200 { public sealed partial record L05_200_validated_class_double : IL05Tag, IValidatedValue<L05_200_validated_class_double, double, ShapeFault> { public static Result<L05_200_validated_class_double, ShapeFault> Create(double value) => new L05_200_validated_class_double(value); string IL05Tag.Tag => "t"; } } +public partial class L05Ot201 { public partial class Inner { public sealed partial record L05_201_validated_class_enum : L05B1, IValidatedValue<L05_201_validated_class_enum, PEnum, ShapeFault> { public static Result<L05_201_validated_class_enum, ShapeFault> Create(PEnum value) => new L05_201_validated_class_enum(value); } } } +public sealed partial record L05_202_validated_class_enum : L05B2b, IValidatedValue<L05_202_validated_class_enum, PEnum, ShapeFault> { public static Result<L05_202_validated_class_enum, ShapeFault> Create(PEnum value) => new L05_202_validated_class_enum(value); } +public partial class L05Oc203 { public sealed partial record L05_203_validated_class_flags_enum : L05E1, IValidatedValue<L05_203_validated_class_flags_enum, PFlags, ShapeFault> { public static Result<L05_203_validated_class_flags_enum, ShapeFault> Create(PFlags value) => new L05_203_validated_class_flags_enum(value); } } +public partial class L05Ot204 { public partial class Inner { public sealed partial record L05_204_validated_class_flags_enum : IL05Tag, IValidatedValue<L05_204_validated_class_flags_enum, PFlags, ShapeFault> { public static Result<L05_204_validated_class_flags_enum, ShapeFault> Create(PFlags value) => new L05_204_validated_class_flags_enum(value); string IL05Tag.Tag => "t"; } } } +public partial struct L05Os205 { public sealed partial record L05_205_validated_class_int : L05B1, IValidatedValue<L05_205_validated_class_int, int, ShapeFault> { public static Result<L05_205_validated_class_int, ShapeFault> Create(int value) => new L05_205_validated_class_int(value); } } +public partial class L05Oc206 { public sealed partial record L05_206_validated_class_int : L05B3c, IValidatedValue<L05_206_validated_class_int, int, ShapeFault> { public static Result<L05_206_validated_class_int, ShapeFault> Create(int value) => new L05_206_validated_class_int(value); } } +public partial class L05Oc207 { public sealed partial record L05_207_validated_class_int : IL05Tag, IValidatedValue<L05_207_validated_class_int, int, ShapeFault> { public static Result<L05_207_validated_class_int, ShapeFault> Create(int value) => new L05_207_validated_class_int(value); string IL05Tag.Tag => "t"; } } +public partial class L05Oc208 { public sealed partial record L05_208_validated_class_long : L05B1, IValidatedValue<L05_208_validated_class_long, long, ShapeFault> { public static Result<L05_208_validated_class_long, ShapeFault> Create(long value) => new L05_208_validated_class_long(value); } } +public partial class L05Ot209 { public partial class Inner { public sealed partial record L05_209_validated_class_long : L05B2b, IValidatedValue<L05_209_validated_class_long, long, ShapeFault> { public static Result<L05_209_validated_class_long, ShapeFault> Create(long value) => new L05_209_validated_class_long(value); } } } +public partial class L05Oc210 { public sealed partial record L05_210_validated_class_long : L05E1, IValidatedValue<L05_210_validated_class_long, long, ShapeFault> { public static Result<L05_210_validated_class_long, ShapeFault> Create(long value) => new L05_210_validated_class_long(value); } } +public partial struct L05Os211 { public sealed partial record L05_211_validated_class_long : L05E1, IValidatedValue<L05_211_validated_class_long, long, ShapeFault> { public static Result<L05_211_validated_class_long, ShapeFault> Create(long value) => new L05_211_validated_class_long(value); } } +public partial class L05Ot212 { public partial class Inner { public sealed partial record L05_212_validated_class_long : IL05Tag, IValidatedValue<L05_212_validated_class_long, long, ShapeFault> { public static Result<L05_212_validated_class_long, ShapeFault> Create(long value) => new L05_212_validated_class_long(value); string IL05Tag.Tag => "t"; } } } +public partial struct L05Os213 { public sealed partial record L05_213_validated_class_own_record_class : L05B1, IValidatedValue<L05_213_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_213_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_213_validated_class_own_record_class(value); } } +public partial class L05Ot214 { public partial class Inner { public sealed partial record L05_214_validated_class_own_record_class : L05B1, IValidatedValue<L05_214_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_214_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_214_validated_class_own_record_class(value); } } } +public sealed partial record L05_215_validated_class_own_record_class : L05B2b, IValidatedValue<L05_215_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_215_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_215_validated_class_own_record_class(value); } +public partial struct L05Os216 { public sealed partial record L05_216_validated_class_own_record_class : L05B3c, IValidatedValue<L05_216_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_216_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_216_validated_class_own_record_class(value); } } +public partial class L05Ot217 { public partial class Inner { public sealed partial record L05_217_validated_class_own_record_class : L05B3c, IValidatedValue<L05_217_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_217_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_217_validated_class_own_record_class(value); } } } +public partial struct L05Os218 { public sealed partial record L05_218_validated_class_own_record_class : L05E1, IValidatedValue<L05_218_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_218_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_218_validated_class_own_record_class(value); } } +public partial class L05Oc219 { public sealed partial record L05_219_validated_class_own_record_class : IValidatedValue<L05_219_validated_class_own_record_class, PRec, ShapeFault> { public static Result<L05_219_validated_class_own_record_class, ShapeFault> Create(PRec value) => new L05_219_validated_class_own_record_class(value); } } +public partial struct L05Os220 { public sealed partial record L05_220_validated_class_string : L05B1, IValidatedValue<L05_220_validated_class_string, string, ShapeFault> { public static Result<L05_220_validated_class_string, ShapeFault> Create(string value) => new L05_220_validated_class_string(value); } } +public partial class L05Ot221 { public partial class Inner { public sealed partial record L05_221_validated_class_string : L05B2b, IValidatedValue<L05_221_validated_class_string, string, ShapeFault> { public static Result<L05_221_validated_class_string, ShapeFault> Create(string value) => new L05_221_validated_class_string(value); } } } +public partial class L05Oc222 { public sealed partial record L05_222_validated_class_string : L05B3c, IValidatedValue<L05_222_validated_class_string, string, ShapeFault> { public static Result<L05_222_validated_class_string, ShapeFault> Create(string value) => new L05_222_validated_class_string(value); } } +public sealed partial record L05_223_validated_class_string : IL05Tag, IValidatedValue<L05_223_validated_class_string, string, ShapeFault> { public static Result<L05_223_validated_class_string, ShapeFault> Create(string value) => new L05_223_validated_class_string(value); string IL05Tag.Tag => "t"; } +public partial struct L05Os224 { public sealed partial record L05_224_validated_class_ulong : L05B1, IValidatedValue<L05_224_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_224_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_224_validated_class_ulong(value); } } +public sealed partial record L05_225_validated_class_ulong : L05B1, IValidatedValue<L05_225_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_225_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_225_validated_class_ulong(value); } +public sealed partial record L05_226_validated_class_ulong : L05B2b, IValidatedValue<L05_226_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_226_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_226_validated_class_ulong(value); } +public partial class L05Oc227 { public sealed partial record L05_227_validated_class_ulong : L05B3c, IValidatedValue<L05_227_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_227_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_227_validated_class_ulong(value); } } +public partial class L05Oc228 { public sealed partial record L05_228_validated_class_ulong : IL05Tag, IValidatedValue<L05_228_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_228_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_228_validated_class_ulong(value); string IL05Tag.Tag => "t"; } } +public partial struct L05Os229 { public sealed partial record L05_229_validated_class_ulong : IL05Tag, IValidatedValue<L05_229_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_229_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_229_validated_class_ulong(value); string IL05Tag.Tag => "t"; } } +public partial struct L05Os230 { public sealed partial record L05_230_validated_class_ulong : IValidatedValue<L05_230_validated_class_ulong, ulong, ShapeFault> { public static Result<L05_230_validated_class_ulong, ShapeFault> Create(ulong value) => new L05_230_validated_class_ulong(value); } } +public partial class L05Ot231 { public partial class Inner { public readonly partial record struct L05_231_validated_struct_DateOnly : IL05Tag, IValidatedValue<L05_231_validated_struct_DateOnly, DateOnly, ShapeFault> { public static Result<L05_231_validated_struct_DateOnly, ShapeFault> Create(DateOnly value) => new L05_231_validated_struct_DateOnly(value); string IL05Tag.Tag => "t"; } } } +public readonly partial record struct L05_232_validated_struct_DateOnly : IValidatedValue<L05_232_validated_struct_DateOnly, DateOnly, ShapeFault> { public static Result<L05_232_validated_struct_DateOnly, ShapeFault> Create(DateOnly value) => new L05_232_validated_struct_DateOnly(value); } +public partial class L05Ot233 { public partial class Inner { public readonly partial record struct L05_233_validated_struct_DateTime : IL05Tag, IValidatedValue<L05_233_validated_struct_DateTime, DateTime, ShapeFault> { public static Result<L05_233_validated_struct_DateTime, ShapeFault> Create(DateTime value) => new L05_233_validated_struct_DateTime(value); string IL05Tag.Tag => "t"; } } } +public partial struct L05Os234 { public readonly partial record struct L05_234_validated_struct_DateTimeOffset : IValidatedValue<L05_234_validated_struct_DateTimeOffset, DateTimeOffset, ShapeFault> { public static Result<L05_234_validated_struct_DateTimeOffset, ShapeFault> Create(DateTimeOffset value) => new L05_234_validated_struct_DateTimeOffset(value); } } +public readonly partial record struct L05_235_validated_struct_DateTimeOffset : IValidatedValue<L05_235_validated_struct_DateTimeOffset, DateTimeOffset, ShapeFault> { public static Result<L05_235_validated_struct_DateTimeOffset, ShapeFault> Create(DateTimeOffset value) => new L05_235_validated_struct_DateTimeOffset(value); } +public partial class L05Oc236 { public readonly partial record struct L05_236_validated_struct_Guid : IValidatedValue<L05_236_validated_struct_Guid, Guid, ShapeFault> { public static Result<L05_236_validated_struct_Guid, ShapeFault> Create(Guid value) => new L05_236_validated_struct_Guid(value); } } +public partial struct L05Os237 { public readonly partial record struct L05_237_validated_struct_Guid : IValidatedValue<L05_237_validated_struct_Guid, Guid, ShapeFault> { public static Result<L05_237_validated_struct_Guid, ShapeFault> Create(Guid value) => new L05_237_validated_struct_Guid(value); } } +public readonly partial record struct L05_238_validated_struct_NodaTime_Instant : IValidatedValue<L05_238_validated_struct_NodaTime_Instant, global::NodaTime.Instant, ShapeFault> { public static Result<L05_238_validated_struct_NodaTime_Instant, ShapeFault> Create(global::NodaTime.Instant value) => new L05_238_validated_struct_NodaTime_Instant(value); } +public partial class L05Oc239 { public readonly partial record struct L05_239_validated_struct_TimeOnly : IL05Tag, IValidatedValue<L05_239_validated_struct_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_239_validated_struct_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_239_validated_struct_TimeOnly(value); string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_240_validated_struct_TimeOnly : IValidatedValue<L05_240_validated_struct_TimeOnly, TimeOnly, ShapeFault> { public static Result<L05_240_validated_struct_TimeOnly, ShapeFault> Create(TimeOnly value) => new L05_240_validated_struct_TimeOnly(value); } +public partial class L05Oc241 { public readonly partial record struct L05_241_validated_struct_TimeSpan : IValidatedValue<L05_241_validated_struct_TimeSpan, TimeSpan, ShapeFault> { public static Result<L05_241_validated_struct_TimeSpan, ShapeFault> Create(TimeSpan value) => new L05_241_validated_struct_TimeSpan(value); } } +public partial struct L05Os242 { public readonly partial record struct L05_242_validated_struct_Uri : IL05Tag, IValidatedValue<L05_242_validated_struct_Uri, Uri, ShapeFault> { public static Result<L05_242_validated_struct_Uri, ShapeFault> Create(Uri value) => new L05_242_validated_struct_Uri(value); string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_243_validated_struct_Uri : IL05Tag, IValidatedValue<L05_243_validated_struct_Uri, Uri, ShapeFault> { public static Result<L05_243_validated_struct_Uri, ShapeFault> Create(Uri value) => new L05_243_validated_struct_Uri(value); string IL05Tag.Tag => "t"; } +public partial class L05Oc244 { public readonly partial record struct L05_244_validated_struct_Uri : IValidatedValue<L05_244_validated_struct_Uri, Uri, ShapeFault> { public static Result<L05_244_validated_struct_Uri, ShapeFault> Create(Uri value) => new L05_244_validated_struct_Uri(value); } } +public partial class L05Oc245 { public readonly partial record struct L05_245_validated_struct_bool : IL05Tag, IValidatedValue<L05_245_validated_struct_bool, bool, ShapeFault> { public static Result<L05_245_validated_struct_bool, ShapeFault> Create(bool value) => new L05_245_validated_struct_bool(value); string IL05Tag.Tag => "t"; } } +public partial class L05Ot246 { public partial class Inner { public readonly partial record struct L05_246_validated_struct_bool : IValidatedValue<L05_246_validated_struct_bool, bool, ShapeFault> { public static Result<L05_246_validated_struct_bool, ShapeFault> Create(bool value) => new L05_246_validated_struct_bool(value); } } } +public readonly partial record struct L05_247_validated_struct_byte : IL05Tag, IValidatedValue<L05_247_validated_struct_byte, byte, ShapeFault> { public static Result<L05_247_validated_struct_byte, ShapeFault> Create(byte value) => new L05_247_validated_struct_byte(value); string IL05Tag.Tag => "t"; } +public partial class L05Oc248 { public readonly partial record struct L05_248_validated_struct_char : IL05Tag, IValidatedValue<L05_248_validated_struct_char, char, ShapeFault> { public static Result<L05_248_validated_struct_char, ShapeFault> Create(char value) => new L05_248_validated_struct_char(value); string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_249_validated_struct_decimal : IL05Tag, IValidatedValue<L05_249_validated_struct_decimal, decimal, ShapeFault> { public static Result<L05_249_validated_struct_decimal, ShapeFault> Create(decimal value) => new L05_249_validated_struct_decimal(value); string IL05Tag.Tag => "t"; } +public partial class L05Ot250 { public partial class Inner { public readonly partial record struct L05_250_validated_struct_double : IL05Tag, IValidatedValue<L05_250_validated_struct_double, double, ShapeFault> { public static Result<L05_250_validated_struct_double, ShapeFault> Create(double value) => new L05_250_validated_struct_double(value); string IL05Tag.Tag => "t"; } } } +public partial class L05Ot251 { public partial class Inner { public readonly partial record struct L05_251_validated_struct_double : IValidatedValue<L05_251_validated_struct_double, double, ShapeFault> { public static Result<L05_251_validated_struct_double, ShapeFault> Create(double value) => new L05_251_validated_struct_double(value); } } } +public partial class L05Oc252 { public readonly partial record struct L05_252_validated_struct_enum : IValidatedValue<L05_252_validated_struct_enum, PEnum, ShapeFault> { public static Result<L05_252_validated_struct_enum, ShapeFault> Create(PEnum value) => new L05_252_validated_struct_enum(value); } } +public partial struct L05Os253 { public readonly partial record struct L05_253_validated_struct_enum : IValidatedValue<L05_253_validated_struct_enum, PEnum, ShapeFault> { public static Result<L05_253_validated_struct_enum, ShapeFault> Create(PEnum value) => new L05_253_validated_struct_enum(value); } } +public partial class L05Ot254 { public partial class Inner { public readonly partial record struct L05_254_validated_struct_enum : IValidatedValue<L05_254_validated_struct_enum, PEnum, ShapeFault> { public static Result<L05_254_validated_struct_enum, ShapeFault> Create(PEnum value) => new L05_254_validated_struct_enum(value); } } } +public partial class L05Ot255 { public partial class Inner { public readonly partial record struct L05_255_validated_struct_flags_enum : IValidatedValue<L05_255_validated_struct_flags_enum, PFlags, ShapeFault> { public static Result<L05_255_validated_struct_flags_enum, ShapeFault> Create(PFlags value) => new L05_255_validated_struct_flags_enum(value); } } } +public readonly partial record struct L05_256_validated_struct_flags_enum : IValidatedValue<L05_256_validated_struct_flags_enum, PFlags, ShapeFault> { public static Result<L05_256_validated_struct_flags_enum, ShapeFault> Create(PFlags value) => new L05_256_validated_struct_flags_enum(value); } +public partial struct L05Os257 { public readonly partial record struct L05_257_validated_struct_int : IL05Tag, IValidatedValue<L05_257_validated_struct_int, int, ShapeFault> { public static Result<L05_257_validated_struct_int, ShapeFault> Create(int value) => new L05_257_validated_struct_int(value); string IL05Tag.Tag => "t"; } } +public partial class L05Ot258 { public partial class Inner { public readonly partial record struct L05_258_validated_struct_int : IL05Tag, IValidatedValue<L05_258_validated_struct_int, int, ShapeFault> { public static Result<L05_258_validated_struct_int, ShapeFault> Create(int value) => new L05_258_validated_struct_int(value); string IL05Tag.Tag => "t"; } } } +public partial class L05Ot259 { public partial class Inner { public readonly partial record struct L05_259_validated_struct_int : IValidatedValue<L05_259_validated_struct_int, int, ShapeFault> { public static Result<L05_259_validated_struct_int, ShapeFault> Create(int value) => new L05_259_validated_struct_int(value); } } } +public readonly partial record struct L05_260_validated_struct_int : IValidatedValue<L05_260_validated_struct_int, int, ShapeFault> { public static Result<L05_260_validated_struct_int, ShapeFault> Create(int value) => new L05_260_validated_struct_int(value); } +public partial struct L05Os261 { public readonly partial record struct L05_261_validated_struct_long : IL05Tag, IValidatedValue<L05_261_validated_struct_long, long, ShapeFault> { public static Result<L05_261_validated_struct_long, ShapeFault> Create(long value) => new L05_261_validated_struct_long(value); string IL05Tag.Tag => "t"; } } +public readonly partial record struct L05_262_validated_struct_long : IL05Tag, IValidatedValue<L05_262_validated_struct_long, long, ShapeFault> { public static Result<L05_262_validated_struct_long, ShapeFault> Create(long value) => new L05_262_validated_struct_long(value); string IL05Tag.Tag => "t"; } +public readonly partial record struct L05_263_validated_struct_own_record_class : IL05Tag, IValidatedValue<L05_263_validated_struct_own_record_class, PRec, ShapeFault> { public static Result<L05_263_validated_struct_own_record_class, ShapeFault> Create(PRec value) => new L05_263_validated_struct_own_record_class(value); string IL05Tag.Tag => "t"; } +public partial struct L05Os264 { public readonly partial record struct L05_264_validated_struct_own_record_class : IValidatedValue<L05_264_validated_struct_own_record_class, PRec, ShapeFault> { public static Result<L05_264_validated_struct_own_record_class, ShapeFault> Create(PRec value) => new L05_264_validated_struct_own_record_class(value); } } +public partial class L05Ot265 { public partial class Inner { public readonly partial record struct L05_265_validated_struct_string : IL05Tag, IValidatedValue<L05_265_validated_struct_string, string, ShapeFault> { public static Result<L05_265_validated_struct_string, ShapeFault> Create(string value) => new L05_265_validated_struct_string(value); string IL05Tag.Tag => "t"; } } } +public partial class L05Oc266 { public readonly partial record struct L05_266_validated_struct_string : IValidatedValue<L05_266_validated_struct_string, string, ShapeFault> { public static Result<L05_266_validated_struct_string, ShapeFault> Create(string value) => new L05_266_validated_struct_string(value); } } +public readonly partial record struct L05_267_validated_struct_string : IValidatedValue<L05_267_validated_struct_string, string, ShapeFault> { public static Result<L05_267_validated_struct_string, ShapeFault> Create(string value) => new L05_267_validated_struct_string(value); } +public readonly partial record struct L05_268_validated_struct_ulong : IL05Tag, IValidatedValue<L05_268_validated_struct_ulong, ulong, ShapeFault> { public static Result<L05_268_validated_struct_ulong, ShapeFault> Create(ulong value) => new L05_268_validated_struct_ulong(value); string IL05Tag.Tag => "t"; } +public readonly partial record struct L05_269_validated_struct_ulong : IValidatedValue<L05_269_validated_struct_ulong, ulong, ShapeFault> { public static Result<L05_269_validated_struct_ulong, ShapeFault> Create(ulong value) => new L05_269_validated_struct_ulong(value); } + +public static partial class L05Registry +{ + static partial void Fill() + { + Add("closed-set", "-", "base-depth-1", "nested-in-class", typeof(L05Oc0.L05_0_closed_set__), () => L05Oc0.L05_0_closed_set__.Alpha, () => L05Oc0.L05_0_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Oc0.L05_0_closed_set__.Beta); + Add("closed-set", "-", "base-depth-1", "nested-two-levels", typeof(L05Ot1.Inner.L05_1_closed_set__), () => L05Ot1.Inner.L05_1_closed_set__.Alpha, () => L05Ot1.Inner.L05_1_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Ot1.Inner.L05_1_closed_set__.Beta); + Add("closed-set", "-", "base-depth-2", "nested-in-class", typeof(L05Oc2.L05_2_closed_set__), () => L05Oc2.L05_2_closed_set__.Alpha, () => L05Oc2.L05_2_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Oc2.L05_2_closed_set__.Beta); + Add("closed-set", "-", "base-depth-2", "nested-in-struct", typeof(L05Os3.L05_3_closed_set__), () => L05Os3.L05_3_closed_set__.Alpha, () => L05Os3.L05_3_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Os3.L05_3_closed_set__.Beta); + Add("closed-set", "-", "base-depth-2", "top-level", typeof(L05_4_closed_set__), () => L05_4_closed_set__.Alpha, () => L05_4_closed_set__.All.First(m => m.Name == "Alpha"), () => L05_4_closed_set__.Beta); + Add("closed-set", "-", "base-depth-3", "nested-in-class", typeof(L05Oc5.L05_5_closed_set__), () => L05Oc5.L05_5_closed_set__.Alpha, () => L05Oc5.L05_5_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Oc5.L05_5_closed_set__.Beta); + Add("closed-set", "-", "base-depth-3", "nested-in-struct", typeof(L05Os6.L05_6_closed_set__), () => L05Os6.L05_6_closed_set__.Alpha, () => L05Os6.L05_6_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Os6.L05_6_closed_set__.Beta); + Add("closed-set", "-", "base-depth-3", "nested-two-levels", typeof(L05Ot7.Inner.L05_7_closed_set__), () => L05Ot7.Inner.L05_7_closed_set__.Alpha, () => L05Ot7.Inner.L05_7_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Ot7.Inner.L05_7_closed_set__.Beta); + Add("closed-set", "-", "base-depth-3", "top-level", typeof(L05_8_closed_set__), () => L05_8_closed_set__.Alpha, () => L05_8_closed_set__.All.First(m => m.Name == "Alpha"), () => L05_8_closed_set__.Beta); + Add("closed-set", "-", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc9.L05_9_closed_set__), () => L05Oc9.L05_9_closed_set__.Alpha, () => L05Oc9.L05_9_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Oc9.L05_9_closed_set__.Beta); + Add("closed-set", "-", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os10.L05_10_closed_set__), () => L05Os10.L05_10_closed_set__.Alpha, () => L05Os10.L05_10_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Os10.L05_10_closed_set__.Beta); + Add("closed-set", "-", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot11.Inner.L05_11_closed_set__), () => L05Ot11.Inner.L05_11_closed_set__.Alpha, () => L05Ot11.Inner.L05_11_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Ot11.Inner.L05_11_closed_set__.Beta); + Add("closed-set", "-", "base-explicit-IEquatable", "top-level", typeof(L05_12_closed_set__), () => L05_12_closed_set__.Alpha, () => L05_12_closed_set__.All.First(m => m.Name == "Alpha"), () => L05_12_closed_set__.Beta); + Add("closed-set", "-", "explicit-interface-member", "nested-in-class", typeof(L05Oc13.L05_13_closed_set__), () => L05Oc13.L05_13_closed_set__.Alpha, () => L05Oc13.L05_13_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Oc13.L05_13_closed_set__.Beta); + Add("closed-set", "-", "explicit-interface-member", "top-level", typeof(L05_14_closed_set__), () => L05_14_closed_set__.Alpha, () => L05_14_closed_set__.All.First(m => m.Name == "Alpha"), () => L05_14_closed_set__.Beta); + Add("closed-set", "-", "none", "nested-in-struct", typeof(L05Os15.L05_15_closed_set__), () => L05Os15.L05_15_closed_set__.Alpha, () => L05Os15.L05_15_closed_set__.All.First(m => m.Name == "Alpha"), () => L05Os15.L05_15_closed_set__.Beta); + Add("record-struct", "DateOnly", "explicit-interface-member", "nested-in-class", typeof(L05Oc16.L05_16_record_struct_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("record-struct", "DateOnly", "explicit-interface-member", "nested-in-struct", typeof(L05Os17.L05_17_record_struct_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("record-struct", "DateOnly", "explicit-interface-member", "nested-two-levels", typeof(L05Ot18.Inner.L05_18_record_struct_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("record-struct", "DateTime", "none", "nested-in-class", typeof(L05Oc19.L05_19_record_struct_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("record-struct", "DateTimeOffset", "none", "nested-in-struct", typeof(L05Os20.L05_20_record_struct_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("record-struct", "Guid", "explicit-interface-member", "nested-in-struct", typeof(L05Os21.L05_21_record_struct_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("record-struct", "Guid", "explicit-interface-member", "top-level", typeof(L05_22_record_struct_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("record-struct", "NodaTime.Instant", "explicit-interface-member", "nested-in-class", typeof(L05Oc23.L05_23_record_struct_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("record-struct", "NodaTime.Instant", "explicit-interface-member", "nested-in-struct", typeof(L05Os24.L05_24_record_struct_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("record-struct", "NodaTime.Instant", "explicit-interface-member", "top-level", typeof(L05_25_record_struct_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("record-struct", "TimeOnly", "explicit-interface-member", "nested-two-levels", typeof(L05Ot26.Inner.L05_26_record_struct_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("record-struct", "TimeOnly", "explicit-interface-member", "top-level", typeof(L05_27_record_struct_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("record-struct", "TimeOnly", "none", "nested-in-struct", typeof(L05Os28.L05_28_record_struct_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("record-struct", "TimeSpan", "explicit-interface-member", "nested-in-class", typeof(L05Oc29.L05_29_record_struct_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("record-struct", "TimeSpan", "none", "nested-in-class", typeof(L05Oc30.L05_30_record_struct_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("record-struct", "Uri", "explicit-interface-member", "top-level", typeof(L05_31_record_struct_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("record-struct", "Uri", "none", "top-level", typeof(L05_32_record_struct_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("record-struct", "bool", "explicit-interface-member", "nested-in-struct", typeof(L05Os33.L05_33_record_struct_bool), () => false, () => false, () => true); + Add("record-struct", "bool", "explicit-interface-member", "nested-two-levels", typeof(L05Ot34.Inner.L05_34_record_struct_bool), () => false, () => false, () => true); + Add("record-struct", "bool", "explicit-interface-member", "top-level", typeof(L05_35_record_struct_bool), () => false, () => false, () => true); + Add("record-struct", "byte", "explicit-interface-member", "top-level", typeof(L05_36_record_struct_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("record-struct", "byte", "none", "nested-in-class", typeof(L05Oc37.L05_37_record_struct_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("record-struct", "char", "explicit-interface-member", "top-level", typeof(L05_38_record_struct_char), () => 'a', () => 'a', () => 'b'); + Add("record-struct", "char", "none", "nested-in-struct", typeof(L05Os39.L05_39_record_struct_char), () => 'a', () => 'a', () => 'b'); + Add("record-struct", "decimal", "explicit-interface-member", "nested-in-class", typeof(L05Oc40.L05_40_record_struct_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("record-struct", "decimal", "explicit-interface-member", "top-level", typeof(L05_41_record_struct_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("record-struct", "decimal", "none", "nested-in-class", typeof(L05Oc42.L05_42_record_struct_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("record-struct", "double", "explicit-interface-member", "nested-in-class", typeof(L05Oc43.L05_43_record_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("record-struct", "double", "explicit-interface-member", "nested-in-struct", typeof(L05Os44.L05_44_record_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("record-struct", "double", "explicit-interface-member", "nested-two-levels", typeof(L05Ot45.Inner.L05_45_record_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("record-struct", "double", "none", "nested-two-levels", typeof(L05Ot46.Inner.L05_46_record_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("record-struct", "enum", "explicit-interface-member", "nested-in-class", typeof(L05Oc47.L05_47_record_struct_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("record-struct", "enum", "explicit-interface-member", "nested-in-struct", typeof(L05Os48.L05_48_record_struct_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("record-struct", "flags-enum", "explicit-interface-member", "top-level", typeof(L05_49_record_struct_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("record-struct", "flags-enum", "none", "top-level", typeof(L05_50_record_struct_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("record-struct", "int", "explicit-interface-member", "nested-in-struct", typeof(L05Os51.L05_51_record_struct_int), () => 42, () => 42, () => 43); + Add("record-struct", "int", "none", "nested-two-levels", typeof(L05Ot52.Inner.L05_52_record_struct_int), () => 42, () => 42, () => 43); + Add("record-struct", "long", "none", "nested-two-levels", typeof(L05Ot53.Inner.L05_53_record_struct_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("record-struct", "long", "none", "top-level", typeof(L05_54_record_struct_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("record-struct", "own-record-class", "explicit-interface-member", "nested-two-levels", typeof(L05Ot55.Inner.L05_55_record_struct_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("record-struct", "string", "explicit-interface-member", "nested-in-struct", typeof(L05Os56.L05_56_record_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("record-struct", "string", "explicit-interface-member", "top-level", typeof(L05_57_record_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("record-struct", "string", "none", "nested-in-class", typeof(L05Oc58.L05_58_record_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("record-struct", "ulong", "explicit-interface-member", "nested-in-class", typeof(L05Oc59.L05_59_record_struct_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("record-struct", "ulong", "explicit-interface-member", "nested-in-struct", typeof(L05Os60.L05_60_record_struct_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("sealed-record-class", "DateOnly", "base-depth-2", "nested-in-struct", typeof(L05Os61.L05_61_sealed_record_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("sealed-record-class", "DateOnly", "base-depth-3", "top-level", typeof(L05_62_sealed_record_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("sealed-record-class", "DateOnly", "none", "nested-in-struct", typeof(L05Os63.L05_63_sealed_record_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("sealed-record-class", "DateOnly", "none", "top-level", typeof(L05_64_sealed_record_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("sealed-record-class", "DateTime", "base-depth-1", "top-level", typeof(L05_65_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTime", "base-depth-2", "nested-in-class", typeof(L05Oc66.L05_66_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTime", "base-depth-3", "nested-in-struct", typeof(L05Os67.L05_67_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTime", "base-depth-3", "nested-two-levels", typeof(L05Ot68.Inner.L05_68_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTime", "base-depth-3", "top-level", typeof(L05_69_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTime", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os70.L05_70_sealed_record_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("sealed-record-class", "DateTimeOffset", "base-depth-1", "nested-in-class", typeof(L05Oc71.L05_71_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-depth-2", "top-level", typeof(L05_72_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-depth-3", "nested-in-class", typeof(L05Oc73.L05_73_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-depth-3", "nested-two-levels", typeof(L05Ot74.Inner.L05_74_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-depth-3", "top-level", typeof(L05_75_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc76.L05_76_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "DateTimeOffset", "base-explicit-IEquatable", "top-level", typeof(L05_77_sealed_record_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("sealed-record-class", "Guid", "base-depth-1", "nested-in-class", typeof(L05Oc78.L05_78_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "Guid", "base-depth-1", "nested-in-struct", typeof(L05Os79.L05_79_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "Guid", "base-depth-2", "top-level", typeof(L05_80_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "Guid", "base-depth-3", "top-level", typeof(L05_81_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "Guid", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc82.L05_82_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "Guid", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot83.Inner.L05_83_sealed_record_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("sealed-record-class", "NodaTime.Instant", "base-depth-1", "nested-two-levels", typeof(L05Ot84.Inner.L05_84_sealed_record_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("sealed-record-class", "NodaTime.Instant", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os85.L05_85_sealed_record_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("sealed-record-class", "NodaTime.Instant", "none", "nested-in-struct", typeof(L05Os86.L05_86_sealed_record_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("sealed-record-class", "TimeOnly", "base-depth-3", "nested-in-struct", typeof(L05Os87.L05_87_sealed_record_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("sealed-record-class", "TimeOnly", "base-depth-3", "nested-two-levels", typeof(L05Ot88.Inner.L05_88_sealed_record_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("sealed-record-class", "TimeSpan", "base-depth-1", "nested-in-class", typeof(L05Oc89.L05_89_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "TimeSpan", "base-depth-1", "nested-two-levels", typeof(L05Ot90.Inner.L05_90_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "TimeSpan", "base-depth-2", "top-level", typeof(L05_91_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "TimeSpan", "base-depth-3", "top-level", typeof(L05_92_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "TimeSpan", "explicit-interface-member", "nested-in-struct", typeof(L05Os93.L05_93_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "TimeSpan", "none", "nested-in-class", typeof(L05Oc94.L05_94_sealed_record_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("sealed-record-class", "Uri", "base-depth-1", "nested-in-class", typeof(L05Oc95.L05_95_sealed_record_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("sealed-record-class", "Uri", "base-depth-2", "nested-in-struct", typeof(L05Os96.L05_96_sealed_record_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("sealed-record-class", "Uri", "base-depth-3", "nested-in-struct", typeof(L05Os97.L05_97_sealed_record_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("sealed-record-class", "Uri", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot98.Inner.L05_98_sealed_record_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("sealed-record-class", "bool", "base-depth-2", "nested-in-class", typeof(L05Oc99.L05_99_sealed_record_class_bool), () => false, () => false, () => true); + Add("sealed-record-class", "bool", "base-depth-2", "nested-two-levels", typeof(L05Ot100.Inner.L05_100_sealed_record_class_bool), () => false, () => false, () => true); + Add("sealed-record-class", "bool", "base-depth-3", "nested-in-struct", typeof(L05Os101.L05_101_sealed_record_class_bool), () => false, () => false, () => true); + Add("sealed-record-class", "bool", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot102.Inner.L05_102_sealed_record_class_bool), () => false, () => false, () => true); + Add("sealed-record-class", "bool", "explicit-interface-member", "nested-two-levels", typeof(L05Ot103.Inner.L05_103_sealed_record_class_bool), () => false, () => false, () => true); + Add("sealed-record-class", "byte", "base-depth-1", "nested-two-levels", typeof(L05Ot104.Inner.L05_104_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "base-depth-2", "nested-in-struct", typeof(L05Os105.L05_105_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "base-depth-3", "nested-in-class", typeof(L05Oc106.L05_106_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "base-depth-3", "top-level", typeof(L05_107_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "explicit-interface-member", "nested-two-levels", typeof(L05Ot108.Inner.L05_108_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "explicit-interface-member", "top-level", typeof(L05_109_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "byte", "none", "top-level", typeof(L05_110_sealed_record_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("sealed-record-class", "char", "base-depth-1", "nested-two-levels", typeof(L05Ot111.Inner.L05_111_sealed_record_class_char), () => 'a', () => 'a', () => 'b'); + Add("sealed-record-class", "char", "base-depth-2", "top-level", typeof(L05_112_sealed_record_class_char), () => 'a', () => 'a', () => 'b'); + Add("sealed-record-class", "char", "base-explicit-IEquatable", "top-level", typeof(L05_113_sealed_record_class_char), () => 'a', () => 'a', () => 'b'); + Add("sealed-record-class", "char", "explicit-interface-member", "nested-two-levels", typeof(L05Ot114.Inner.L05_114_sealed_record_class_char), () => 'a', () => 'a', () => 'b'); + Add("sealed-record-class", "decimal", "base-depth-1", "nested-in-class", typeof(L05Oc115.L05_115_sealed_record_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("sealed-record-class", "decimal", "base-depth-3", "nested-in-struct", typeof(L05Os116.L05_116_sealed_record_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("sealed-record-class", "decimal", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc117.L05_117_sealed_record_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("sealed-record-class", "decimal", "base-explicit-IEquatable", "top-level", typeof(L05_118_sealed_record_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("sealed-record-class", "double", "base-depth-1", "nested-two-levels", typeof(L05Ot119.Inner.L05_119_sealed_record_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("sealed-record-class", "double", "base-depth-2", "top-level", typeof(L05_120_sealed_record_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("sealed-record-class", "double", "base-depth-3", "nested-in-class", typeof(L05Oc121.L05_121_sealed_record_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("sealed-record-class", "double", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc122.L05_122_sealed_record_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("sealed-record-class", "enum", "base-depth-1", "nested-in-struct", typeof(L05Os123.L05_123_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "enum", "base-depth-2", "nested-two-levels", typeof(L05Ot124.Inner.L05_124_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "enum", "base-depth-3", "nested-two-levels", typeof(L05Ot125.Inner.L05_125_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "enum", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot126.Inner.L05_126_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "enum", "explicit-interface-member", "nested-in-struct", typeof(L05Os127.L05_127_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "enum", "none", "nested-in-class", typeof(L05Oc128.L05_128_sealed_record_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("sealed-record-class", "flags-enum", "base-depth-1", "nested-in-class", typeof(L05Oc129.L05_129_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "flags-enum", "base-depth-1", "nested-two-levels", typeof(L05Ot130.Inner.L05_130_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "flags-enum", "base-depth-2", "top-level", typeof(L05_131_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "flags-enum", "base-depth-3", "top-level", typeof(L05_132_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "flags-enum", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot133.Inner.L05_133_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "flags-enum", "explicit-interface-member", "nested-in-struct", typeof(L05Os134.L05_134_sealed_record_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("sealed-record-class", "int", "base-depth-2", "top-level", typeof(L05_135_sealed_record_class_int), () => 42, () => 42, () => 43); + Add("sealed-record-class", "int", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os136.L05_136_sealed_record_class_int), () => 42, () => 42, () => 43); + Add("sealed-record-class", "long", "base-depth-2", "nested-two-levels", typeof(L05Ot137.Inner.L05_137_sealed_record_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("sealed-record-class", "long", "base-depth-3", "nested-two-levels", typeof(L05Ot138.Inner.L05_138_sealed_record_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("sealed-record-class", "long", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os139.L05_139_sealed_record_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("sealed-record-class", "own-record-class", "base-depth-2", "nested-two-levels", typeof(L05Ot140.Inner.L05_140_sealed_record_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("sealed-record-class", "own-record-class", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os141.L05_141_sealed_record_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("sealed-record-class", "own-record-class", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot142.Inner.L05_142_sealed_record_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("sealed-record-class", "string", "base-depth-1", "nested-in-struct", typeof(L05Os143.L05_143_sealed_record_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("sealed-record-class", "string", "base-depth-3", "top-level", typeof(L05_144_sealed_record_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("sealed-record-class", "string", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot145.Inner.L05_145_sealed_record_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("sealed-record-class", "ulong", "base-depth-3", "nested-two-levels", typeof(L05Ot146.Inner.L05_146_sealed_record_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("sealed-record-class", "ulong", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot147.Inner.L05_147_sealed_record_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("sealed-record-class", "ulong", "base-explicit-IEquatable", "top-level", typeof(L05_148_sealed_record_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "DateOnly", "base-depth-1", "nested-in-struct", typeof(L05Os149.L05_149_validated_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-class", "DateOnly", "base-explicit-IEquatable", "top-level", typeof(L05_150_validated_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-class", "DateOnly", "explicit-interface-member", "top-level", typeof(L05_151_validated_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-class", "DateOnly", "none", "nested-two-levels", typeof(L05Ot152.Inner.L05_152_validated_class_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-class", "DateTime", "base-depth-1", "nested-in-struct", typeof(L05Os153.L05_153_validated_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-class", "DateTime", "base-depth-1", "nested-two-levels", typeof(L05Ot154.Inner.L05_154_validated_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-class", "DateTime", "base-depth-2", "top-level", typeof(L05_155_validated_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-class", "DateTime", "base-depth-3", "nested-in-class", typeof(L05Oc156.L05_156_validated_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-class", "DateTime", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os157.L05_157_validated_class_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-class", "DateTimeOffset", "explicit-interface-member", "nested-in-class", typeof(L05Oc158.L05_158_validated_class_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("validated-class", "Guid", "base-depth-2", "nested-in-class", typeof(L05Oc159.L05_159_validated_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("validated-class", "Guid", "base-depth-2", "nested-two-levels", typeof(L05Ot160.Inner.L05_160_validated_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("validated-class", "Guid", "explicit-interface-member", "nested-in-class", typeof(L05Oc161.L05_161_validated_class_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("validated-class", "NodaTime.Instant", "base-depth-2", "nested-two-levels", typeof(L05Ot162.Inner.L05_162_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "NodaTime.Instant", "base-depth-3", "nested-in-class", typeof(L05Oc163.L05_163_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "NodaTime.Instant", "base-depth-3", "nested-in-struct", typeof(L05Os164.L05_164_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "NodaTime.Instant", "base-depth-3", "nested-two-levels", typeof(L05Ot165.Inner.L05_165_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "NodaTime.Instant", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc166.L05_166_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "NodaTime.Instant", "base-explicit-IEquatable", "top-level", typeof(L05_167_validated_class_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-class", "TimeOnly", "base-depth-1", "nested-in-class", typeof(L05Oc168.L05_168_validated_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-class", "TimeOnly", "base-depth-1", "nested-in-struct", typeof(L05Os169.L05_169_validated_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-class", "TimeOnly", "base-depth-2", "top-level", typeof(L05_170_validated_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-class", "TimeOnly", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc171.L05_171_validated_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-class", "TimeOnly", "none", "top-level", typeof(L05_172_validated_class_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-class", "TimeSpan", "base-depth-1", "top-level", typeof(L05_173_validated_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("validated-class", "TimeSpan", "base-depth-2", "nested-in-class", typeof(L05Oc174.L05_174_validated_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("validated-class", "TimeSpan", "base-depth-2", "nested-in-struct", typeof(L05Os175.L05_175_validated_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("validated-class", "TimeSpan", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot176.Inner.L05_176_validated_class_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("validated-class", "Uri", "base-depth-1", "nested-in-struct", typeof(L05Os177.L05_177_validated_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-class", "Uri", "base-depth-3", "nested-two-levels", typeof(L05Ot178.Inner.L05_178_validated_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-class", "Uri", "explicit-interface-member", "top-level", typeof(L05_179_validated_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-class", "Uri", "none", "nested-in-class", typeof(L05Oc180.L05_180_validated_class_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-class", "bool", "base-depth-1", "nested-in-struct", typeof(L05Os181.L05_181_validated_class_bool), () => false, () => false, () => true); + Add("validated-class", "bool", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc182.L05_182_validated_class_bool), () => false, () => false, () => true); + Add("validated-class", "bool", "explicit-interface-member", "nested-in-struct", typeof(L05Os183.L05_183_validated_class_bool), () => false, () => false, () => true); + Add("validated-class", "byte", "base-depth-2", "nested-in-struct", typeof(L05Os184.L05_184_validated_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("validated-class", "byte", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc185.L05_185_validated_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("validated-class", "byte", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot186.Inner.L05_186_validated_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("validated-class", "byte", "explicit-interface-member", "nested-in-struct", typeof(L05Os187.L05_187_validated_class_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("validated-class", "char", "base-depth-2", "nested-in-class", typeof(L05Oc188.L05_188_validated_class_char), () => 'a', () => 'a', () => 'b'); + Add("validated-class", "char", "base-depth-3", "nested-in-class", typeof(L05Oc189.L05_189_validated_class_char), () => 'a', () => 'a', () => 'b'); + Add("validated-class", "char", "base-depth-3", "nested-two-levels", typeof(L05Ot190.Inner.L05_190_validated_class_char), () => 'a', () => 'a', () => 'b'); + Add("validated-class", "char", "base-explicit-IEquatable", "nested-two-levels", typeof(L05Ot191.Inner.L05_191_validated_class_char), () => 'a', () => 'a', () => 'b'); + Add("validated-class", "char", "base-explicit-IEquatable", "top-level", typeof(L05_192_validated_class_char), () => 'a', () => 'a', () => 'b'); + Add("validated-class", "decimal", "base-depth-1", "top-level", typeof(L05_193_validated_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-class", "decimal", "base-depth-2", "nested-two-levels", typeof(L05Ot194.Inner.L05_194_validated_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-class", "decimal", "base-depth-2", "top-level", typeof(L05_195_validated_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-class", "decimal", "none", "nested-in-struct", typeof(L05Os196.L05_196_validated_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-class", "decimal", "none", "top-level", typeof(L05_197_validated_class_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-class", "double", "base-depth-2", "top-level", typeof(L05_198_validated_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("validated-class", "double", "base-depth-3", "nested-in-struct", typeof(L05Os199.L05_199_validated_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("validated-class", "double", "explicit-interface-member", "nested-in-struct", typeof(L05Os200.L05_200_validated_class_double), () => 0.0, () => -0.0, () => 1.5); + Add("validated-class", "enum", "base-depth-1", "nested-two-levels", typeof(L05Ot201.Inner.L05_201_validated_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("validated-class", "enum", "base-depth-2", "top-level", typeof(L05_202_validated_class_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("validated-class", "flags-enum", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc203.L05_203_validated_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("validated-class", "flags-enum", "explicit-interface-member", "nested-two-levels", typeof(L05Ot204.Inner.L05_204_validated_class_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("validated-class", "int", "base-depth-1", "nested-in-struct", typeof(L05Os205.L05_205_validated_class_int), () => 42, () => 42, () => 43); + Add("validated-class", "int", "base-depth-3", "nested-in-class", typeof(L05Oc206.L05_206_validated_class_int), () => 42, () => 42, () => 43); + Add("validated-class", "int", "explicit-interface-member", "nested-in-class", typeof(L05Oc207.L05_207_validated_class_int), () => 42, () => 42, () => 43); + Add("validated-class", "long", "base-depth-1", "nested-in-class", typeof(L05Oc208.L05_208_validated_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-class", "long", "base-depth-2", "nested-two-levels", typeof(L05Ot209.Inner.L05_209_validated_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-class", "long", "base-explicit-IEquatable", "nested-in-class", typeof(L05Oc210.L05_210_validated_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-class", "long", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os211.L05_211_validated_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-class", "long", "explicit-interface-member", "nested-two-levels", typeof(L05Ot212.Inner.L05_212_validated_class_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-class", "own-record-class", "base-depth-1", "nested-in-struct", typeof(L05Os213.L05_213_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "base-depth-1", "nested-two-levels", typeof(L05Ot214.Inner.L05_214_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "base-depth-2", "top-level", typeof(L05_215_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "base-depth-3", "nested-in-struct", typeof(L05Os216.L05_216_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "base-depth-3", "nested-two-levels", typeof(L05Ot217.Inner.L05_217_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "base-explicit-IEquatable", "nested-in-struct", typeof(L05Os218.L05_218_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "own-record-class", "none", "nested-in-class", typeof(L05Oc219.L05_219_validated_class_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-class", "string", "base-depth-1", "nested-in-struct", typeof(L05Os220.L05_220_validated_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-class", "string", "base-depth-2", "nested-two-levels", typeof(L05Ot221.Inner.L05_221_validated_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-class", "string", "base-depth-3", "nested-in-class", typeof(L05Oc222.L05_222_validated_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-class", "string", "explicit-interface-member", "top-level", typeof(L05_223_validated_class_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-class", "ulong", "base-depth-1", "nested-in-struct", typeof(L05Os224.L05_224_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "base-depth-1", "top-level", typeof(L05_225_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "base-depth-2", "top-level", typeof(L05_226_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "base-depth-3", "nested-in-class", typeof(L05Oc227.L05_227_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "explicit-interface-member", "nested-in-class", typeof(L05Oc228.L05_228_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "explicit-interface-member", "nested-in-struct", typeof(L05Os229.L05_229_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-class", "ulong", "none", "nested-in-struct", typeof(L05Os230.L05_230_validated_class_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-struct", "DateOnly", "explicit-interface-member", "nested-two-levels", typeof(L05Ot231.Inner.L05_231_validated_struct_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-struct", "DateOnly", "none", "top-level", typeof(L05_232_validated_struct_DateOnly), () => new DateOnly(2026, 10, 15), () => DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber), () => new DateOnly(2026, 10, 16)); + Add("validated-struct", "DateTime", "explicit-interface-member", "nested-two-levels", typeof(L05Ot233.Inner.L05_233_validated_struct_DateTime), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), () => new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified), () => new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)); + Add("validated-struct", "DateTimeOffset", "none", "nested-in-struct", typeof(L05Os234.L05_234_validated_struct_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("validated-struct", "DateTimeOffset", "none", "top-level", typeof(L05_235_validated_struct_DateTimeOffset), () => new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), () => new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2)), () => new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)); + Add("validated-struct", "Guid", "none", "nested-in-class", typeof(L05Oc236.L05_236_validated_struct_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("validated-struct", "Guid", "none", "nested-in-struct", typeof(L05Os237.L05_237_validated_struct_Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => new Guid("11111111111111111111111111111111"), () => Guid.Parse("22222222-2222-2222-2222-222222222222")); + Add("validated-struct", "NodaTime.Instant", "none", "top-level", typeof(L05_238_validated_struct_NodaTime_Instant), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30), () => global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks()), () => global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)); + Add("validated-struct", "TimeOnly", "explicit-interface-member", "nested-in-class", typeof(L05Oc239.L05_239_validated_struct_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-struct", "TimeOnly", "none", "top-level", typeof(L05_240_validated_struct_TimeOnly), () => new TimeOnly(8, 30, 15), () => new TimeOnly(new TimeOnly(8, 30, 15).Ticks), () => new TimeOnly(8, 30, 16)); + Add("validated-struct", "TimeSpan", "none", "nested-in-class", typeof(L05Oc241.L05_241_validated_struct_TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.FromSeconds(5400), () => TimeSpan.FromMinutes(91)); + Add("validated-struct", "Uri", "explicit-interface-member", "nested-in-struct", typeof(L05Os242.L05_242_validated_struct_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-struct", "Uri", "explicit-interface-member", "top-level", typeof(L05_243_validated_struct_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-struct", "Uri", "none", "nested-in-class", typeof(L05Oc244.L05_244_validated_struct_Uri), () => new Uri("https://example.com/a"), () => new Uri("HTTPS://EXAMPLE.COM/a"), () => new Uri("https://example.com/b")); + Add("validated-struct", "bool", "explicit-interface-member", "nested-in-class", typeof(L05Oc245.L05_245_validated_struct_bool), () => false, () => false, () => true); + Add("validated-struct", "bool", "none", "nested-two-levels", typeof(L05Ot246.Inner.L05_246_validated_struct_bool), () => false, () => false, () => true); + Add("validated-struct", "byte", "explicit-interface-member", "top-level", typeof(L05_247_validated_struct_byte), () => (byte)7, () => (byte)7, () => (byte)8); + Add("validated-struct", "char", "explicit-interface-member", "nested-in-class", typeof(L05Oc248.L05_248_validated_struct_char), () => 'a', () => 'a', () => 'b'); + Add("validated-struct", "decimal", "explicit-interface-member", "top-level", typeof(L05_249_validated_struct_decimal), () => 7.8m, () => 7.80m, () => 7.81m); + Add("validated-struct", "double", "explicit-interface-member", "nested-two-levels", typeof(L05Ot250.Inner.L05_250_validated_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("validated-struct", "double", "none", "nested-two-levels", typeof(L05Ot251.Inner.L05_251_validated_struct_double), () => 0.0, () => -0.0, () => 1.5); + Add("validated-struct", "enum", "none", "nested-in-class", typeof(L05Oc252.L05_252_validated_struct_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("validated-struct", "enum", "none", "nested-in-struct", typeof(L05Os253.L05_253_validated_struct_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("validated-struct", "enum", "none", "nested-two-levels", typeof(L05Ot254.Inner.L05_254_validated_struct_enum), () => PEnum.B, () => (PEnum)1, () => PEnum.C); + Add("validated-struct", "flags-enum", "none", "nested-two-levels", typeof(L05Ot255.Inner.L05_255_validated_struct_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("validated-struct", "flags-enum", "none", "top-level", typeof(L05_256_validated_struct_flags_enum), () => PFlags.P | PFlags.Q, () => (PFlags)3, () => PFlags.R); + Add("validated-struct", "int", "explicit-interface-member", "nested-in-struct", typeof(L05Os257.L05_257_validated_struct_int), () => 42, () => 42, () => 43); + Add("validated-struct", "int", "explicit-interface-member", "nested-two-levels", typeof(L05Ot258.Inner.L05_258_validated_struct_int), () => 42, () => 42, () => 43); + Add("validated-struct", "int", "none", "nested-two-levels", typeof(L05Ot259.Inner.L05_259_validated_struct_int), () => 42, () => 42, () => 43); + Add("validated-struct", "int", "none", "top-level", typeof(L05_260_validated_struct_int), () => 42, () => 42, () => 43); + Add("validated-struct", "long", "explicit-interface-member", "nested-in-struct", typeof(L05Os261.L05_261_validated_struct_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-struct", "long", "explicit-interface-member", "top-level", typeof(L05_262_validated_struct_long), () => 4200000000L, () => 4200000000L, () => 4200000001L); + Add("validated-struct", "own-record-class", "explicit-interface-member", "top-level", typeof(L05_263_validated_struct_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-struct", "own-record-class", "none", "nested-in-struct", typeof(L05Os264.L05_264_validated_struct_own_record_class), () => new PRec { A = "x", B = 1 }, () => new PRec { A = new string("x".ToCharArray()), B = 1 }, () => new PRec { A = "y", B = 1 }); + Add("validated-struct", "string", "explicit-interface-member", "nested-two-levels", typeof(L05Ot265.Inner.L05_265_validated_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-struct", "string", "none", "nested-in-class", typeof(L05Oc266.L05_266_validated_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-struct", "string", "none", "top-level", typeof(L05_267_validated_struct_string), () => "alpha", () => new string("alpha".ToCharArray()), () => "beta"); + Add("validated-struct", "ulong", "explicit-interface-member", "top-level", typeof(L05_268_validated_struct_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + Add("validated-struct", "ulong", "none", "top-level", typeof(L05_269_validated_struct_ulong), () => 4200000000UL, () => 4200000000UL, () => 4200000001UL); + } +} diff --git a/tools/probe/corpus/harness/OffTheWire.cs b/tools/probe/corpus/harness/OffTheWire.cs new file mode 100644 index 00000000..eae52bbf --- /dev/null +++ b/tools/probe/corpus/harness/OffTheWire.cs @@ -0,0 +1,221 @@ +// Claim L06-off-the-wire (README.md#what-typekit-guarantees): "Option and Result stay off the wire. System.Text.Json throws +// NotSupportedException for either, in both directions, instead of writing {} that reads back as None. [...] If you want a wire format of +// your own, a converter you register on the options takes precedence." The message names the type (src/CodoMetis.TypeKit/README.md +// "Option or Result in JSON"); a source-generated context refuses an Option<T>? or Result<...>? property with the serializer's own +// InvalidOperationException when it builds that contract (ibid., "Good to know"). +// Oracle: exception, in every position, with the row's options and input, for an Option or Result produced by the row's operation. A +// converter of the application's for the same type in the same row is the baseline: it must win in both directions, and where the options +// leave the member out (it writes and reads nothing there), no converter runs, which is intended ("A missing property reaches no converter"). +#pragma warning disable CMTK0001 // default-instance: the default Option and Result are the shapes under test +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using CodoMetis.TypeKit; +using Probes.Shapes; +namespace Probes.Json; + +/// <summary>An application's own converter for an Option or Result: writes "own-or" and reads it back as the value it was made with, counting reads.</summary> +public sealed class OwnOrConverter<T>(T value) : JsonConverter<T> +{ + public int Reads; + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Reads++; + if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName) || reader.GetString() != "own-or") throw new JsonException("not own-or"); + return value; + } + public override void Write(Utf8JsonWriter writer, T v, JsonSerializerOptions options) => writer.WriteStringValue("own-or"); + public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Read(ref reader, typeToConvert, options); + public override void WriteAsPropertyName(Utf8JsonWriter writer, T v, JsonSerializerOptions options) => writer.WritePropertyName("own-or"); +} + +public static class OffTheWire +{ + public static async Task<string?> Check(Row r) + { + var kind = r["shape.kind"]; + var e = kind == "closed-set" ? Registry.Get(kind, "-") : Registry.Get(kind, r["shape.wrapped"]); + var position = r["shape.position"]; + var containing = position == "root" ? "class" : r["shape.containing"]; + var shape = r["or.shape"]; var op = r["or.operation"]; + _ = r["input.json"]; // the row's input, which every check of the row runs under; the read applies it + if (shape == "Result-E" && op is "query-syntax" or "FirstOrNone") + { r.Substituted("or.operation", "Match", "Result<TError> has no " + op + " (claims.json excludes the pair)"); op = "Match"; } + var voValue = Make.Vo(e, e.Valid()); + var v = Make.Held(e.Vo); + var produce = typeof(OffTheWire).GetMethod(nameof(Produce), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(v); + var (orType, orValue) = await (Task<(Type, object)>)produce.Invoke(null, [shape, op, voValue])!; + var key = shape == "Option" ? "O" : shape == "Result-E" ? "RE" : "RTE"; + var pb = typeof(OffTheWire).Assembly.GetType($"Probes.Json.L06PB_{v.Name}_{key}")!; var pd = typeof(OffTheWire).Assembly.GetType($"Probes.Json.L06PD_{v.Name}_{key}")!; + var eo = e with { Vo = orType, Plain = orType, PolyBase = pb, PolyDerived = pd, PolyBasePlain = pb, PolyDerivedPlain = pd }; + var hv = Pos.Build(position, containing, e, true, voValue); // the value object's shape: its document is the read's input + var ho = Pos.Build(position, containing, eo, true, orValue); + var o = JCtx.Apply(r, e, hv.Declared, ho.Declared); + var own = (JsonConverter)Activator.CreateInstance(typeof(OwnOrConverter<>).MakeGenericType(orType), orValue)!; + var oOwn = new JsonSerializerOptions(o); oOwn.Converters.Insert(0, own); + int OwnReads() => (int)own.GetType().GetField("Reads")!.GetValue(own)!; + var name = Jx.Name(orType); + // not-promised a-source-generated-context-refuses-a-nullable + var nullableUnderContext = r.Is("json.TypeInfoResolver", "source-generated-context") && position == "nullable-property"; + string? Refused(Exception? x, string dir) + { + if (nullableUnderContext) + return x is InvalidOperationException ? null : $"{dir}: a source-generated context refuses an {name}? property with the serializer's InvalidOperationException; got {(x is null ? "no exception" : Jx.Ex(x))}"; + if (x is not NotSupportedException) return $"{dir}: expected NotSupportedException naming {name}, got {(x is null ? "no exception" : Jx.Ex(x))}"; + // TypeKit's refusal, told by its type and never by its words: the serializer passes a converter's NotSupportedException on as the + // inner exception of its own, so one in the chain is thrown by NotWireTypeJsonConverterFactory's converter, by nameof + if (!ThrownBy(x, typeof(NotWireTypeJsonConverterFactory))) return $"{dir}: the NotSupportedException is not {nameof(NotWireTypeJsonConverterFactory)}'s: {Jx.Ex(x)}"; + return null; + } + + // the baseline: the application's own converter for the type wins, in both directions + var (jOwn, xOwn) = Jx.Ser(ho, oOwn); + var (jv, xv) = Jx.Ser(hv, o); + var refused = Baseline.Refusal; + var fw = r.Check("never-written", refused, c => + { + if (xOwn is not null) + { + if (xv is not null && xv.GetType() == xOwn.GetType()) return c.Inconclusive("write: the options refuse the shape itself, with the application's converter alike: " + Jx.Ex(xv)); + return $"write: a converter registered on the options for {name} does not take precedence: {Jx.Ex(xOwn)}"; + } + var ownWritten = Jx.Tokens(jOwn!).Any(t => t.Text == "own-or"); + var (jo, xo) = Jx.Ser(ho, o); + if (xo is null) + { + if (ownWritten || jo != jOwn) return $"write: {name} was written: {Jx.Cut(jo)}"; + // the options leave the member out, the application's converter's document alike: no converter ran + c.Note(NoteKeys.JsonMemberLeftOutByOptions, "write: " + Jx.Cut(jo)); + return c.Inconclusive("write: the options leave the member out, so no converter runs: " + Jx.Cut(jo)); + } + if (Refused(xo, "write") is { } f) return f; + c.Note(NoteKeys.Compared, $"{name} by {op}, {position}: write refused"); + return null; + }); + // reading: the value object's document with the row's input, where the Option or Result stands; measured before the check, so that + // a document the serializer refuses before the member (side (1), Sides) decides the check's baseline + var full = Jx.Full(o); + var (jf, xf) = Jx.Ser(hv, full); + // does the application's converter get called there? Its own document, read under the same options, says so + var (jOwnFull, _) = Jx.Ser(ho, Jx.Full(oOwn)); + string input = r["input.json"], text = ""; + if (xf is null) text = JsonParity.InputFor(input, e, full, jf!, position); + else if (jOwnFull is not null) { input = "valid"; text = jOwnFull; } + var reads = xOwn is null && (xf is null || jOwnFull is not null); + var (_, xOwnRead) = reads ? Jx.De(jOwnFull ?? "null", ho.Declared, oOwn) : (null, null); + var memberRead = reads && OwnReads() > 0; + var (vr, xr) = reads ? Jx.De(text, ho.Declared, o) : (null, null); + // side (1): the same document with the value object where the Option or Result stands, read under the same options; refusing alike + // (the same exception type at the same path, no message compared), the serializer refused the document before the member + var three = new Sides.Answer("read", null, xr); + var one = new Sides.Answer("read", null, reads && xr is not null && !ThrownBy(xr, typeof(NotWireTypeJsonConverterFactory)) ? Jx.De(text, hv.Declared, o).Ex : null); + var framework = Sides.Alike(one, three) ? Sides.Measured(one, "the document with the value object where the Option or Result stands") : null; + var fr = await Sides.Check(r, "never-read", refused, framework, three, null, null, async c => + { + await Task.CompletedTask; + if (xOwn is not null) return c.Inconclusive("read: the options refuse the shape itself, so no document has the member: " + Jx.Ex(xOwn)); + _ = c["input.json"]; + if (xf is not null) + { + // the value object's own shape cannot be written here (a value object over a class under Preserve, a class as a key): the + // document with the application's converter has a token where the Option or Result stands, and is read as it is + if (jOwnFull is null) return c.Inconclusive("read: no document has the member: " + Jx.Ex(xf)); + c.Substituted("input.json", "valid", "the value object's document cannot be written: " + xf.GetType().Name); + } + if (xOwnRead is not null) return $"read: a converter registered on the options for {name} does not take precedence: {Jx.Ex(xOwnRead)}"; + if (xr is null && input == "null" && position == "in-patch" && Jx.Member(ho.Declared, vr) is { } patch && patch.GetType().GetProperty("State")!.GetValue(patch)!.ToString() == "Null") + // a JSON null is the patch's own null (docs/merge-patches.md): it holds no Option or Result, and none was read + return c.Inapplicable(Cite.Docs("docs/merge-patches.md#tell-absent-null-and-a-value-apart"), $"read null: the Patch<{name}> is null, nothing of {name} read"); + if (xr is null) + { + if (memberRead) return $"read {input}: {name} was read without an exception\n text={Jx.Cut(text)}"; + c.Note(NoteKeys.JsonMemberLeftOutByOptions, "read " + input); + return c.Inapplicable(Cite.NotPromised("a-missing-property-reaches-no-converter"), "read " + input); + } + if (Refused(xr, "read " + input) is { } f) return f + $"\n text={Jx.Cut(text)}"; + c.Note(NoteKeys.Compared, $"{name} by {op}, {position}: read {input} refused"); + return null; + }); + return fw ?? fr; + } + + /// <summary> + /// Whether a refusal, or one in its chain of inner exceptions, was thrown by a converter that <paramref name="factory"/> declares: the + /// method that threw it (its TargetSite) belongs to the factory's type or to a type nested in it. Told by types, never by a message. + /// </summary> + static bool ThrownBy(Exception? x, Type factory) + { + for (; x is not null; x = x.InnerException) + for (var t = x.TargetSite?.DeclaringType; t is not null; t = t.DeclaringType) + if (t == factory) return true; + return false; + } + + static async Task<(Type, object)> Produce<V>(string shape, string op, V v) where V : notnull + { + switch (shape) + { + case "Option": + { + Option<V> x = op switch + { + "Match" => Option.Some(v).Match(s => Option.Some(s), () => Option.None<V>()), + "Bind-Map-chain" => Option.Some(v).Bind(s => Option.Some(s)).Map(s => s), + "query-syntax" => from s in Option.Some(v) select s, + "Sequence-Transpose" => Option.Some(Result<V, ShapeFault>.Success(v)).Transpose().Match(s => s, _ => Option.None<V>()), + "Result.Catch" => Result.Catch(() => v).ToOption(), + "FirstOrNone" => new[] { v }.FirstOrNone(), + "default-instance" => default, + "after-await-elsewhere" => await Task.Run(() => Option.Some(v)).ConfigureAwait(false), + _ => throw new ArgumentException(op), + }; + return (typeof(Option<V>), x); + } + case "Result-E": + { + Result<V> x = op switch + { + "Match" => Result<V>.Error(v).Match(() => Result<V>.Success(), er => Result<V>.Error(er)), + "Bind-Map-chain" => Result<V>.Success().Bind(() => Result<V>.Error(v)), + "Sequence-Transpose" => new[] { Result<V>.Error(v) }.Sequence(), + "Result.Catch" => Result.Catch(() => { }).MapError(_ => v), + "default-instance" => default, + "after-await-elsewhere" => await Task.Run(() => Result<V>.Error(v)).ConfigureAwait(false), + _ => throw new ArgumentException(op), + }; + return (typeof(Result<V>), x); + } + default: + { + Result<V, ShapeFault> x; + if (shape == "Task-of-Result") + { + var ok = Task.FromResult(Result<V, ShapeFault>.Success(v)); + x = op switch + { + "Bind-Map-chain" => await ok.BindAsync(s => Result<V, ShapeFault>.Success(s)).MapAsync(s => s), + "FirstOrNone" => await Task.FromResult(new[] { v }.FirstOrNone()).ToResultAsync(ShapeFault.Refused), + "Result.Catch" => await Result.CatchAsync(() => Task.FromResult(v)).MapErrorAsync(_ => ShapeFault.Refused), + _ => await Task.FromResult(await Sync(op, v)).ConfigureAwait(false), + }; + } + else if (shape == "ValueTask-of-Result") x = await new ValueTask<Result<V, ShapeFault>>(await Sync(op, v)); + else x = await Sync(op, v); + return (typeof(Result<V, ShapeFault>), x); + } + } + } + + static async Task<Result<V, ShapeFault>> Sync<V>(string op, V v) where V : notnull => op switch + { + "Match" => Result<V, ShapeFault>.Success(v).Match(s => Result<V, ShapeFault>.Success(s), er => Result<V, ShapeFault>.Error(er)), + "Bind-Map-chain" => Result<V, ShapeFault>.Success(v).Bind(s => Result<V, ShapeFault>.Success(s)).Map(s => s), + "query-syntax" => from s in Result<V, ShapeFault>.Success(v) select s, + "Sequence-Transpose" => new[] { Result<V, ShapeFault>.Success(v) }.Sequence().Map(l => l[0]), + "Result.Catch" => Result.Catch(() => v).MapError(_ => ShapeFault.Refused), + "FirstOrNone" => new[] { v }.FirstOrNone().ToResult(ShapeFault.Refused), + "default-instance" => default, + "after-await-elsewhere" => await Task.Run(() => Result<V, ShapeFault>.Success(v)).ConfigureAwait(false), + _ => throw new ArgumentException(op), + }; +} diff --git a/tools/probe/corpus/harness/Positions.cs b/tools/probe/corpus/harness/Positions.cs index 32de325e..800e60ba 100644 --- a/tools/probe/corpus/harness/Positions.cs +++ b/tools/probe/corpus/harness/Positions.cs @@ -43,10 +43,12 @@ public static Held Build(string position, string containing, Entry e, bool vo, o var create = typeof(ImmutableArray).GetMethods().First(x => x.Name == "CreateRange" && x.GetParameters().Length == 1).MakeGenericMethod(t); member = create.Invoke(null, [l])!; m = member.GetType(); break; } - case "dictionary-key": { var d = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(t, typeof(int)))!; d.Add(sample, 1); member = d; m = d.GetType(); break; } + // a dictionary key is never null: a nullable twin (or, under http.nullability=nullable, a nullable value object) is a key of the type it holds + case "dictionary-key": { var d = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(Make.Held(t), typeof(int)))!; d.Add(sample, 1); member = d; m = d.GetType(); break; } case "dictionary-value": { var d = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(typeof(string), t))!; d.Add("k", sample); member = d; m = d.GetType(); break; } + // a Patch<T> holds no null (T : notnull): a nullable twin is patched as the type it holds, as a dictionary key is case "in-patch": - member = typeof(Patch).GetMethod("Value")!.MakeGenericMethod(t).Invoke(null, [sample])!; m = member.GetType(); break; + member = typeof(Patch).GetMethod("Value")!.MakeGenericMethod(Make.Held(t)).Invoke(null, [sample])!; m = member.GetType(); break; case "polymorphic-derived-property": { var derived = vo ? e.PolyDerived : e.PolyDerivedPlain; @@ -68,23 +70,27 @@ public static Held Build(string position, string containing, Entry e, bool vo, o } /// <summary> -/// Input equivalence for the differential oracles: the wrapped side and the value-object side must be the same -/// shape under the same options, differing only where the value object stands in for its wrapped type. A -/// mismatch is a harness error, never a pass or a product violation. +/// Input equivalence for the differential oracles: the twin and the value object must hold the same shape under the same options, the +/// twin standing where the value object stands, at its nullability: both admit null or neither (shapes.cs makes the twin of a class value +/// object over a struct the struct made nullable). The one difference no plain type can mirror is a struct value object over a class, +/// which refuses the null its twin reads (not-promised a-struct-refuses-a-json-null), and a dictionary key and what a patch holds, which +/// are never null on either side. A mismatch is a harness error, never a pass or a product violation (Sides.Twins). /// </summary> public static class Equivalence { - public static string? Shapes(Type plain, Type vo, Entry e) + public static string? Shapes(Type plain, Type vo, Entry e) => Shapes(plain, vo, e, key: false); + + static string? Shapes(Type plain, Type vo, Entry e, bool key) { if (plain == vo) return null; - if (vo == e.Vo) return plain == e.Plain || Nullable.GetUnderlyingType(plain) == e.Plain ? null : $"{Name(plain)} stands where {Name(vo)} should face {Name(e.Plain)}"; - // A nullable struct value object faces its wrapped type made nullable, or the wrapped class itself (already nullable). - if (Nullable.GetUnderlyingType(vo) == e.Vo) return Nullable.GetUnderlyingType(plain) == e.Plain || (plain == e.Plain && !plain.IsValueType) ? null : $"{Name(plain)} against {Name(vo)}"; - if (plain.IsArray && vo.IsArray) return Shapes(plain.GetElementType()!, vo.GetElementType()!, e); + if (Make.Held(vo) == Make.Held(e.Vo)) return Member(plain, vo, e, key); + if (plain.IsArray && vo.IsArray) return Shapes(plain.GetElementType()!, vo.GetElementType()!, e, false); if (plain.IsGenericType && vo.IsGenericType && plain.GetGenericTypeDefinition() == vo.GetGenericTypeDefinition()) { var pa = plain.GetGenericArguments(); var va = vo.GetGenericArguments(); - for (var i = 0; i < pa.Length; i++) if (Shapes(pa[i], va[i], e) is { } why) return why; + // a dictionary's key and what a patch holds are never null, on either side + var notNull = plain.GetGenericTypeDefinition() == typeof(Dictionary<,>) || plain.GetGenericTypeDefinition() == typeof(Patch<>); + for (var i = 0; i < pa.Length; i++) if (Shapes(pa[i], va[i], e, notNull && i == 0) is { } why) return why; return null; } // The generated stand-ins (the polymorphic bases and derived types): one per side by design, equal in @@ -93,17 +99,27 @@ public static class Equivalence { var pp = plain.GetProperties().ToDictionary(x => x.Name); var vp = vo.GetProperties().ToDictionary(x => x.Name); if (!pp.Keys.Order().SequenceEqual(vp.Keys.Order())) return $"{Name(plain)} and {Name(vo)} have different properties"; - foreach (var (name, prop) in pp) if (Shapes(prop.PropertyType, vp[name].PropertyType, e) is { } why) return $"{name}: {why}"; + foreach (var (name, prop) in pp) if (Shapes(prop.PropertyType, vp[name].PropertyType, e, false) is { } why) return $"{name}: {why}"; var pd = plain.GetCustomAttributes(typeof(System.Text.Json.Serialization.JsonDerivedTypeAttribute), false).Length; var vd = vo.GetCustomAttributes(typeof(System.Text.Json.Serialization.JsonDerivedTypeAttribute), false).Length; if (pd != vd) return $"{Name(plain)} and {Name(vo)} declare {pd} and {vd} derived types"; if ((plain.BaseType is { } pb && pb != typeof(object)) || (vo.BaseType is { } vb && vb != typeof(object))) - return Shapes(plain.BaseType!, vo.BaseType!, e); + return Shapes(plain.BaseType!, vo.BaseType!, e, false); return null; } return $"{Name(plain)} and {Name(vo)} are different shapes"; } + /// <summary>Where the value object stands: its twin, at its nullability.</summary> + static string? Member(Type plain, Type vo, Entry e, bool key) + { + if (Make.Held(plain) != Make.Held(e.Plain)) return $"{Name(plain)} stands where {Name(vo)} should face {Name(e.Plain)}"; + if (key || Make.AdmitsNull(plain) == Make.AdmitsNull(vo)) return null; + // a struct value object over a class refuses the JSON null its twin reads: no plain type mirrors it, the claims cite it + if (!Make.AdmitsNull(vo) && !Make.Held(e.Plain).IsValueType) return null; + return $"{Name(plain)} and {Name(vo)} differ in nullability: the twin {(Make.AdmitsNull(plain) ? "admits" : "refuses")} null, the value object {(Make.AdmitsNull(vo) ? "admits" : "refuses")} it"; + } + public static string? Options(JsonSerializerOptions plain, JsonSerializerOptions vo, bool allowConverters) { if (ReferenceEquals(plain, vo)) return null; @@ -176,87 +192,132 @@ static string Input(string input, Entry e, JsonSerializerOptions o, string jp, s } } - /// <summary>True when the value object may legitimately refuse (or differ on) an input the wrapped type reads: the documented differences.</summary> - public static bool ReadMayDiffer(Row r, Entry e, string input, Exception? xv) - { - if (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && xv is NotSupportedException) return true; - if (Validated(e) && input == "refused-by-create" && r.Get("json.Converters") != "StoredJsonConverterFactory") return true; - if (input == "null" && e.Vo.IsValueType) return true; - if (input == "null" && !e.Vo.IsValueType && e.Plain.IsValueType) return true; - if (e.Kind == "closed-set" && input != "valid") return true; - return false; - } static bool Validated(Entry e) => e.Kind.StartsWith("validated"); - /// <summary>The oracle "JSON parity" (differential): a shape with the value object and the same shape with the wrapped type give the same - /// bytes under the same options and read each other's JSON. Returns null for a pass, otherwise the failure.</summary> + /// <summary> + /// The oracle "JSON parity" (differential) as L01's checks, three-sided (Sides): the value object's write (3) against the wrapped twin in + /// the same shape under the same options (2), held to the twin under the application's options less TypeKit's converters (1), as + /// the-underlying-types-json, or as-a-dictionary-key-too where the value object is a dictionary key; and the read of the twin's document + /// with the row's input.json, read-as-the-underlying-type-reads, alike. L01 promises the underlying type's JSON, so side (1) runs on every + /// row. Under an application's converter for the value object, which writes it instead of the generated one, the read is that + /// converter's own document read back. Every check runs; returns the first failure. + /// </summary> public static string? Check(Row r, Entry e, JsonSerializerOptions o, string position, string containing, string input, JsonSerializerOptions? oPlain = null) { oPlain ??= o; var plain = e.Valid(); var voV = Make.Vo(e, plain); - var hp = Pos.Build(position, containing, e, false, plain); - var hv = Pos.Build(position, containing, e, true, voV); - // Input equivalence: the two sides may differ only by the substituted type, not in shape or options. - if (Equivalence.Shapes(hp.Declared, hv.Declared, e) is { } whyShape) { r.Outcome("harness-error", "inputs not equivalent: " + whyShape); return null; } + var (hp, hv) = Sides.Twins(position, containing, e, plain, voV); + // Input equivalence of the options: the two sides may differ only by the substituted type, not in options (the shapes: Sides.Twins). if (Equivalence.Options(oPlain, o, allowConverters: e.Kind == "closed-set") is { } whyOptions) { r.Outcome("harness-error", "inputs not equivalent: " + whyOptions); return null; } - var (jp, ep) = Ser(hp, oPlain); var (jv, ev) = Ser(hv, o); - var ownVo = r.Get("json.Converters") == "own-for-value-object"; - var preserveOwn = r.Get("json.ReferenceHandler") == "Preserve"; - if (ownVo) + var one = Sides.WithoutTypeKit(oPlain); + var w1 = Sides.Write(hp, one); var w2 = Sides.Write(hp, oPlain); var w3 = Sides.Write(hv, o); + var write = position == "dictionary-key" ? "as-a-dictionary-key-too" : "the-underlying-types-json"; + const string read = "read-as-the-underlying-type-reads"; + string? fw, fr; + if (r.Get("json.Converters") == "own-for-value-object") { - if (ev is not null && ep is null) return $"own VO converter row: VO serialization threw {ev.GetType().Name}: {ev.Message}"; - r.Note("own-vo"); - if (jv is not null) - { - var (back, eb) = De(jv, hv, o); - if (eb is not null) { if (r.Get("json.UnmappedMemberHandling") == "Disallow" || input != "valid") { r.Note("own-vo:read-back-refused"); return null; } return "own VO converter: read-back threw " + eb.GetType().Name + ": " + eb.Message; } - if (back != jv) return $"own VO converter round trip differs: {jv} vs {back}"; - } - return null; + // "Your own converter wins" (AspNetCore README#what-the-document-says): the application's converter writes the value object + // instead of the generated one, so the twin's bytes are none of its. claims.json declares the write inapplicable there. + fw = r.Check(write, Baseline.Twin, c => c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), "write")); + fr = r.Check(read, Baseline.RoundTrip, c => Sides.Interferes(w1, w2) ?? OwnVoRead(c, e, hp, hv, oPlain, o, w3.Value, w3.Thrown, w2.Thrown)); + return fw ?? fr; } - if (e.Wrapped == "byte" && position == "array-element" && jp != jv) { r.Note("byte[]-is-base64-inherent"); return null; } // STJ writes byte[] as base64; an array of byte value objects is a JSON array - if (ep is not null && ev is not null) + fw = Sides.Check(r, write, Baseline.Twin, Sides.Framework(r, e, hp, one, w1, w3), w3, w1, w2, c => Write(c, e, position, w2, w3)); + if (e.Wrapped == "byte" && position == "array-element") + fr = r.Check(read, Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("a-byte-array-is-base64"), "read")); + else if (e.Kind == "closed-set" && position == "dictionary-key" && r.Get("json.DictionaryKeyPolicy") is not (null or "default")) + fr = r.Check(read, Baseline.Twin, c => c.Inapplicable(Cite.NotPromised("a-closed-set-key-is-its-name"), "read")); + else if (w2.Value is not { } jp) + fr = r.Check(read, Baseline.Twin, c => c.Inconclusive("read: the wrapped type is not written under these options, so there is no document to read")); + else { - // Both refused: evidence about refusing, not about serializing. Refusing differently is no agreement at all. - if (ep.GetType() != ev.GetType()) { r.Note("both-threw-differently:" + ep.GetType().Name + "/" + ev.GetType().Name); return null; } - r.Note("both-threw:" + ep.GetType().Name + ":" + ev.Message[..Math.Min(70, ev.Message.Length)]); return null; + var text = Input(input, e, oPlain, jp, position); + var r1 = Sides.Read(text, hp, one); var r2 = Sides.Read(text, hp, oPlain); var r3 = Sides.Read(text, hv, o); + fr = Sides.Check(r, read, Baseline.Twin, Sides.Framework(r, e, hp, one, r1, r3), r3, r1, r2, c => Read(c, e, position, input, text, jp, r2, r3)); } - if (ep is null && ev is not null) + return fw ?? fr; + } + + /// <summary>The value object's document (3) is the wrapped twin's (2), byte for byte, in the same shape under the same options; (1) and (3) refusing alike is decided before (Sides).</summary> + static string? Write(Check c, Entry e, string position, Sides.Answer p, Sides.Answer v) + { + // System.Text.Json writes byte[] as base64 and an array of value objects over byte as a JSON array (KB-038); claims.json declares it + if (e.Wrapped == "byte" && position == "array-element" && p.Value != v.Value) return c.Inapplicable(Cite.NotPromised("a-byte-array-is-base64"), "write"); + if (p.Refused && v.Refused) + // Both refused, not alike (Sides.Alike): evidence about refusing, not about serializing, and nothing to compare. + return c.Inconclusive($"write: both refuse, not alike: the wrapped type {p}, the value object {v}"); + if (!p.Refused && v.Refused) { - if (r.Is("json.MaxDepth", "3") && ev is JsonException && ev.Message.Contains("maximum allowed depth")) { r.Note("stj-maxdepth-charges-custom-converters"); return null; } // inherent to STJ: any custom converter costs a level (design note D1) - if (preserveOwn && e.Wrapped == "own-record-class" && ev is NotSupportedException) { r.Note("preserve-documented"); return null; } // documented (Generators README#json) - return $"wrapped serialized ({jp}) but the value object threw {ev.GetType().Name}: {ev.Message}"; + if (c.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && v.Thrown is NotSupportedException) return c.Refused(Cite.NotPromised("preserved-references-over-a-walked-class"), "write"); + return $"wrapped serialized ({p.Value}) but the value object threw {v.Thrown!.GetType().Name}: {v.Thrown.Message}"; } - if (ep is not null && ev is null) return $"value object serialized ({jv}) where the wrapped type threw {ep.GetType().Name}: {ep.Message}"; - if (jp != jv && e.Kind == "closed-set" && position == "dictionary-key" && r.Get("json.DictionaryKeyPolicy") != "default") { r.Note("closed-set-key-unpoliced"); return null; } // a closed set travels as its name - if (jp != jv) + if (p.Refused) return $"value object serialized ({v.Value}) where the wrapped type threw {p.Thrown!.GetType().Name}: {p.Thrown.Message}"; + // a closed set travels as its name, which no key policy renames; claims.json declares it + if (p.Value != v.Value && e.Kind == "closed-set" && position == "dictionary-key" && c.Get("json.DictionaryKeyPolicy") is not (null or "default")) return c.Inapplicable(Cite.NotPromised("a-closed-set-key-is-its-name"), "write"); + if (p.Value != v.Value) return $"JSON differs: wrapped {Cut(p.Value)} vs value object {Cut(v.Value)}"; + c.Note(NoteKeys.Compared, "write"); + return null; + } + + /// <summary> + /// The wrapped twin's document, with the row's input.json, read into both shapes (and what each read written again): each reads it as the + /// other does, or refuses as a document says; (1) and (3) refusing alike is decided before (Sides). + /// </summary> + static string? Read(Check c, Entry e, string position, string input, string text, string jp, Sides.Answer p, Sides.Answer v) + { + var stored = c.Get("json.Converters") == "StoredJsonConverterFactory"; + if (p.Refused && v.Refused) return c.Inconclusive($"read {input}: both refuse, not alike: the wrapped type {p}, the value object {v}"); + if (!p.Refused && v.Refused) { - // intended: a struct VO over... none. DateTime is written as UTC; sample is UTC so equal. - return $"JSON differs: wrapped {Cut(jp)} vs value object {Cut(jv)}"; + var xv = v.Thrown!; + if (c.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && xv is NotSupportedException) return c.Refused(Cite.NotPromised("preserved-references-over-a-walked-class"), "read"); + // The documented refusals, each the JsonException the generated converter throws. + if (!(xv is JsonException) && ((Validated(e) && input == "refused-by-create" && !stored) || (input == "null" && e.Vo.IsValueType) || (e.Kind == "closed-set" && input != "valid"))) + return $"input {input}: the refusal is {xv.GetType().Name}, not JsonException: {Cut(xv.Message)}"; + if (Validated(e) && input == "refused-by-create" && !stored) return c.Refused(Cite.NotPromised("a-validated-value-object-refuses-what-create-refuses"), $"read {input}"); + if (input == "null" && e.Vo.IsValueType) return c.Refused(Cite.NotPromised("a-struct-refuses-a-json-null"), $"read {input}"); + if (e.Kind == "closed-set" && input != "valid") return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#matching-parsing-and-binding"), $"read {input}"); // "Unknown names are rejected." + return $"input {input}: wrapped read it, value object threw {xv.GetType().Name}: {Cut(xv.Message)}\n text={Cut(text)}"; } - // reading - var text = Input(input, e, oPlain, jp!, position); - var (dp, xp) = De(text, hp, oPlain); var (dv, xv) = De(text, hv, o); - if (xp is not null && xv is not null) + // a reference-type value object takes JSON null as null, as any class does + if (p.Refused && input == "null" && Make.AdmitsNull(e.Vo) && !Make.AdmitsNull(e.Plain)) return c.Inapplicable(Cite.Docs("docs/json.md#null-and-missing"), "read null"); + if (p.Refused) return $"input {input}: value object read what the wrapped type refused ({p.Thrown?.GetType().Name}: {Cut(p.Thrown?.Message)})\n text={Cut(text)}"; + if (Validated(e) && input == "refused-by-create" && text != jp && !stored) + return $"validated value object accepted the refused value{(position == "dictionary-key" ? " as a dictionary key" : "")}: {Cut(text)}"; + if (p.Value != v.Value) return $"input {input}: read-back differs: wrapped {Cut(p.Value)} vs value object {Cut(v.Value)}"; + c.Note(NoteKeys.Compared, "read " + input); + return null; + } + + /// <summary> + /// Under an application's converter for the value object, the generated converter does not run: the value object's document is that + /// converter's, and it reads back as written under the row's options. Where the value object's document cannot be written, a converter + /// the application registers for the wrapped type at the same position is the baseline. + /// </summary> + static string? OwnVoRead(Check c, Entry e, Held hp, Held hv, JsonSerializerOptions oPlain, JsonSerializerOptions o, string? jv, Exception? ev, Exception? ep) + { + var oOwn = new JsonSerializerOptions(oPlain); + oOwn.Converters.Insert(0, (System.Text.Json.Serialization.JsonConverter)Activator.CreateInstance(typeof(OwnWrappedConverter<>).MakeGenericType(Make.Held(e.Plain)), JsonRow.Inner(e))!); + var (jo, eo) = Ser(hp, oOwn); + if (ev is not null) { - if (xp.GetType() != xv.GetType() && !(xp is JsonException && xv is JsonException)) { r.Note("both-threw-differently(read):" + xp.GetType().Name + "/" + xv.GetType().Name); return null; } - r.Note("read-both-threw:" + xp.GetType().Name); return null; + // System.Text.Json refuses any custom converter there (MaxDepth counts its value one level deeper, KB-040), the wrapped + // type's under a converter of the application's alike + if (ep is not null || (eo is not null && eo.GetType() == ev.GetType())) return c.Inconclusive($"the application's converter cannot write here, for the wrapped type too: {ev.GetType().Name}"); + return $"own VO converter row: VO serialization threw {ev.GetType().Name}: {ev.Message}"; } - if (xp is null && xv is not null) + c.Note(NoteKeys.OwnVo); + var (back, eb) = De(jv!, hv, o); + if (eb is not null) { - if (preserveOwn && e.Wrapped == "own-record-class" && xv is NotSupportedException) return null; - if (Validated(e) && input == "refused-by-create" && r.Get("json.Converters") != "StoredJsonConverterFactory") return xv is JsonException ? null : $"refusal is {xv.GetType().Name}, not JsonException: {xv.Message}"; - if (input == "null" && e.Vo.IsValueType) return null; // intended: a struct value object refuses JSON null - if (e.Kind == "closed-set" && input != "valid") return xv is JsonException ? null : $"closed set refusal is {xv.GetType().Name}"; // a closed set has the members it has - return $"input {input}: wrapped read it, value object threw {xv.GetType().Name}: {Cut(xv.Message)}\n text={Cut(text)}"; + // the same converter for the wrapped type, at the same position: its own document does not read back under these options either + var ebOwn = jo is null ? eo : De(jo, hp, oOwn).Ex; + if (ebOwn is not null && ebOwn.GetType() == eb.GetType()) return c.Inconclusive($"the application's converter's document does not read back under these options, for the wrapped type too: {eb.GetType().Name}"); + return "own VO converter: read-back threw " + eb.GetType().Name + ": " + eb.Message; } - if (xp is not null && xv is null && input == "null" && !e.Vo.IsValueType && e.Plain.IsValueType) return null; // a reference-type value object takes JSON null as null, as any class does (README: "a JSON null for a struct value object") - if (xp is not null && xv is null) return $"input {input}: value object read what the wrapped type refused ({xp.GetType().Name}: {Cut(xp.Message)})\n text={Cut(text)}"; - if (Validated(e) && input == "refused-by-create" && text != jp && r.Get("json.Converters") != "StoredJsonConverterFactory" && position != "dictionary-key") - return $"validated value object accepted the refused value: {Cut(text)}"; - if (dp != dv) return $"input {input}: read-back differs: wrapped {Cut(dp)} vs value object {Cut(dv)}"; - r.Note("compared"); return null; + if (back != jv) return $"own VO converter round trip differs: {jv} vs {back}"; + return null; } static string Cut(string? s) => s is null ? "(null)" : s.Length > 200 ? s[..200] + "..." : s; } diff --git a/tools/probe/corpus/harness/PreserveGuard.cs b/tools/probe/corpus/harness/PreserveGuard.cs new file mode 100644 index 00000000..2f046f3d --- /dev/null +++ b/tools/probe/corpus/harness/PreserveGuard.cs @@ -0,0 +1,252 @@ +// Claim C10-preserve-guard (src/CodoMetis.TypeKit.Generators/README.md#json): "Preserved references (ReferenceHandler.Preserve) throw a +// NotSupportedException where the serializer could write $id or $ref inside the underlying value. That's a class it walks as an object [...] +// These are unaffected: a value the serializer writes with a value converter (a number, a string, a Guid, a Uri), anything a converter of +// your own writes, on the options or on one property, a struct of such values, and ReferenceHandler.IgnoreCycles." +// Oracle: claim for the guarded rows (a value object over a class the serializer walks, under Preserve, with no converter of the +// application's for it: NotSupportedException naming the type and Preserve, both directions, never a document); differential for the +// unaffected rows (the value object's document is the wrapped type's under the same options, never an $id twice, and every read agrees +// with the wrapped type's, a refusal being the section's: a JsonException naming the value object, with no inner exception). +using System.Text.Json; +using Probes.Shapes; +namespace Probes.Json; + +public static class PreserveGuard +{ + public static string? Check(Row r) + { + var handler = r["handler"]; + var want = handler == "none" ? "default" : handler; + var row = r; + if (r.Get("json.ReferenceHandler") is { } jr && jr != want) + { + // one setting under two names: the claim's handler decides (claims.json excludes the differing pairs) + r.Substituted("json.ReferenceHandler", want, "handler is the same setting"); + row = r.With("json.ReferenceHandler", want); + } + var kind = r["shape.kind"]; + var e = kind == "closed-set" ? Registry.Get(kind, "-") : Registry.Get(kind, r["shape.wrapped"]); + var position = r["shape.position"]; + var containing = position == "root" ? "class" : r["shape.containing"]; + _ = r["input.json"]; // the row's input, which every check of the row runs under; the reads apply it + var plain = e.Valid(); var voValue = Make.Vo(e, plain); + var (hp, hv) = Sides.Twins(position, containing, e, plain, voValue); + var o = JCtx.Apply(row, e, hp.Declared, hv.Declared); + // A closed set travels as its name, which no naming policy renames and no converter for strings writes (docs/closed-sets.md). + var op = kind == "closed-set" && (row.Get("json.DictionaryKeyPolicy") is not (null or "default") || row.Is("json.Converters", "own-for-wrapped")) + ? JCtx.Apply(row.With("json.DictionaryKeyPolicy", "default").With("json.Converters", row.Is("json.Converters", "own-for-wrapped") ? "default" : row["json.Converters"]), e, hp.Declared, hv.Declared) + : o; + var ownWrapped = row.Is("json.Converters", "own-for-wrapped"); + var ownVo = row.Is("json.Converters", "own-for-value-object"); + var guarded = want == "Preserve" && e.Wrapped == "own-record-class" && !ownWrapped && !ownVo; + // The obligation the row's handler and converters select: a converter of the application's, the guarded class under Preserve, + // IgnoreCycles, and otherwise a value the serializer writes with a value converter, under Preserve or with no handler at all. + var obligation = ownWrapped || ownVo ? "own-converters-unaffected" : guarded ? "refused-where-references-could-be-written" + : want == "IgnoreCycles" ? "ignore-cycles-unaffected" : "value-converters-unaffected"; + return guarded ? Guarded(r, obligation, e, hp, hv, o) : Unaffected(r, obligation, e, hp, hv, op, o, ownVo); + } + + /// <summary> + /// The guarded rows: NotSupportedException naming the wrapped type and Preserve, in both directions, never a document. The read of the + /// wrapped type's document is three-sided (Sides): where the twin without TypeKit refuses it alike, the serializer refused the document + /// before any converter ran. + /// </summary> + static string? Guarded(Row r, string obligation, Entry e, Held hp, Held hv, JsonSerializerOptions o) + { + var wrapped = Make.Held(e.Plain); + string? Refusal(Exception? x, string what) + { + if (x is not NotSupportedException) return $"{what}: expected NotSupportedException naming {wrapped.Name} under ReferenceHandler.Preserve, got {(x is null ? "no exception" : Jx.Ex(x))}"; + if (!x.Message.Contains(wrapped.FullName!) || !x.Message.Contains("ReferenceHandler.Preserve")) return $"{what}: the NotSupportedException does not name {wrapped.FullName} and ReferenceHandler.Preserve: {x.Message}"; + return null; + } + var (jv, xv) = Jx.Ser(hv, o); + var (jp, xp) = Jx.Ser(hp, o); + var fw = r.Check(obligation, Baseline.Refusal, c => + { + if (jv is not null) + { + // the options may leave the member out of the document, for the wrapped type too: then no converter ran, and nothing was refused + if (jp is not null && !Jx.Tokens(jp).Any(t => t.Text == "x") && !Jx.Tokens(jv).Any(t => t.Text == "x")) + { c.Note(NoteKeys.JsonMemberLeftOutByOptions, "write: " + Jx.Cut(jv)); return c.Inconclusive("write: the options leave the member out, for the wrapped type too: " + Jx.Cut(jv)); } + return Refusal(null, "write") + $"; it wrote {Jx.Cut(jv)}" + (DuplicateId(jv) is { } dup ? $" with {dup} twice" : ""); + } + if (Refusal(xv, "write") is { } f) return f; + c.Note(NoteKeys.Compared, "write refused"); + return null; + }); + if (xp is not null) + return fw ?? r.Check(obligation, Baseline.Refusal, c => c.Inconclusive("read: the wrapped type's document is refused under these options, so there is none to read: " + Jx.Ex(xp))); + // reading: the wrapped type's own document, with the row's input applied + var input = r["input.json"]; + var text = JsonParity.InputFor(input, e, Jx.Full(o), Jx.Ser(hp, Jx.Full(o)).Json ?? jp!, r["shape.position"]); + var (vp, ep) = Jx.De(text, hp.Declared, o); + var (vv, ev) = Jx.De(text, hv.Declared, o); + var one = Sides.WithoutTypeKit(o); + var r1 = new Sides.Answer("read", null, Jx.De(text, hp.Declared, one).Ex); + var r3 = new Sides.Answer("read", null, ev); + var fr = Sides.Check(r, obligation, Baseline.Refusal, r1.Refused && Sides.Alike(r1, r3) ? Sides.Measured(r1, "the twin's read under the application's options, less TypeKit's converters") : null, r3, null, null, c => + { + if (ev is null) + { + // no converter ran: the read holds none of the wrapped value (a JSON null for a class, a member the options do not read) + if (vv is not null && Jx.Tokens(JsonSerializer.Serialize(vv, hv.Declared, Jx.Plain)).Any(t => t.Text == "x")) + return $"read {input}: the value object over {Make.Held(e.Plain).Name} read a document under Preserve: {Jx.Cut(text)}"; + c.Note(NoteKeys.JsonMemberLeftOutByOptions, "read " + input); + return c.Inconclusive($"read {input}: no converter ran, the read holds none of the wrapped value"); + } + if (ev is NotSupportedException) { if (Refusal(ev, "read " + input) is { } f) return f; c.Note(NoteKeys.Compared, "read " + input + " refused"); return null; } + // refused before the guard: a JsonException naming the value object, with no inner exception (Generators README#json) + if (ev is not JsonException || ev.InnerException is not null) return Refusal(ev, "read " + input) + $"\n text={Jx.Cut(text)}"; + if (!ev.Message.Contains(Make.Held(e.Vo).Name)) return Refusal(ev, "read " + input) + $"\n text={Jx.Cut(text)}"; + c.Note(NoteKeys.Compared, "read " + input + ": refused naming the value object before the guard"); + return null; + }); + return fw ?? fr; + } + + /// <summary> + /// The unaffected rows, three-sided (Sides; C10 promises these are unaffected, so side (1) runs on every row): the value object's + /// document (3) is the wrapped type's under the same options (2), which is the twin's without TypeKit's converters (1), never an $id + /// twice, and its read agrees with the wrapped type's (ReadAlike); under a converter of the application's for the value object, that + /// converter's document reads back and reads the row's input alike with and without the reference handler. + /// </summary> + static string? Unaffected(Row r, string obligation, Entry e, Held hp, Held hv, JsonSerializerOptions op, JsonSerializerOptions o, bool ownVo) + { + var one = Sides.WithoutTypeKit(op); + var w1 = Sides.Write(hp, one); var w2 = Sides.Write(hp, op); var w3 = Sides.Write(hv, o); + var (jp, xp) = (w2.Value, w2.Thrown); var (jv, xv) = (w3.Value, w3.Thrown); + // byte[] is base64 by the serializer's own rule (KB-038), an array of value objects a JSON array: unaffected means the value + // object's document reads back as written + var byteArray = e.Wrapped == "byte" && r.Is("shape.position", "array-element") && jv is not null && jp is not null && jv != jp; + var written = Sides.Framework(r, e, hp, one, w1, w3); + var fw = Sides.Check(r, obligation, ownVo || byteArray ? Baseline.RoundTrip : Baseline.Twin, written, w3, w1, w2, c => + { + if (xp is not null && xv is not null) return $"write: both threw, not alike: the wrapped type {w2}, the value object {w3}"; + if (xv is not null) return $"write: the value object threw {Jx.Ex(xv)} where the wrapped type wrote {Jx.Cut(jp)}"; + // the application's converter for the value object writes its own document: its baseline is that document read back, whatever + // the twin's writes ("anything a converter of your own writes" is unaffected) + if (xp is not null && !ownVo) return $"write: the value object wrote {Jx.Cut(jv)} where the wrapped type threw {Jx.Ex(xp)}"; + if (DuplicateId(jv!) is { } dup) return $"write: $id {dup} twice in {Jx.Cut(jv)}"; + if (ownVo) + { + c.Note(NoteKeys.OwnVo); // the application's converter for the value object writes its own document; it reads it back, unaffected + var (jf, _) = Jx.Ser(hv, Jx.Full(o)); + var (_, xr) = Jx.De(jf ?? jv!, hv.Declared, o); + if (xr is not null) return "own converter for the value object: its document does not read back: " + Jx.Ex(xr); + return null; + } + if (byteArray) + { + var (back, xb) = Jx.De(jv!, hv.Declared, o); + if (xb is not null) return "write: the array of byte value objects does not read back: " + Jx.Ex(xb); + var again = JsonSerializer.Serialize(back, hv.Declared, o); + if (again != jv) return $"round trip: {Jx.Cut(jv)} read back as {Jx.Cut(again)}"; + c.Note(NoteKeys.Compared, "an array of byte value objects round-trips as a JSON array"); + return null; + } + if (jv != jp) return $"write: the value object wrote {Jx.Cut(jv)}, the wrapped type {Jx.Cut(jp)}"; + c.Note(NoteKeys.Compared, "write"); + return null; + }); + if (ownVo) return fw ?? OwnReadsInput(r, obligation, e, hp, hv, o); + // the wrapped type's base64 document is no array of value objects (KB-038): the round trip above holds the value object to its own + if (byteArray) return fw ?? r.Check(obligation, Baseline.Twin, c => c.Inapplicable(Cite.Fact("KB-038"), "read: the wrapped type's document is a base64 string")); + // the value object's document is refused as the framework refuses it, so is its read: the write's refusal, as it is + if (written is not null && Sides.Alike(written.Answer, w3)) return fw ?? Sides.Check(r, obligation, Baseline.Twin, written, w3, null, null, _ => (string?)null); + var full = Jx.Ser(hp, Jx.Full(op)).Json ?? jp; + if (full is null) return fw ?? r.Check(obligation, Baseline.Twin, c => c.Inconclusive("read: the wrapped type is not written under these options, so there is no document to read")); + var input = r["input.json"]; + var text = JsonParity.InputFor(input, e, Jx.Full(op), full, r["shape.position"]); + var r1 = Sides.Read(text, hp, one); var r2 = Sides.Read(text, hp, op); var r3 = Sides.Read(text, hv, o); + var fr = Sides.Check(r, obligation, Baseline.Twin, Sides.Framework(r, e, hp, one, r1, r3), r3, r1, r2, c => ReadAlike.Check(c, e, input, text, r2, r3, hp, hv, op, o)); + return fw ?? fr; + } + + /// <summary> + /// The application's converter for the value object reads the row's input inside its own spelling ("vo:" and the wrapped + /// type's JSON) alike under the row's reference handler and under none: "anything a converter of your own writes" is unaffected + /// (Generators README#json). Each side writes its own document, so Preserve's metadata is its own, and only the value is rewritten. + /// Where the handler's document is refused as the framework refuses a converter's value (KB-040), the check is that refusal. + /// </summary> + static string? OwnReadsInput(Row r, string obligation, Entry e, Held hp, Held hv, JsonSerializerOptions o) + { + var input = r["input.json"]; + var inner = JsonRow.Inner(e); + var wrapped = JsonSerializer.Serialize(Make.Unwrap(e, Make.Vo(e, e.Valid())), Make.Held(e.Plain), inner); + var value = "vo:" + JsonParity.InputFor(input, e, inner, wrapped, "root"); + var bare = new JsonSerializerOptions(o) { ReferenceHandler = null }; + (string? Read, string? Refused, Sides.Answer? Write) Through(JsonSerializerOptions x) + { + var w = Sides.Write(hv, Jx.Full(x)); + if (w.Refused) return (null, "write: " + w.Thrown!.GetType().Name, w); + var own = Jx.Tokens(w.Value!).Where(t => t.Type is JsonTokenType.String or JsonTokenType.PropertyName && t.Text is { } s && s.StartsWith("vo:", StringComparison.Ordinal)).ToList(); + if (own.Count == 0) return (null, null, null); + var (v, rx) = Jx.De(Jx.Rewrite(w.Value!, own, _ => JsonSerializer.Serialize(value)), hv.Declared, x); + if (rx is not null) return (null, "read: " + rx.GetType().Name, null); + return (JsonSerializer.Serialize(v, hv.Declared, bare), null, null); + } + var handled = Through(o); var unhandled = Through(bare); + // the handler's document, written by the application's converter, refused as the framework refuses any converter's value there + var framework = (handled.Write ?? unhandled.Write) is { } refused ? Sides.WrittenByAConverter(r, e, hp, Sides.WithoutTypeKit(Jx.Full(handled.Write is null ? bare : o)), refused) : null; + return Sides.Check(r, obligation, Baseline.RoundTrip, framework, (handled.Write ?? unhandled.Write) ?? new Sides.Answer("write", null), null, null, c => + { + if (handled.Read is null && handled.Refused is null && unhandled.Read is null && unhandled.Refused is null) + { c.Note(NoteKeys.JsonMemberLeftOutByOptions, "own converter: the options write no member"); return c.Inconclusive("own converter: the options write no member to read"); } + if (handled.Read != unhandled.Read || handled.Refused != unhandled.Refused) + return $"read {input} through the application's converter: {handled.Read ?? handled.Refused ?? "no member"} under ReferenceHandler {c["handler"]}, {unhandled.Read ?? unhandled.Refused ?? "no member"} without one"; + c.Note(NoteKeys.Compared, $"read {input} through the application's converter, alike with and without the reference handler: {Jx.Cut(handled.Read ?? handled.Refused)}"); + return null; + }); + } + + /// <summary>The first $id value a document holds twice, or null.</summary> + public static string? DuplicateId(string json) + { + var t = Jx.Tokens(json); var seen = new HashSet<string>(); + for (var i = 0; i + 1 < t.Count; i++) + if (t[i].Type == JsonTokenType.PropertyName && t[i].Text == "$id" && !seen.Add(t[i + 1].Text ?? t[i + 1].Raw)) return t[i + 1].Text; + return null; + } +} + +/// <summary> +/// One read of the same text into the wrapped type's shape (2) and the value object's (3), each written again to be compared: they agree, or +/// differ only as the documents say (a struct value object refuses a JSON null, a sealed record reads it as null, a validated one refuses what +/// Create refuses, a closed set has only its members); a refusal of the value object's is a JsonException naming it, with no inner exception +/// (Generators README#json). Side (1) refusing alike with (3) is decided before (Sides). +/// </summary> +public static class ReadAlike +{ + public static string? Check(Check c, Entry e, string input, string text, Sides.Answer p, Sides.Answer v, Held hp, Held hv, JsonSerializerOptions op, JsonSerializerOptions ov) + { + var voName = Make.Held(e.Vo).Name; + string? Named(Exception x) => x is JsonException && x.Message.Contains(voName) && x.InnerException is null ? null + : $"read {input}: the value object's refusal is {Jx.Ex(x)}{(x.InnerException is null ? "" : " with an inner exception")}, not a JsonException naming {voName}\n text={Jx.Cut(text)}"; + var stored = c.Is("json.Converters", "StoredJsonConverterFactory"); + if (!p.Refused && !v.Refused) + { + if (e.Kind.StartsWith("validated") && input == "refused-by-create" && !stored && e.Refused() is not null && text != Jx.Ser(hp, op).Json && !c.Is("shape.position", "dictionary-key")) + return $"read {input}: the validated value object read what Create refuses\n text={Jx.Cut(text)}"; + if (p.Value != v.Value) return $"read {input}: read back differs: wrapped {Jx.Cut(p.Value)} vs value object {Jx.Cut(v.Value)}\n text={Jx.Cut(text)}"; + c.Note(NoteKeys.Compared, "read " + input); return null; + } + if (p.Refused && v.Refused) + { + if (v.Operation != "read") return $"read {input}: both refuse, not alike: the wrapped type {p}, the value object {v}"; + if (Named(v.Thrown!) is { } f) return f + $"; the wrapped type: {p}"; + c.Note(NoteKeys.Compared, "read " + input + ": both refuse, the value object naming itself"); return null; + } + if (v.Refused) + { + var intended = v.Operation == "read" && ((input == "null" && e.Vo.IsValueType) || (e.Kind.StartsWith("validated") && input == "refused-by-create" && !stored) || (e.Kind == "closed-set" && input != "valid")); + if (!intended) return $"read {input}: the wrapped type read it, the value object refused: {v} {Jx.Ex(v.Thrown!)}\n text={Jx.Cut(text)}"; + if (Named(v.Thrown!) is { } f) return f; + c.Note(NoteKeys.Compared, "read " + input + ": refused as documented"); return null; + } + // the wrapped type refused and the value object read: only a JSON null for a sealed record, which reads it as any class does + if (input == "null" && Make.AdmitsNull(e.Vo) && !Make.AdmitsNull(e.Plain) && Jx.De(text, hv.Declared, ov).Value is { } read + && Jx.Tokens(JsonSerializer.Serialize(read, hv.Declared, Jx.Plain)).Any(t => t.Type == JsonTokenType.Null)) + { c.Note(NoteKeys.Compared, "read null: the sealed record reads null, as any class does"); return null; } + return $"read {input}: the value object read what the wrapped type refused ({p})\n text={Jx.Cut(text)}"; + } +} diff --git a/tools/probe/corpus/harness/Registry.cs b/tools/probe/corpus/harness/Registry.cs index 42181be7..abe2a1d8 100644 --- a/tools/probe/corpus/harness/Registry.cs +++ b/tools/probe/corpus/harness/Registry.cs @@ -16,7 +16,7 @@ public static Entry Of(Row r) { // The parameter or body declared nullable on both sides (T? against wrapped?): the dimension G1 exposed. static Type N(Type t) => t.IsValueType && Nullable.GetUnderlyingType(t) is null ? typeof(Nullable<>).MakeGenericType(t) : t; - r.Note("binding:nullable"); + r.Note(NoteKeys.BindingNullable); e = e with { Vo = N(e.Vo), Plain = N(e.Plain) }; } return e; diff --git a/tools/probe/corpus/harness/Row.cs b/tools/probe/corpus/harness/Row.cs index 0d96de72..79703c65 100644 --- a/tools/probe/corpus/harness/Row.cs +++ b/tools/probe/corpus/harness/Row.cs @@ -1,26 +1,43 @@ -// Row plumbing shared by every probe: rows come from `prober rows --claim X` (JSON, copied to Probes/rows/X.json); -// each probe records one line per row to $PROBE_OUT/<claim>.jsonl (idx, status pass|fail, note) so the ledger is written by script. +// Row plumbing shared by every probe: each probe's rows are its own file, written by `prober rows --probe P` (the corpus's +// rows/P.json.gz, unpacked by the replay to Probes/rows/P.json); each probe records one line per row to $PROBE_OUT/<label>.jsonl +// (idx, category, applied settings, reasons, findings, note, and the checks the row ran), which the replay reads. using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.RegularExpressions; namespace Probes.Shapes; /// <summary> /// What a row actually did, shared by the row and every copy made of it (With, Fresh): the axes the harness -/// read, the settings it substituted, and how the row ended. The ledger credits only the axes read, at the -/// value that applied, and only for a row that ended as a pass. +/// read, the settings it substituted, how the row ended, the open findings it stepped around, and the checks it +/// ran (<see cref="Row.Check(string, Baseline?, Func{Check, string?})"/>), each with evidence of its own. The ledger +/// credits only the axes read, at the value that applied, and only for a check that ended as a pass. /// </summary> public sealed class Evidence { public readonly HashSet<string> Read = new(); public readonly Dictionary<string, string> Applied = new(); + public readonly SortedSet<string> Findings = new(StringComparer.Ordinal); + /// <summary>How the row ended: the highest of its own outcomes and its checks'.</summary> public string Category = "pass"; + /// <summary>How the row ended outside any check: its own notes, a failure it returned, an exception it threw.</summary> + public string Own = "pass"; public readonly List<string> Reasons = new(); - static readonly string[] Order = ["pass", "inapplicable", "expected-refusal", "inconclusive", "harness-error", "violation"]; + /// <summary>The checks the row ran, in the order they ended.</summary> + public readonly List<Check> Checks = new(); + static readonly string[] Order = ["pass", "inapplicable", "expected-refusal", "inconclusive", "known-finding", "harness-error", "violation"]; + public static int Rank(string category) => + Array.IndexOf(Order, category) is var i and >= 0 ? i : throw new ArgumentException("no outcome category " + category, nameof(category)); public void Raise(string category, string reason) { - if (Array.IndexOf(Order, category) > Array.IndexOf(Order, Category)) Category = category; + if (Rank(category) > Rank(Own)) Own = category; + Lift(category); Reasons.Add(category + ":" + reason); } + /// <summary>Raises the row's category to a check's, without a reason of the row's own: the check keeps its reasons.</summary> + public void Lift(string category) + { + if (Rank(category) > Rank(Category)) Category = category; + } } public sealed class Row @@ -28,125 +45,522 @@ public sealed class Row readonly Dictionary<string, string> d; public Evidence Ev { get; } public int Index { get; } - public Row(int index, Dictionary<string, string> d) : this(index, d, new Evidence()) { } - Row(int index, Dictionary<string, string> d, Evidence ev) { Index = index; this.d = d; Ev = ev; } + /// <summary>The claim whose rows file holds the row, whose declarations <see cref="Check(string, Baseline?, Func{Check, string?})"/> applies; null for a row in hand.</summary> + public string? Claim { get; } + public Row(int index, Dictionary<string, string> d, string? claim = null) : this(index, d, new Evidence(), claim) { } + Row(int index, Dictionary<string, string> d, Evidence ev, string? claim) { Index = index; this.d = d; Ev = ev; Claim = claim; } public string this[string axis] => d.TryGetValue(axis, out var v) ? Mark(axis, v) : throw new KeyNotFoundException($"row has no axis {axis}"); public string? Get(string axis) => d.TryGetValue(axis, out var v) ? Mark(axis, v) : null; public bool Is(string axis, string value) => d.TryGetValue(axis, out var v) && Mark(axis, v) == value; /// <summary>Every axis, each marked read: a caller that enumerates is applying them (the JSON options builder).</summary> public IEnumerable<KeyValuePair<string, string>> All { get { foreach (var kv in d) { Mark(kv.Key, kv.Value); yield return kv; } } } - string Mark(string axis, string v) { lock (Ev) Ev.Read.Add(axis); return v; } + /// <summary> + /// Marks an axis read: for the check of this row that is running, if one is (a helper handed the row inside a + /// check's body reads for that check), else for the row, whose reads every check of it ran under. + /// </summary> + string Mark(string axis, string v) + { + if (Scope.Running(Ev) is { } c) c.MarkRead(axis); + else lock (Ev) Ev.Read.Add(axis); + return v; + } + /// <summary>The value the row holds for an axis, without marking it read: a check marks its own.</summary> + internal string? Raw(string axis) => d.TryGetValue(axis, out var v) ? v : null; + /// <summary>Every axis and its value, none marked read: what a fact's fixture is matched against (Sides.Settings), whose covering answer then reads its own.</summary> + internal IReadOnlyDictionary<string, string> Values => d; /// <summary>The setting ran as <paramref name="actual"/>, not as the row asked: only the actual value earns credit.</summary> - public void Substituted(string axis, string actual, string reason) { lock (Ev) Ev.Applied[axis] = actual; Note($"substituted:{axis}={actual}({reason})"); } - public void Outcome(string category, string reason) { lock (Ev) Ev.Raise(category, reason); } - /// <summary>The settings that ran: the axes the harness read, at the value that applied.</summary> + public void Substituted(string axis, string actual, string reason) + { + if (Scope.Running(Ev) is { } c) { c.Substituted(axis, actual, reason); return; } + lock (Ev) Ev.Applied[axis] = actual; + Note(NoteKeys.Substituted, $"{axis}={actual}, {reason}"); + } + public void Outcome(string category, string reason) + { + if (Scope.Running(Ev) is { } c) c.Outcome(category, reason); + else lock (Ev) Ev.Raise(category, reason); + } + /// <summary>The settings that ran: the axes the harness read outside any check, at the value that applied.</summary> public Dictionary<string, string> AppliedSettings() { lock (Ev) return Ev.Read.Where(d.ContainsKey).ToDictionary(a => a, a => Ev.Applied.TryGetValue(a, out var v) ? v : d[a]); } public string Notes { get; private set; } = ""; - public void Note(string n) + /// <summary> + /// Records what a check saw, by a key of <see cref="NoteKeys"/>; the key alone decides the outcome, the + /// detail only explains it. Inside a check's body the note is the check's. + /// </summary> + public void Note(NoteKeys key, string? detail = null) { + if (Scope.Running(Ev) is { } c) { c.Noted(key, detail); return; } + var n = detail is null ? key.Key : $"{key.Key}({detail})"; if (!Notes.Split(';').Contains(n)) Notes += (Notes.Length == 0 ? "" : ";") + n; - if (NoteCategories.Of(n) is { } c) Outcome(c, n); + if (key.Outcome != "pass") Outcome(key.Outcome, n); + } + /// <summary> + /// Steps around an open finding: the row (or, inside a check's body, the check) ends as known-finding and earns + /// nothing. The replay fails a probe that skips for a finding the ledger has fixed or dismissed, and the release + /// gate refuses while any skips. + /// </summary> + public void Skip(string finding, string why) + { + lock (Ev) Ev.Findings.Add(finding); + Outcome("known-finding", $"{finding}: {why}"); } public System.Text.StringBuilder Log { get; } = new(); - public Row Fresh() => new Row(Index, new Dictionary<string, string>(d), Ev); - public Row With(string axis, string value) { var c = new Dictionary<string, string>(d) { [axis] = value }; return new Row(Index, c, Ev); } + public Row Fresh() => new Row(Index, new Dictionary<string, string>(d), Ev, Claim); + public Row With(string axis, string value) { var c = new Dictionary<string, string>(d) { [axis] = value }; return new Row(Index, c, Ev, Claim); } public override string ToString() => string.Join(" ", d.Where(kv => kv.Value != "default").OrderBy(kv => kv.Key).Select(kv => $"{kv.Key}={kv.Value}")); + + /// <summary>The check running in this flow, so a Row whose method is named Check reaches the type's flow by another name.</summary> + static class Scope + { + public static Check? Running(Evidence ev) => Probes.Shapes.Check.Running(ev); + public static Check? Enter(Check c) => Probes.Shapes.Check.Enter(c); + public static void Leave(Check? outer) => Probes.Shapes.Check.Leave(outer); + } + + /// <summary> + /// Runs one check of the row: one obligation of the claim, compared against one <paramref name="baseline"/>, with + /// reads, substitutions, notes and an outcome of its own. A failure the body returns is a violation; an exception + /// is a harness error, recorded and not rethrown, so the row's other checks still run (and null is returned, so + /// no exception ever reads as a violation); otherwise the highest of its notes, else a pass, and a pass with no + /// baseline is the harness error <c>pass-without-baseline</c>: a pass is a comparison that ran. The row's category + /// is raised to the check's. Returns the failure, so a probe may stop the row at its first violation or run every + /// check. A body that only returns null or throws names its form, <c>_ => (string?)null</c>, or the two overloads are ambiguous. + /// Before the body, the claim's declarations of where the obligation does not apply are read (claims.json, staged + /// beside the rows): where one matches the row's values, the check is inapplicable with the declaration's citation + /// and the body does not run. + /// </summary> + public async Task<string?> Check(string obligation, Baseline? baseline, Func<Check, Task<string?>> body) + { + var c = new Check(this, obligation, baseline); + var outer = Scope.Enter(c); + string? fail = null; Exception? thrown = null; + try { if (!Declared(c)) fail = await body(c); } + catch (Exception ex) { thrown = ex; } + finally { Scope.Leave(outer); } + return Close(c, fail, thrown); + } + + /// <summary>The synchronous form of <see cref="Check(string, Baseline?, Func{Check, Task{string?}})"/>.</summary> + public string? Check(string obligation, Baseline? baseline, Func<Check, string?> body) + { + var c = new Check(this, obligation, baseline); + var outer = Scope.Enter(c); + string? fail = null; Exception? thrown = null; + try { if (!Declared(c)) fail = body(c); } + catch (Exception ex) { thrown = ex; } + finally { Scope.Leave(outer); } + return Close(c, fail, thrown); + } + + /// <summary> + /// Applies the first declaration of the check's obligation whose every axis the row holds at a value it lists: the + /// check is inapplicable with the declaration's citation, those axes read for it. True when one matched. + /// </summary> + bool Declared(Check c) + { + foreach (var (axes, cite) in Rows.Declarations(Claim, c.Obligation)) + if (axes.All(kv => d.TryGetValue(kv.Key, out var v) && kv.Value.Contains(v))) + { + c.Declared(cite, string.Join(" ", axes.Keys.Order(StringComparer.Ordinal).Select(a => $"{a}={c[a]}"))); + return true; + } + return false; + } + + string? Close(Check c, string? fail, Exception? thrown) + { + if (thrown is not null) c.Outcome("harness-error", "UNEXPECTED " + thrown.GetType().Name + ": " + thrown.Message); + else if (fail is not null) { c.Failure = fail; c.Outcome("violation", fail); } + if (c.Category == "pass" && c.Against is null) c.Outcome("harness-error", "pass-without-baseline"); + lock (Ev) { Ev.Checks.Add(c); Ev.Lift(c.Category); } + return thrown is null ? fail : null; + } } /// <summary> -/// The outcome a harness note stands for. A row whose checks returned no failure is a pass only if none of -/// its notes says a check was skipped (inapplicable) or ended in a refusal (expected refusal): a refusal is -/// evidence about refusing, not about serializing or binding successfully. +/// One check of a row (<see cref="Row.Check(string, Baseline?, Func{Check, string?})"/>): the obligation it checks, the +/// baseline it compares against, the axes it read at the value that applied, its notes and how it ended. Its evidence +/// is its own; the row's reads, made outside any check, are what every check of the row ran under. /// </summary> -public static class NoteCategories +public sealed class Check { - static readonly string[] Refusal = - [ - "both-threw", "read-both-threw", "ser-both-threw", "ser-refused", "read-back-refused", "refused-for-wrapped-too", - "preserve-refused-by-stj", "aspnet-refuses", "preserve-documented", "stj-maxdepth", "mvc-400-unwritable", - "validated-refuses-type-limit", "plain-read-back-fails-too", "own-vo-converter-threw", "plain-unbindable", - "documented-diff", "class-vo-takes-null", "own-vo:read-back-refused", - ]; - static readonly string[] Inapplicable = - [ - "-NA", "no-text:", "cannot-bind", "not-bindable-from-text", "not-in-api-explorer", "body-binding-skipped", - "converter-not-described", "closed-set-differs", "closed-set-vs-own-wrapped-converter", "stored-factory-skips-validation", - "maxvalidationdepth1", "route-text-unsafe", "doc:F2", "wrapped-round-trip-lossy-too", "base64", "own-spelling", - "reads-its-own-spelling", "closed-set-key-unpoliced", "nullability-not-comparable", - ]; - public static string? Of(string note) - { - if (note.StartsWith("substituted:")) return null; - if (note.StartsWith("both-threw-differently")) return "inconclusive"; - if (Refusal.Any(note.Contains)) return "expected-refusal"; - if (Inapplicable.Any(note.Contains) || note == "own-vo-converter") return "inapplicable"; + static readonly AsyncLocal<Check?> Current = new(); + static readonly Regex Id = new("^[a-z0-9][a-z0-9-]*$"); + readonly Row row; + internal Check(Row row, string obligation, Baseline? against) + { + this.row = row; Obligation = obligation; Against = against; + // An obligation is an id of the claim's obligations (claims.json), or "claim"; anything else is the harness's mistake. + if (!Id.IsMatch(obligation ?? "")) Outcome("harness-error", $"obligation-not-an-id({obligation})"); + } + /// <summary>The claim's obligation this check checks, as claims.json names it, or <c>claim</c> for a claim without obligations.</summary> + public string Obligation { get; } + /// <summary>What the check compared against; null where it compared nothing, which no pass may be.</summary> + public Baseline? Against { get; } + /// <summary>The evidence of this check alone: its reads, substitutions, reasons and category.</summary> + public Evidence Ev { get; } = new(); + public string Category { get { lock (Ev) return Ev.Category; } } + /// <summary>The failure the body returned, if it returned one.</summary> + public string? Failure { get; internal set; } + public string Notes { get; private set; } = ""; + + internal static Check? Running(Evidence rowEvidence) => Current.Value is { } c && ReferenceEquals(c.row.Ev, rowEvidence) ? c : null; + internal static Check? Enter(Check c) { var outer = Current.Value; Current.Value = c; return outer; } + internal static void Leave(Check? outer) => Current.Value = outer; + + public string this[string axis] => row.Raw(axis) is { } v ? Mark(axis, v) : throw new KeyNotFoundException($"row has no axis {axis}"); + public string? Get(string axis) => row.Raw(axis) is { } v ? Mark(axis, v) : null; + public bool Is(string axis, string value) => row.Raw(axis) is { } v && Mark(axis, v) == value; + string Mark(string axis, string v) { MarkRead(axis); return v; } + internal void MarkRead(string axis) { lock (Ev) Ev.Read.Add(axis); } + /// <summary>The setting ran as <paramref name="actual"/> for this check, not as the row asked: only the actual value earns credit.</summary> + public void Substituted(string axis, string actual, string reason) + { + lock (Ev) Ev.Applied[axis] = actual; + Note(NoteKeys.Substituted, $"{axis}={actual}, {reason}"); + } + /// <summary>How the check was compared, by a key of <see cref="NoteKeys"/>; a key of another outcome raises the check's.</summary> + public void Note(NoteKeys key, string? detail = null) => Noted(key, detail); + /// <summary>A note the row routes here from inside the check's body; its key is a member, written out where the row's note was made.</summary> + internal void Noted(NoteKeys key, string? detail) + { + var n = detail is null ? key.Key : $"{key.Key}({detail})"; + Remember(n); + if (key.Outcome != "pass") Outcome(key.Outcome, n); + } + /// <summary>The check does not apply to this row, for the reason <paramref name="cite"/> names; returns null, no failure.</summary> + public string? Inapplicable(Cite cite, string? detail = null) => Cited("inapplicable", cite, detail); + /// <summary>The check met the refusal <paramref name="cite"/> says is intended; returns null, no failure.</summary> + public string? Refused(Cite cite, string? detail = null) => Cited("expected-refusal", cite, detail); + /// <summary>The check does not apply where its claim declares so (claims.json): the declaration's cite, read from the file, not a literal.</summary> + internal void Declared(Cite cite, string settings) => Cited("inapplicable", cite, "declared " + settings); + /// <summary>The check could not tell; returns null, no failure.</summary> + public string? Inconclusive(string reason) { Outcome("inconclusive", reason); return null; } + public System.Text.StringBuilder Log => row.Log; + + string? Cited(string category, Cite cite, string? detail) + { + var n = detail is null ? cite.Spelling : $"{cite.Spelling}({detail})"; + Remember(n); + Outcome(category, n); return null; } + void Remember(string n) { lock (Ev) if (!Notes.Split(';').Contains(n)) Notes += (Notes.Length == 0 ? "" : ";") + n; } + internal void Outcome(string category, string reason) { lock (Ev) Ev.Raise(category, reason); } + + /// <summary>The axes this check read, at the value that applied: its own substitution, else the row's, else the row's value.</summary> + public Dictionary<string, string> AppliedSettings() + { + Dictionary<string, string> rowApplied; + lock (row.Ev) rowApplied = new(row.Ev.Applied); + lock (Ev) + return Ev.Read.Where(a => row.Raw(a) is not null) + .ToDictionary(a => a, a => Ev.Applied.TryGetValue(a, out var v) ? v : rowApplied.TryGetValue(a, out var w) ? w : row.Raw(a)!); + } +} + +/// <summary> +/// What a check compared the value object against: a closed catalogue, as <see cref="NoteKeys"/> is, each member +/// spelled as the evidence line carries it. There is no text baseline: a check that matched a message names the +/// refusal, the fact or the sentence of the docs it held the message to, or it compared nothing. +/// </summary> +public sealed class Baseline +{ + /// <summary>The baseline as a check's evidence spells it.</summary> + public string Spelling { get; } + Baseline(string spelling) { Spelling = spelling; } + public override string ToString() => Spelling; + + /// <summary>The wrapped twin in the same place, with TypeKit.</summary> + public static readonly Baseline Twin = new("twin"); + /// <summary>The wrapped twin without TypeKit.</summary> + public static readonly Baseline Framework = new("framework"); + /// <summary>The input itself, read back.</summary> + public static readonly Baseline RoundTrip = new("round-trip"); + /// <summary>An in-memory model of the same operations.</summary> + public static readonly Baseline Model = new("model"); + /// <summary>The documented refusal: its type, the name it gives, no inner exception, no quoted input.</summary> + public static readonly Baseline Refusal = new("refusal"); + /// <summary>The wrapped type's path in the same process.</summary> + public static readonly Baseline Cost = new("cost"); + /// <summary>A pinned fact's fixture, by its id under design/knowledge/ (<c>KB-031</c>).</summary> + public static Baseline Fact(string id) => new("fact:" + Cite.FactForm(id)); + /// <summary>A sentence of the docs, by <c>file#anchor</c>, where the wrapped type's side cannot answer.</summary> + public static Baseline Docs(string source) => new("docs:" + Cite.DocsForm(source)); +} + +/// <summary> +/// Why a check did not apply or met an intended refusal: a claim's not-promised entry, a section of the docs, a +/// pinned fact or a decision record. Each factory checks the form of what it is given; that it exists is held by +/// `prober register` and `publish` and by the conventions tests, which also require a string literal. +/// </summary> +public sealed class Cite +{ + static readonly Regex Id = new("^[a-z0-9][a-z0-9-]*$"); + static readonly Regex Source = new(@"^(?!/)(?!.*(^|/)\.\.?(/|#|$))[A-Za-z0-9_./-]+\.[A-Za-z0-9]+(#[A-Za-z0-9][A-Za-z0-9._-]*)?$"); + static readonly Regex FactId = new(@"^KB-\d{3}$"); + static readonly Regex RecordId = new(@"^\d{4}$"); + /// <summary>The citation as a reason spells it: <c>not-promised:id</c>, <c>docs:file#anchor</c>, <c>fact:KB-031</c>, <c>record:0094</c>.</summary> + public string Spelling { get; } + Cite(string spelling) { Spelling = spelling; } + public override string ToString() => Spelling; + + /// <summary>An id of the claim's <c>not-promised</c> list.</summary> + public static Cite NotPromised(string id) => new("not-promised:" + Form(id, Id, "a not-promised id", "closed-set-has-no-text")); + /// <summary>A section of the docs, as a not-promised entry's <c>source</c> spells it: <c>file#anchor</c>, the file from the repository root.</summary> + public static Cite Docs(string source) => new("docs:" + DocsForm(source)); + /// <summary>A knowledge entry under design/knowledge/.</summary> + public static Cite Fact(string id) => new("fact:" + FactForm(id)); + /// <summary>A decision record under design/adr/.</summary> + public static Cite Record(string id) => new("record:" + Form(id, RecordId, "a decision record's number", "0094")); + + /// <summary>A citation as claims.json spells it (<c>not-promised:id</c>, <c>docs:file#anchor</c>, <c>fact:KB-031</c>, <c>record:0094</c>), by its factory.</summary> + internal static Cite Parse(string spelling) + { + var at = spelling.IndexOf(':'); + var value = at < 0 ? "" : spelling[(at + 1)..]; + return (at < 0 ? "" : spelling[..at]) switch + { + "not-promised" => NotPromised(value), "docs" => Docs(value), "fact" => Fact(value), "record" => Record(value), + _ => throw new ArgumentException($"a citation is not-promised:id, docs:file#anchor, fact:KB-NNN or record:NNNN, not '{spelling}'", nameof(spelling)), + }; + } + + internal static string DocsForm(string source) => Form(source, Source, "a docs source", "docs/json.md#decimals-in-one-spelling"); + internal static string FactForm(string id) => Form(id, FactId, "a knowledge entry's id", "KB-031"); + static string Form(string value, Regex form, string what, string example) => + value is not null && form.IsMatch(value) ? value : throw new ArgumentException($"{what} is spelled like {example}, not '{value}'", nameof(value)); +} + +/// <summary> +/// Every note a check may make, by exact key, and the outcome it stands for: the catalogue, and each of its +/// members one key. A check writes <c>r.Note(NoteKeys.Compared, detail)</c>; the constructor is private, so a +/// key outside the catalogue, a misspelled one or one built from text does not compile (decision 103). "pass" +/// keys only explain how a check that ran was compared. A check that did not apply, ended in a refusal or could +/// not tell says so through its scope, citing what makes it so (<c>c.Inapplicable(Cite…)</c>, <c>c.Refused(Cite…)</c>, +/// <c>c.Inconclusive(reason)</c>); the one other key is <see cref="FrameworkRefuses"/>, the three-sided +/// differential's expected refusal, against side (1)'s baseline. The outcome keys of the notes those replaced +/// were removed once no harness or probe source called them (WS2's close). +/// </summary> +public sealed class NoteKeys +{ + const string Pass = "pass", ExpectedRefusal = "expected-refusal"; + /// <summary>The key as a row's notes and reasons spell it.</summary> + public string Key { get; } + /// <summary>The outcome a note with this key stands for; a note never makes a violation or a harness error.</summary> + public string Outcome { get; } + NoteKeys(string key, string outcome) { Key = key; Outcome = outcome; } + public override string ToString() => Key; + + // how a check that ran was compared + public static readonly NoteKeys Compared = new("compared", Pass); + public static readonly NoteKeys Substituted = new("substituted", Pass); + public static readonly NoteKeys Dropped = new("dropped", Pass); + public static readonly NoteKeys CtxStrict = new("ctx-strict", Pass); + public static readonly NoteKeys CtxFallback = new("ctx-fallback", Pass); // not called since the one ProbeContext lists every shape (shapes.cs): no fallback under the context + public static readonly NoteKeys BindingNullable = new("binding:nullable", Pass); + public static readonly NoteKeys OwnVo = new("own-vo", Pass); + public static readonly NoteKeys ConverterWrapsInner = new("converter-wraps-inner", Pass); + public static readonly NoteKeys DocumentedCharGuidKeyNeverGeneratedChecked = new("documented-char-guid-key-never-generated-checked", Pass); + public static readonly NoteKeys DocumentedCharGuidNotGeneratedChecked = new("documented-char-guid-not-generated-checked", Pass); + public static readonly NoteKeys SchemaComparedWithNullableWrapped = new("schema:compared-with-nullable-wrapped", Pass); // not called since the twin is at the value object's nullability (shapes.cs) + public static readonly NoteKeys SchemaNullableSpellingsNormalised = new("schema:nullable-spellings-normalised", Pass); + public static readonly NoteKeys SchemaClassVoNullabilityNormalised = new("schema:class-vo-nullability-normalised", Pass); + public static readonly NoteKeys SchemaNeitherSideDocumented = new("schema:neither-side-documented", Pass); + // a nullable value object that is a component is null around its reference, the wrapped type null inside its schema (decision 36) + public static readonly NoteKeys SchemaNullAroundReferenceNormalised = new("schema:null-around-reference-normalised", Pass); + // EF: the claim's check ran, against the documented expectation where the wrapped type's own side cannot answer + public static readonly NoteKeys RefusalNamesTheType = new("refusal-names-the-type", Pass); + public static readonly NoteKeys StoredFactoryReadUnvalidated = new("stored-factory-read-unvalidated", Pass); + public static readonly NoteKeys DocumentedCharGuidDefaultGenerationNormalized = new("documented-char-guid-default-generation-normalized", Pass); + public static readonly NoteKeys PlainKeyThrowsOnSqliteVoCheckedAgainstExpected = new("plain-key-throws-on-sqlite-vo-checked-against-expected", Pass); + public static readonly NoteKeys TpcSqliteNoSequenceAssignChecked = new("tpc-sqlite-no-sequence-assign-checked", Pass); + // the document part has nothing to check: ASP.NET alone refuses the same document in the same row, or + // leaves the controller out of it (docs/aspnet-core.md, "Bind value objects in MVC"), checked absent + public static readonly NoteKeys DocRefusedByAspnetAlone = new("doc:refused-by-aspnet-alone", Pass); + public static readonly NoteKeys DocControllerNotInApiExplorer = new("doc:controller-not-in-api-explorer", Pass); + // L03's JSON half has nothing to compare: the wrapped type alone refuses under the row's options, the value object alike, in the same row + public static readonly NoteKeys JsonHalfWrappedAloneRefuses = new("json-half:wrapped-alone-refuses", Pass); // not called since the three-sided differential (Sides): framework-refuses + // JSON family (C01, C10, L06): the options leave the member out of one direction for the plain type without TypeKit too, shown by a + // baseline in the same row; that direction has nothing to check, and the row is judged by the other one (both left out: each direction inconclusive) + public static readonly NoteKeys JsonMemberLeftOutByOptions = new("json:member-left-out-by-options", Pass); + // a read the wrapped type's shape refuses with the very same exception and message: the serializer refused the document before any + // value object ran (the baseline in the same row), so the read has nothing to check; the row is judged by its write and other reads + public static readonly NoteKeys JsonReadRefusedByStjAlone = new("json:read-refused-by-stj-alone", Pass); // not called since the three-sided differential (Sides): framework-refuses + // ASP.NET's OpenAPI 2.0 writer drops the reference of a nullable struct component; a plain struct beside it in the same + // schema, without a converter, is written the same, so that position has nothing to check (C12) + public static readonly NoteKeys SchemaNullableReferenceLostForPlainToo = new("schema:nullable-reference-lost-for-plain-too", Pass); + // the three-sided differential (Sides): the wrapped twin without TypeKit and the value object refuse alike, at the same operation and + // stage with the same exception type and status, so the framework refuses; no message is compared + public static readonly NoteKeys FrameworkRefuses = new("framework-refuses", ExpectedRefusal); } public static class Rows { - public static string Root() + public static string Root() => TryRoot() ?? throw new DirectoryNotFoundException("no CodoMetis.TypeKit.Samples.csproj above " + AppContext.BaseDirectory); + static string? TryRoot() { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "CodoMetis.TypeKit.Samples.csproj"))) dir = dir.Parent; - return dir!.FullName; + return dir?.FullName; + } + + /// <summary> + /// The claims the replay stages beside the rows (Probes/claims.json); where none is staged, as for a probe the prober's + /// own tests run in-process, no claim declares anything. The prober's tests point it at a file of their own. + /// </summary> + internal static string? ClaimsFile { get; set; } = TryRoot() is { } root ? Path.Combine(root, "Probes", "claims.json") : null; + static readonly Dictionary<string, Dictionary<string, JsonObject>> Claims = new(); + + /// <summary>A claim as the staged claims.json holds it, or null where the file or the claim is not there.</summary> + public static JsonObject? Claim(string id) + { + if (ClaimsFile is not { } path) return null; + lock (Claims) + { + if (!Claims.TryGetValue(path, out var claims)) + Claims[path] = claims = !File.Exists(path) ? new() + : JsonNode.Parse(File.ReadAllText(path))!.AsArray().OfType<JsonObject>().ToDictionary(c => c["id"]!.GetValue<string>()); + return claims.GetValueOrDefault(id); + } } - public static IEnumerable<Row> Load(string claim) + + /// <summary>The declarations of where <paramref name="obligation"/> of <paramref name="claim"/> does not apply: each its axis values and its citation.</summary> + public static IEnumerable<(Dictionary<string, string[]> Axes, Cite Cite)> Declarations(string? claim, string obligation) + { + var o = (claim is null ? null : Claim(claim))?["obligations"]?.AsArray().OfType<JsonObject>().FirstOrDefault(x => x["id"]?.GetValue<string>() == obligation); + foreach (var declared in (o?["inapplicable"]?.AsArray() ?? []).OfType<JsonObject>()) + yield return (declared["axes"]!.AsObject().ToDictionary(kv => kv.Key, kv => kv.Value!.AsArray().Select(v => v!.GetValue<string>()).ToArray()), + Cite.Parse(declared["cite"]!.GetValue<string>())); + } + /// <summary>The rows of one probe, from its own file (rows/<paramref name="probe"/>.json); a sibling probe of the claim reads its own.</summary> + public static IEnumerable<Row> Load(string probe) { - var doc = JsonNode.Parse(File.ReadAllText(Path.Combine(Root(), "Probes", "rows", claim + ".json")))!; + var path = Path.Combine(Root(), "Probes", "rows", probe + ".json"); + if (!File.Exists(path)) throw new FileNotFoundException($"no rows file for the probe {probe}: {path}", path); + return Read(path); + } + /// <summary>The rows of a rows file, each holding the file's claim, whose declarations its checks apply.</summary> + internal static IEnumerable<Row> Read(string path) + { + var doc = JsonNode.Parse(File.ReadAllText(path))!; + var claim = doc["claim"]?.GetValue<string>(); var i = 0; foreach (var r in doc["rows"]!.AsArray()) - yield return new Row(i++, r!["row"]!.AsObject().ToDictionary(kv => kv.Key, kv => kv.Value!.GetValue<string>())); + yield return new Row(i++, r!["row"]!.AsObject().ToDictionary(kv => kv.Key, kv => kv.Value!.GetValue<string>()), claim); } - /// <summary>xunit MemberData: (claim, index). Optional filter by env PROBE_ROWS="0-20" for quick runs.</summary> - public static TheoryData<string, int> Data(string claim) + /// <summary> + /// xunit MemberData: (probe, index), the probe naming its rows file, labelled with the row's settings so a test list or a + /// failure names them. Optional filter by env PROBE_ROWS="0-20" for quick runs. + /// </summary> + public static TheoryData<string, int> Data(string probe) { var d = new TheoryData<string, int>(); var range = Environment.GetEnvironmentVariable("PROBE_ROWS"); (int lo, int hi) = range is null ? (0, int.MaxValue) : (int.Parse(range.Split('-')[0]), int.Parse(range.Split('-')[1])); var only = Environment.GetEnvironmentVariable("PROBE_ONLY"); // "axis=value" keeps rows with that value - foreach (var r in Load(claim)) if (r.Index >= lo && r.Index <= hi && (only is null || only.Split(',').All(c => c.Contains("!=") ? !r.Is(c.Split("!=")[0], c.Split("!=")[1]) : r.Is(c.Split('=')[0], c.Split('=')[1])))) d.Add(claim, r.Index); + foreach (var r in Load(probe)) + if (r.Index >= lo && r.Index <= hi && (only is null || only.Split(',').All(c => c.Contains("!=") ? !r.Is(c.Split("!=")[0], c.Split("!=")[1]) : r.Is(c.Split('=')[0], c.Split('=')[1])))) + d.Add(new TheoryDataRow<string, int>(probe, r.Index) { Label = $"#{r.Index} {r}" }); return d; } static readonly Dictionary<string, List<Row>> Cache = new(); - public static Row Get(string claim, int index) + public static Row Get(string probe, int index) { - lock (Cache) { if (!Cache.TryGetValue(claim, out var l)) Cache[claim] = l = Load(claim).ToList(); return l[index]; } + lock (Cache) { if (!Cache.TryGetValue(probe, out var l)) Cache[probe] = l = Load(probe).ToList(); return l[index]; } } } public static class Rec { static readonly object Gate = new(); - /// <summary>One line per row to $PROBE_OUT/claim.jsonl: how it ended and the settings that ran.</summary> - public static void Write(string claim, int idx, string category, string note, Row? row = null) + /// <summary> + /// One line per row to $PROBE_OUT/label.jsonl: how it ended and the settings that ran, and for a row that ran + /// checks, each check's obligation, baseline, outcome, settings, reasons and notes. <paramref name="passed"/> is + /// the row's test result where its category alone does not say it (a row with a passing check and another that + /// did not apply passes). + /// </summary> + public static void Write(string label, int idx, string category, string note, Row? row = null, bool? passed = null) { var dir = Environment.GetEnvironmentVariable("PROBE_OUT") ?? "/tmp/probe-out"; Directory.CreateDirectory(dir); var applied = row?.AppliedSettings() ?? new Dictionary<string, string>(); var reasons = row is null ? [] : row.Ev.Reasons.ToArray(); - lock (Gate) File.AppendAllText(Path.Combine(dir, claim + ".jsonl"), - JsonSerializer.Serialize(new { idx, status = category == "pass" ? "pass" : "fail", category, completed = row is not null, - applied, reasons, note = note.Length > 600 ? note[..600] : note }) + "\n"); + var findings = row is null ? [] : row.Ev.Findings.ToArray(); + var status = passed ?? category == "pass" ? "pass" : "fail"; + note = note.Length > 600 ? note[..600] : note; + var checks = row is null ? [] : Checks(row); + var line = checks.Count == 0 + ? JsonSerializer.Serialize(new { idx, status, category, completed = row is not null, applied, reasons, findings, note }) + : JsonSerializer.Serialize(new + { + idx, status, category, completed = row is not null, applied, reasons, findings, note, + checks = checks.Select(c => new + { + obligation = c.Obligation, baseline = c.Against?.Spelling, category = c.Category, applied = c.AppliedSettings(), + reasons = Reasons(c), notes = c.Notes, + }), + }); + lock (Gate) File.AppendAllText(Path.Combine(dir, label + ".jsonl"), line + "\n"); } + + static List<Check> Checks(Row row) { lock (row.Ev) return [.. row.Ev.Checks]; } + static string[] Reasons(Check c) { lock (c.Ev) return [.. c.Ev.Reasons]; } + /// <summary> /// Runs the check on a fresh copy of the row (its own evidence); null = no failure, otherwise the failure text. - /// An exception the check did not expect is a harness error until triaged, never a pass. + /// An exception the check did not expect is a harness error until triaged, never a pass and never a + /// violation: the row fails the test with the exception named, and no seed counts it caught. A violation or a + /// harness error fails the test; any other outcome but a pass skips it with its reasons, so a test run + /// counts what earned nothing. A row that ran checks (<see cref="Row.Check(string, Baseline?, Func{Check, string?})"/>) + /// fails when the row itself or any check ended as a violation or a harness error, passes when at least one check + /// passed, and otherwise skips, listing each check's outcome. <paramref name="label"/> names the row in a failure and + /// its evidence file, and is the probe whose rows file holds the row unless <paramref name="rowsOf"/> names it: a + /// probe passes its theory's first argument, the probe's own name, as one or the other. /// </summary> - public static async Task Run(string claim, int idx, Func<Row, Task<string?>> check, string? rowsOf = null) + public static Task Run(string label, int idx, Func<Row, Task<string?>> check, string? rowsOf = null) => Run(label, Rows.Get(rowsOf ?? label, idx), check); + + /// <summary>The same for a row in hand, without a rows file: how the prober's tests run the harness.</summary> + public static async Task Run(string label, Row cached, Func<Row, Task<string?>> check) { - var cached = Rows.Get(rowsOf ?? claim, idx); - var row = new Row(cached.Index, cached.All.ToDictionary(kv => kv.Key, kv => kv.Value)); - string? fail; + var idx = cached.Index; + var row = new Row(cached.Index, cached.All.ToDictionary(kv => kv.Key, kv => kv.Value), cached.Claim); + string? fail = null, unexpected = null; try { fail = await check(row); } - catch (Exception ex) { fail = "UNEXPECTED " + ex.GetType().Name + ": " + ex.Message; row.Outcome("harness-error", fail); } - if (fail is not null) row.Outcome("violation", fail); - Write(claim, idx, row.Ev.Category, fail ?? row.Notes, row); - Assert.True(fail is null, $"[{claim} #{idx}] {row}\n{fail}"); + catch (Exception ex) { unexpected = "UNEXPECTED " + ex.GetType().Name + ": " + ex.Message; row.Outcome("harness-error", unexpected); } + var checks = Checks(row); + if (checks.Count == 0) + { + // An exception is the harness breaking, not the claim's check firing: a harness error and never a + // violation, so a seeded defect that only makes a check throw is no catch (decision 103). + if (fail is not null) row.Outcome("violation", fail); + Write(label, idx, row.Ev.Category, fail ?? unexpected ?? row.Notes, row); + if (fail is not null) Assert.Fail($"[{label} #{idx}] {row}\n{fail}"); + if (row.Ev.Category == "harness-error") Assert.Fail($"[{label} #{idx}] {row}\nharness error: {string.Join("; ", row.Ev.Reasons)}"); + if (row.Ev.Category != "pass") Assert.Skip($"{row.Ev.Category}: {string.Join("; ", row.Ev.Reasons.Where(x => !x.StartsWith("pass:")))}"); + return; + } + // A failure the row returns from one of its checks is that check's violation, recorded there; any other is the row's own. + if (fail is not null && !checks.Any(c => c.Failure == fail)) row.Outcome("violation", fail); + var failing = checks.Where(c => c.Category is "violation" or "harness-error").ToList(); + var own = row.Ev.Reasons.Where(x => !x.StartsWith("pass:")).ToList(); + var ownFailed = row.Ev.Own is "violation" or "harness-error"; + var passed = !ownFailed && failing.Count == 0 && checks.Any(c => c.Category == "pass"); + Write(label, idx, row.Ev.Category, fail ?? unexpected ?? row.Notes, row, passed); + if (ownFailed || failing.Count > 0) + Assert.Fail($"[{label} #{idx}] {row}\n" + string.Join("\n", (ownFailed ? own : []).Concat(failing.Select(Shown)))); + if (!passed) Assert.Skip(string.Join("; ", own.Concat(checks.Select(Shown)))); + } + + /// <summary>A check as a failure or a skip lists it: <c>smallest-scale inapplicable: not-promised:closed-set-has-no-scale</c>.</summary> + static string Shown(Check c) + { + var category = c.Category; + var reasons = Reasons(c).Where(x => !x.StartsWith("pass:")).Select(x => x.StartsWith(category + ":") ? x[(category.Length + 1)..] : x); + return $"{c.Obligation} {category}: {string.Join(", ", reasons)}"; } } diff --git a/tools/probe/corpus/harness/RuleControllers.g.cs b/tools/probe/corpus/harness/RuleControllers.g.cs index 7662c5f2..9b3f57cf 100644 --- a/tools/probe/corpus/harness/RuleControllers.g.cs +++ b/tools/probe/corpus/harness/RuleControllers.g.cs @@ -1,4 +1,4 @@ -// Generated by the prober (ruleCtl): controllers answering composed faults, one action per arity x response variant (C03, C05). +// Generated by tools/probe/generators/rules.cs: controllers answering composed faults, one action per arity x response variant (C03, C05). using CodoMetis.TypeKit; using CodoMetis.TypeKit.AspNetCore; using Microsoft.AspNetCore.Http.HttpResults; diff --git a/tools/probe/corpus/harness/SchemaClaim.cs b/tools/probe/corpus/harness/SchemaClaim.cs new file mode 100644 index 00000000..65b49f19 --- /dev/null +++ b/tools/probe/corpus/harness/SchemaClaim.cs @@ -0,0 +1,720 @@ +// Shared code of the claims C08-same-name-warning, C12-described-converter, C13-patch-required and C16-options-agreement +// (harness; the probes are in probes/C08..., C12..., C13..., C16...). Every check starts the shared host (Hx.Start, which applies the +// row's json.*, openapi.*, mvc.* and http.* axes) with only the endpoints of its claim: minimal ones under Hx.M, controllers of its own +// under Hx.C, the actions it does not need removed by a convention. +// - C08 (AspNetCore README#faults-of-several-types, "Keep names distinct"): two fault types an endpoint answers with that write one +// name for two errors make the host log a warning at startup naming both; qualified names, or leaving one out, fix it. +// - C12 (docs/json.md#describing-what-a-converter-writes, AspNetCore README "A described type"): the document describes a type as its +// described converter or the options' JsonSchemaDescriptions say, as a property, a nullable one, a list's items, a dictionary's +// additionalProperties, inside a value object and inside a patch; a parameter keeps ASP.NET's schema. +// - C13 (docs/merge-patches.md#required-constructor-parameters): under RespectRequiredConstructorParameters a constructor-parameter +// patch is required in the document and on the wire, an init one is neither, and without the option neither is. +// - C16 (AspNetCore README#what-the-document-says, "Register the same on both option sets"): a host with controllers whose two option +// sets register different converters or descriptions has its document refused, naming the type and what the options say of it, +// also for a type held only inside a patch; a minimal-API host builds it. The docs name converters and descriptions: a different +// naming policy or number handling is not "described differently", so the document builds there (DescribedOptionsAgreement +// compares JsonSchemaDescription.For, which neither setting changes). +// Each claim's obligations are checks of their own (Row.Check), every check of a row on the same host or hosts, the answers fetched once. +#pragma warning disable xUnit1051 +using System.Globalization; +using System.Net; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.AspNetCore; +using CodoMetis.TypeKit.ClosedSets; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Probes.Shapes; +using HttpJsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; +using MvcJsonOptions = Microsoft.AspNetCore.Mvc.JsonOptions; +namespace Probes.Schema.NsA +{ + /// <summary>One of two fault sets of one nesting chain (Twin) in two namespaces: qualified names meet too.</summary> + public sealed partial class Twin : IFaultSet<Twin> + { + public static readonly Twin Same = new(FaultKind.Conflict, null); + public static readonly Twin OnlyA = new(FaultKind.NotFound, null); + private Twin(FaultKind kind, string? description) { Kind = kind; Description = description; } + public FaultKind Kind { get; } + public string? Description { get; } + } +} +namespace Probes.Schema.NsB +{ + public sealed partial class Twin : IFaultSet<Twin> + { + public static readonly Twin Same = new(FaultKind.Conflict, null); + public static readonly Twin OnlyB = new(FaultKind.Unavailable, null); + private Twin(FaultKind kind, string? description) { Kind = kind; Description = description; } + public FaultKind Kind { get; } + public string? Description { get; } + } +} +namespace Probes.Schema +{ + using TwinA = Probes.Schema.NsA.Twin; + using TwinB = Probes.Schema.NsB.Twin; + + // ---------------------------------------------------------------- types the documents describe + + /// <summary>A type an application writes with a converter of its own (docs/json.md's Coordinates).</summary> + public readonly record struct Coord(double Lat, double Lon); + /// <summary>A second one, held only inside a patch (C16).</summary> + public readonly record struct CoordP(double Lat, double Lon); + public readonly partial record struct CoordVo : IValue<Coord>; + public readonly partial record struct DecVo : IValue<decimal>; + + static class CoordText + { + public static (double, double) Read(ref Utf8JsonReader reader, string prefix) => + reader.GetString() is { } s && s.StartsWith(prefix, StringComparison.Ordinal) && s[prefix.Length..].Split(',') is [var a, var b] + ? (double.Parse(a, CultureInfo.InvariantCulture), double.Parse(b, CultureInfo.InvariantCulture)) + : throw new JsonException(); + public static string Write(string prefix, double a, double b) => string.Create(CultureInfo.InvariantCulture, $"{prefix}{a},{b}"); + } + + /// <summary>source=IDescribedJsonConverter: a converter that says what it writes.</summary> + public sealed class CoordConverter : JsonConverter<Coord>, IDescribedJsonConverter + { + public static JsonObject Schema() => new() { ["type"] = "string", ["pattern"] = @"^c:-?\d+(\.\d+)?,-?\d+(\.\d+)?$" }; + public override Coord Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var (a, b) = CoordText.Read(ref reader, "c:"); return new(a, b); } + public override void Write(Utf8JsonWriter writer, Coord value, JsonSerializerOptions options) => writer.WriteStringValue(CoordText.Write("c:", value.Lat, value.Lon)); + public JsonSchemaDescription? DescribeJson(Type typeToConvert, JsonSerializerOptions options) => JsonSchemaDescription.Of(Schema()); + } + /// <summary>source=JsonSchemaDescriptions: a converter someone else wrote, which says nothing; the options describe it.</summary> + public sealed class CoordPlainConverter : JsonConverter<Coord> + { + public static JsonObject Schema() => new() { ["type"] = "string", ["pattern"] = @"^d:-?\d+(\.\d+)?,-?\d+(\.\d+)?$" }; + public override Coord Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var (a, b) = CoordText.Read(ref reader, "d:"); return new(a, b); } + public override void Write(Utf8JsonWriter writer, Coord value, JsonSerializerOptions options) => writer.WriteStringValue(CoordText.Write("d:", value.Lat, value.Lon)); + } + public sealed class CoordPConverter : JsonConverter<CoordP>, IDescribedJsonConverter + { + public static JsonObject Schema() => new() { ["type"] = "string", ["pattern"] = @"^p:-?\d+(\.\d+)?,-?\d+(\.\d+)?$" }; + public override CoordP Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var (a, b) = CoordText.Read(ref reader, "p:"); return new(a, b); } + public override void Write(Utf8JsonWriter writer, CoordP value, JsonSerializerOptions options) => writer.WriteStringValue(CoordText.Write("p:", value.Lat, value.Lon)); + public JsonSchemaDescription? DescribeJson(Type typeToConvert, JsonSerializerOptions options) => JsonSchemaDescription.Of(Schema()); + } + + /// <summary>C12: every position the docs name ("a property, a body, a nullable one, a list's element, a dictionary's value, and inside a value object or a patch").</summary> + public sealed record CoordDto + { + public Coord At { get; init; } + public Coord? Maybe { get; init; } + /// <summary>A plain nullable struct beside Maybe: what ASP.NET writes for a nullable reference in the same document.</summary> + public Box? Twin { get; init; } + public List<Coord> Route { get; init; } = []; + public Dictionary<string, Coord> ByName { get; init; } = []; + public required CoordVo Vo { get; init; } + public Patch<Coord> Patched { get; init; } + } + public sealed record DecDto + { + public decimal At { get; init; } + public decimal? Maybe { get; init; } + public List<decimal> Route { get; init; } = []; + public Dictionary<string, decimal> ByName { get; init; } = []; + public required DecVo Vo { get; init; } + public Patch<decimal> Patched { get; init; } + } + /// <summary>A plain struct without a converter of its own: the baselines' twin.</summary> + public readonly record struct Box(int V); + public sealed record BoxDto { public Box Inner { get; init; } } + /// <summary>C13: the docs' two declarations of a patch field.</summary> + public sealed record InitPatchDto { public Patch<int> Floor { get; init; } } + public sealed record CtorPatchDto(Patch<int> Floor); + /// <summary>C16: a described type as a property, and one held only inside a patch.</summary> + public sealed record AgreeDto { public Coord At { get; init; } } + public sealed record AgreePatchDto { public Patch<CoordP> P { get; init; } } + + /// <summary> + /// The application lists its own types for json.TypeInfoResolver=source-generated-context: appended to the resolver chain, it + /// answers only where the row's resolver does not (a reflection resolver always does). Under the strict context the chain is + /// ProbeContext then this, with no reflection behind them. + /// </summary> + [JsonSerializable(typeof(CoordDto)), JsonSerializable(typeof(DecDto)), JsonSerializable(typeof(InitPatchDto)), JsonSerializable(typeof(CtorPatchDto))] + [JsonSerializable(typeof(AgreeDto)), JsonSerializable(typeof(AgreePatchDto)), JsonSerializable(typeof(Coord)), JsonSerializable(typeof(CoordP))] + [JsonSerializable(typeof(Coord?)), JsonSerializable(typeof(List<Coord>)), JsonSerializable(typeof(Dictionary<string, Coord>)), JsonSerializable(typeof(CoordVo))] + [JsonSerializable(typeof(Patch<Coord>)), JsonSerializable(typeof(Patch<CoordP>)), JsonSerializable(typeof(Patch<int>)), JsonSerializable(typeof(Patch<decimal>))] + [JsonSerializable(typeof(decimal)), JsonSerializable(typeof(decimal?)), JsonSerializable(typeof(List<decimal>)), JsonSerializable(typeof(Dictionary<string, decimal>))] + [JsonSerializable(typeof(BoxDto)), JsonSerializable(typeof(Box?))] + [JsonSerializable(typeof(ProblemDetails))] // what a fault is answered as (C08), as an application on a source-generated context lists it + [JsonSerializable(typeof(DecVo)), JsonSerializable(typeof(int)), JsonSerializable(typeof(string)), JsonSerializable(typeof(double))] + public partial class SchemaJson : JsonSerializerContext { } + + // ---------------------------------------------------------------- controllers + + /// <summary>Keeps only the named actions of the probe's own controllers: one controller type serves every row.</summary> + public sealed class KeepOnly(Type controllerBase, params string[] actions) : IApplicationModelConvention + { + public void Apply(ApplicationModel application) + { + foreach (var c in application.Controllers.Where(c => controllerBase.IsAssignableFrom(c.ControllerType))) + foreach (var a in c.Actions.ToList()) + if (!actions.Contains(a.ActionName)) c.Actions.Remove(a); + } + } + + public abstract class SchemaCtlBase : ControllerBase + { + [HttpPost("s/coord/c")] public CoordDto Coord([FromBody] CoordDto b) => b; + [HttpPost("s/dec/c")] public DecDto Dec([FromBody] DecDto b, [FromQuery] decimal q) => b; + [HttpPost("s/init/c")] public IActionResult Init([FromBody] InitPatchDto b) => ModelState.IsValid && b is not null ? Content(b.Floor.State.ToString()) : BadRequest(); + [HttpPost("s/ctor/c")] public IActionResult Ctor([FromBody] CtorPatchDto b) => ModelState.IsValid && b is not null ? Content(b.Floor.State.ToString()) : BadRequest(); + [HttpPost("s/box/c")] public IActionResult Boxed([FromBody] BoxDto b) => ModelState.IsValid && b is not null ? Content("Box") : BadRequest(); + [HttpPost("s/agree/c")] public AgreeDto Agree([FromBody] AgreeDto b) => b; + [HttpPost("s/agreep/c")] public AgreePatchDto AgreeP([FromBody] AgreePatchDto b) => b; + } + [ApiController] public sealed class SchemaApiCtl : SchemaCtlBase { } + public sealed class SchemaPlainCtl : SchemaCtlBase { } + + /// <summary>C08: errors that two fault types write alike. e: 1 the first type's, 2 the second type's, 3 SFault.First (arity 3).</summary> + public static class AlikeErr + { + public static Result<Fault<HFault, KFault>> K2(int e) => e switch + { 1 => Fault<HFault, KFault>.Of(HFault.Clash), 2 => Fault<HFault, KFault>.Of(KFault.Clash), _ => Result<Fault<HFault, KFault>>.Success() }; + public static Result<Fault<HFault, KFault, SFault>> K3(int e) => e switch + { + 1 => Fault<HFault, KFault, SFault>.Of(HFault.Clash), 2 => Fault<HFault, KFault, SFault>.Of(KFault.Clash), 3 => Fault<HFault, KFault, SFault>.Of(SFault.First), + _ => Result<Fault<HFault, KFault, SFault>>.Success(), + }; + public static Result<Fault<TwinA, TwinB>> T2(int e) => e switch + { 1 => Fault<TwinA, TwinB>.Of(TwinA.Same), 2 => Fault<TwinA, TwinB>.Of(TwinB.Same), _ => Result<Fault<TwinA, TwinB>>.Success() }; + public static Result<Fault<TwinA, TwinB, SFault>> T3(int e) => e switch + { + 1 => Fault<TwinA, TwinB, SFault>.Of(TwinA.Same), 2 => Fault<TwinA, TwinB, SFault>.Of(TwinB.Same), 3 => Fault<TwinA, TwinB, SFault>.Of(SFault.First), + _ => Result<Fault<TwinA, TwinB, SFault>>.Success(), + }; + } + public abstract class AlikeCtlBase : ControllerBase + { + [HttpGet("s/alike/c")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> K2None(int e) => AlikeErr.K2(e).ToHttpResult(); + [HttpGet("s/alike/c"), WithoutFaults<KFault>(nameof(KFault.Clash))] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>> K2Wo(int e) => AlikeErr.K2(e).ToHttpResult(); + [HttpGet("s/alike/c")] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>, FaultProblem<SFault>> K3None(int e) => AlikeErr.K3(e).ToHttpResult(); + [HttpGet("s/alike/c"), WithoutFaults<KFault>(nameof(KFault.Clash))] public Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>, FaultProblem<SFault>> K3Wo(int e) => AlikeErr.K3(e).ToHttpResult(); + [HttpGet("s/alike/c")] public Results<NoContent, FaultProblem<TwinA>, FaultProblem<TwinB>> T2None(int e) => AlikeErr.T2(e).ToHttpResult(); + [HttpGet("s/alike/c"), WithoutFaults<TwinB>(nameof(TwinB.Same))] public Results<NoContent, FaultProblem<TwinA>, FaultProblem<TwinB>> T2Wo(int e) => AlikeErr.T2(e).ToHttpResult(); + [HttpGet("s/alike/c")] public Results<NoContent, FaultProblem<TwinA>, FaultProblem<TwinB>, FaultProblem<SFault>> T3None(int e) => AlikeErr.T3(e).ToHttpResult(); + [HttpGet("s/alike/c"), WithoutFaults<TwinB>(nameof(TwinB.Same))] public Results<NoContent, FaultProblem<TwinA>, FaultProblem<TwinB>, FaultProblem<SFault>> T3Wo(int e) => AlikeErr.T3(e).ToHttpResult(); + } + [ApiController] public sealed class AlikeApiCtl : AlikeCtlBase { } + public sealed class AlikePlainCtl : AlikeCtlBase { } + + /// <summary>Every log line with its category and level (ListedLogs keeps neither).</summary> + public sealed class CategoryLogs : ILoggerProvider + { + public readonly List<(string Category, LogLevel Level, string Message)> Lines = []; + public ILogger CreateLogger(string categoryName) => new L(this, categoryName); + public void Dispose() { } + sealed class L(CategoryLogs o, string category) : ILogger + { + public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null; + public bool IsEnabled(LogLevel logLevel) => true; + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) + { lock (o.Lines) o.Lines.Add((category, logLevel, formatter(state, exception))); } + } + } + + // ---------------------------------------------------------------- the checks + + public static class SchemaChecks + { + static string Cut(string? s) => s is null ? "(null)" : s.Length > 300 ? s[..300] + "..." : s; + static void Process(Row r, Entry en) { if (r.Is("process.converter-lookup", "wrapped-type-converter-looked-up-first")) _ = System.ComponentModel.TypeDescriptor.GetConverter(Make.Held(en.Plain)); } + + /// <summary>The host's minimal-API JSON options, which the document is built from.</summary> + static JsonSerializerOptions HttpOptions(WebApplication app) => app.Services.GetRequiredService<IOptions<HttpJsonOptions>>().Value.SerializerOptions; + /// <summary>The JSON name of a CLR property under the options (naming policy, renaming modifier).</summary> + static string JsonName(JsonSerializerOptions o, Type t, string clr) => + o.GetTypeInfo(t).Properties.First(p => (p.AttributeProvider as MemberInfo)?.Name == clr).Name; + static void ListOwnTypes(JsonSerializerOptions o) => o.TypeInfoResolverChain.Add(SchemaJson.Default); + + static HostCfg Cfg(Row r, string[] actions, Action<IEndpointRouteBuilder> minimal, Action<IServiceCollection>? services = null) => new() + { + Payload = false, FaultControllers = true, + Controllers = [Hx.Plain(r) ? typeof(SchemaPlainCtl) : typeof(SchemaApiCtl)], + Minimal = minimal, + Services = s => + { + s.Configure<MvcOptions>(o => o.Conventions.Add(new KeepOnly(typeof(SchemaCtlBase), actions))); + s.PostConfigure<HttpJsonOptions>(o => ListOwnTypes(o.SerializerOptions)); + s.PostConfigure<MvcJsonOptions>(o => ListOwnTypes(o.JsonSerializerOptions)); + services?.Invoke(s); + }, + }; + + /// <summary>The paths of one endpoint family on the row's sides: the minimal one and the controller's.</summary> + static List<string> Sides(Row r, string family) + { + var sides = new List<string>(); + if (Hx.HasMinimal(r)) sides.Add(Hx.M(r, $"/s/{family}/m")); + if (Hx.HasMvc(r)) sides.Add(Hx.C(r, $"/s/{family}/c")); + return sides; + } + + /// <summary>The operation of a path, by the document's spelling of it.</summary> + static JsonNode? Op(JsonNode doc, string path, string method) => doc["paths"]?[path]?[method]; + /// <summary>The request body's schema, references resolved: OpenAPI 3 (requestBody) and 2.0 (a body parameter).</summary> + static JsonNode? BodySchema(JsonNode doc, JsonNode op) + { + var s = op["requestBody"]?["content"]?["application/json"]?["schema"] + ?? op["parameters"]?.AsArray().FirstOrDefault(p => p?["in"]?.GetValue<string>() == "body")?["schema"]; + return s is null ? null : Hx.Deep(doc, s); + } + /// <summary>A schema with its keys sorted and its nullability markers gone, to compare with a description.</summary> + static JsonNode? Norm(JsonNode? n) => Hx.NoNull(Hx.Deep(new JsonObject(), n)); + static bool Admits(JsonNode? n) + { + var t = n?.ToJsonString() ?? ""; + return t.Contains("\"null\"") || t.Contains("\"nullable\":true") || t.Contains("\"x-nullable\":true"); + } + + // ============================================================ C08 + /// <summary> + /// C08, a check per obligation over two hosts (HFault.Clash beside KFault.Clash, and NsA.Twin.Same beside NsB.Twin.Same): each host's + /// warning, or none, is checked for the obligation its naming and leave-out select (bare-names-warn, qualified-chain-warns, + /// qualified-names-fix, leave-one-out-fixes), with what its endpoints answer; warning-names-both, where a host warns, for the names + /// the warning gives. + /// </summary> + public static async Task<string?> C08(Row r) + { + var en = Registry.Of(r); Process(r, en); + var qualified = r["naming"] == "qualified"; + var arity = int.Parse(r["arity"]); + var leave = r["leave-out"] == "WithoutFaults-one"; + var runs = new[] { await Alike(r, en, twin: false, arity, leave, qualified), await Alike(r, en, twin: true, arity, leave, qualified) }; + string? failed = null; + foreach (var o in runs.SelectMany(a => a.Obligations).Distinct().ToList()) + { + var f = r.Check(o, Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types"), c => + { + foreach (var a in runs) if (a.Judge(o) is { } fail) return fail; + c.Note(NoteKeys.Compared, string.Join(", ", runs.Where(a => a.Obligations.Contains(o)).Select(a => a.Action))); + return null; + }); + failed ??= f; + } + return failed; + } + + /// <summary> + /// One host of C08: what its endpoints answered and what it logged at startup, judged per obligation by <see cref="Judge"/>. Warns: + /// the obligation of whether the host warns (and what it answers); the names the warning gives are warning-names-both's. + /// </summary> + sealed record AlikeRun(string Action, string? Down, bool Twin, bool Leave, bool Qualified, bool OwnWrapped, List<string> Sides, + List<(string Path, int E, Hx.Wire Wire)> Answers, List<string> Lines) + { + string First => Twin ? "Twin" : "HFault"; + string Second => Twin ? "Twin" : "KFault"; + string Member => Twin ? "Same" : "Clash"; + string Written(string type) => Qualified ? $"{type}.{Member}" : Member; + bool Warn => Written(First) == Written(Second) && !Leave; + public string Warns => Leave ? "leave-one-out-fixes" : !Qualified ? "bare-names-warn" : Twin ? "qualified-chain-warns" : "qualified-names-fix"; + public string[] Obligations => Warn ? [Warns, "warning-names-both"] : [Warns]; + + public string? Judge(string o) + { + if (!Obligations.Contains(o)) return null; + if (Down is not null) return Down; + if (o == Warns) + { + foreach (var (path, e, w) in Answers) + { + if (e == 2 && Leave) + { + // the member left out is not answered (README#leaving-out-what-a-route-cannot-answer): proof that the leave-out applied + if (w.Thrown is null || !w.Thrown.StartsWith(nameof(InvalidOperationException))) return $"{Action} {path} e=2: the member left out was answered: {(int)w.Status} {Cut(w.Body)} {w.Thrown}"; + continue; + } + if (w.Thrown is not null) return $"{Action} {path} e={e}: request threw {w.Thrown}"; + var want = e == 3 ? (Qualified ? "SFault.First" : "First") : Written(e == 1 ? First : Second); + const int status = 409; // Conflict for both clashing members, and SFault.First as the host maps it + if ((int)w.Status != status) return $"{Action} {path} e={e}: status {(int)w.Status}, expected {status}: {Cut(w.Body)}"; + JsonNode? body; try { body = JsonNode.Parse(w.Body); } catch { return $"{Action} {path} e={e}: body is not JSON: {Cut(w.Body)}"; } + // the application's own string converter (json.Converters=own-for-wrapped over string) wraps every string it writes + var fault = body?["fault"]?.GetValue<string>() is { } f && OwnWrapped && f.StartsWith("own:") ? f[4..].Trim('"') : body?["fault"]?.GetValue<string>(); + if (fault != want) return $"{Action} {path} e={e}: fault {fault}, expected {want}"; + } + if (!Warn) + return Lines.Count == 0 ? null : $"{Action} ({(Qualified ? "qualified" : "bare")} names, {(Leave ? "one left out" : "none left out")}): the errors are written {Written(First)} and {Written(Second)}, yet the host warned: {Cut(Lines[0])}"; + return Lines.Count == Sides.Count ? null : $"{Action}: {Sides.Count} endpoint(s) answer {First}.{Member} and {Second}.{Member} alike as \"{Written(First)}\", {Lines.Count} warning(s) at startup{(Lines.Count > 0 ? ": " + Cut(Lines[0]) : "")}"; + } + var names = Twin ? (Qualified ? new[] { "NsA.Twin.Same", "NsB.Twin.Same" } : ["Twin.Same"]) : ["HFault.Clash", "KFault.Clash"]; + foreach (var l in Lines) + if (names.FirstOrDefault(n => !l.Contains(n, StringComparison.Ordinal)) is { } missing) return $"{Action}: the warning does not name {missing}: {Cut(l)}"; + return null; + } + } + + /// <summary> + /// One endpoint per side answering two fault types whose clashing members are HFault.Clash and KFault.Clash (bare names meet, + /// qualified ones do not) or NsA.Twin.Same and NsB.Twin.Same (one nesting chain in two namespaces: both meet). + /// </summary> + static async Task<AlikeRun> Alike(Row r, Entry en, bool twin, int arity, bool leave, bool qualified) + { + var logs = new CategoryLogs(); + var action = (twin ? "T" : "K") + arity + (leave ? "Wo" : "None"); + var cfg = new HostCfg + { + Payload = false, FaultControllers = true, Qualified = qualified, + Controllers = [Hx.Plain(r) ? typeof(AlikePlainCtl) : typeof(AlikeApiCtl)], + Minimal = b => + { + RouteHandlerBuilder m = (twin, arity) switch + { + (false, 2) => b.MapGet("/s/alike/m", (int e) => AlikeErr.K2(e).ToHttpResult()), + (false, _) => b.MapGet("/s/alike/m", (int e) => AlikeErr.K3(e).ToHttpResult()), + (true, 2) => b.MapGet("/s/alike/m", (int e) => AlikeErr.T2(e).ToHttpResult()), + (true, _) => b.MapGet("/s/alike/m", (int e) => AlikeErr.T3(e).ToHttpResult()), + }; + if (leave && twin) m.WithoutFaults(TwinB.Same); + else if (leave) m.WithoutFaults(KFault.Clash); + }, + Services = s => + { + s.AddSingleton<ILoggerProvider>(logs); + s.Configure<MvcOptions>(o => o.Conventions.Add(new KeepOnly(typeof(AlikeCtlBase), action))); + // the application lists its own types (ProblemDetails, what a fault is answered as) beside the row's resolver + s.PostConfigure<HttpJsonOptions>(o => ListOwnTypes(o.SerializerOptions)); + s.PostConfigure<MvcJsonOptions>(o => ListOwnTypes(o.JsonSerializerOptions)); + }, + }; + var sides = Sides(r, "alike"); + var ownWrapped = r.Is("json.Converters", "own-for-wrapped"); + var answers = new List<(string, int, Hx.Wire)>(); + WebApplication app; + try { app = await Hx.Start(r, en, cfg); } + catch (Exception ex) when (ex is not Probes.Shapes.Sides.InputsNotEquivalent) { return new(action, $"{action}: host did not start: {ex.GetType().Name}: {Cut(ex.Message)}", twin, leave, qualified, ownWrapped, sides, answers, []); } + await using var _ = app; + // what each error is written as on the wire: the client's view of the two + foreach (var path in sides) + foreach (var e in arity == 3 ? new[] { 1, 2, 3 } : [1, 2]) + answers.Add((path, e, await Hx.Get(app, $"{path}?e={e}"))); + List<string> lines; + lock (logs.Lines) lines = logs.Lines.Where(l => l.Category == "CodoMetis.TypeKit.AspNetCore.FaultResponses" && l.Level == LogLevel.Warning).Select(l => l.Message).ToList(); + return new(action, null, twin, leave, qualified, ownWrapped, sides, answers, lines); + } + + // ============================================================ C12 + /// <summary> + /// C12, a check per obligation on one host: the source's own (own-converter-describes-itself, or options-describe for + /// JsonSchemaDescriptions) at a property, every-position at every position the docs name, and for the decimal converter + /// parameter-keeps-aspnet, the query parameter against what ASP.NET writes without converters. + /// </summary> + public static async Task<string?> C12(Row r) + { + var en = Registry.Of(r); Process(r, en); + var source = r["source"]; + var dec = source == "decimal-converter"; + var family = dec ? "dec" : "coord"; + var dto = dec ? typeof(DecDto) : typeof(CoordDto); + void Register(JsonSerializerOptions o) + { + // first among the options' converters, on both option sets in the same order ("Register the same on both option sets") + switch (source) + { + case "IDescribedJsonConverter": o.Converters.Insert(0, new CoordConverter()); break; + case "JsonSchemaDescriptions": + o.Converters.Insert(0, new CoordPlainConverter()); + o.Converters.Insert(1, new JsonSchemaDescriptions().Describe<Coord>(JsonSchemaDescription.Of(CoordPlainConverter.Schema()))); + break; + case "decimal-converter": o.Converters.Insert(0, new DecimalStringJsonConverter()); break; + default: throw new ArgumentException("unknown source " + source); + } + } + var cfg = Cfg(r, [dec ? "Dec" : "Coord"], + b => { if (dec) b.MapPost("/s/dec/m", (DecDto body, decimal q) => body); else b.MapPost("/s/coord/m", (CoordDto body) => body); }, + s => { s.PostConfigure<HttpJsonOptions>(o => Register(o.SerializerOptions)); s.PostConfigure<MvcJsonOptions>(o => Register(o.JsonSerializerOptions)); }); + WebApplication? app = null; string? down = null; + try { app = await Hx.Start(r, en, cfg); } + catch (Exception ex) when (ex is not Probes.Shapes.Sides.InputsNotEquivalent) { down = "host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } + await using var _ = app; + JsonNode? doc = null; Exception? docFailed = null; + if (app is not null) { try { doc = await Hx.Doc(app); } catch (Exception ex) { docFailed = ex; } } + var described = source == "JsonSchemaDescriptions" ? "options-describe" : "own-converter-describes-itself"; + string[] obligations = dec ? [described, "every-position", "parameter-keeps-aspnet"] : [described, "every-position"]; + string? failed = null; + foreach (var o in obligations) + { + var against = o == "parameter-keeps-aspnet" ? Baseline.Framework : Baseline.Docs("docs/json.md#describing-what-a-converter-writes"); + var f = await r.Check(o, against, async c => + { + if (down is not null) return down; + // a document ASP.NET alone refuses the same way has nothing to check (noted there) + if (docFailed is not null) return await Hx.DocRefusedByAspNetAlone(r, en, cfg, docFailed); + var http = HttpOptions(app!); + var want = Norm(dec ? new DecimalStringJsonConverter().DescribeJson(typeof(decimal), http)!.Schema + : source == "IDescribedJsonConverter" ? CoordConverter.Schema() : CoordPlainConverter.Schema()); + foreach (var path in Sides(r, family)) + { + var op = Op(doc!, path, "post"); + if (op is null) return $"document has no POST {path}"; + if (o == "parameter-keeps-aspnet") + { + // "Parameters aren't JSON": the query parameter keeps ASP.NET's number, never the converter's string + var q = op["parameters"]?.AsArray().FirstOrDefault(p => p?["name"]?.GetValue<string>() == "q"); + if (q is null) return $"{path}: no parameter q"; + var qs = Hx.Deep(doc!, q["schema"] ?? q); + if (!(qs?.ToJsonString() ?? "").Contains("\"number\"")) return $"{path}: the query parameter q is described as {Cut(qs?.ToJsonString())}, not ASP.NET's number"; + if (JsonNode.DeepEquals(Norm(qs), want)) return $"{path}: the query parameter q is described as the converter's JSON"; + continue; + } + if (BodySchema(doc!, op) is not JsonObject body) return $"{path}: no request body schema"; + var props = body["properties"]?.AsObject(); + JsonNode? P(string clr) => props?[JsonName(http, dto, clr)]; + var positions = new (string What, JsonNode? Got, bool Null)[] + { + ("a property", P("At"), false), ("a nullable property", P("Maybe"), true), ("a list's items", P("Route")?["items"], false), + ("a dictionary's additionalProperties", P("ByName")?["additionalProperties"], false), ("inside a value object", P("Vo"), false), + ("inside a patch", P("Patched"), true), + }; + foreach (var (what, got, admitsNull) in positions) + { + // the source's own obligation is held at a property; every-position at each position the docs name + if (o != "every-position" && what != "a property") continue; + if (got is null) return $"{path}: {what} is missing from the body schema: {Cut(body.ToJsonString())}"; + // ASP.NET's OpenAPI 2.0 writer drops the reference of a nullable struct component ({"allOf":[{"x-nullable":true}]}): + // the plain struct beside it, which has no converter, is written the same, so the position has nothing to check + if (what == "a nullable property" && !dec && P("Twin") is { } twin && JsonNode.DeepEquals(got, twin) && Norm(twin) is JsonObject { Count: 0 }) + { + c.Note(NoteKeys.SchemaNullableReferenceLostForPlainToo, path); + continue; + } + if (!JsonNode.DeepEquals(Norm(got), want)) return $"{path}: {what} is {Cut(got.ToJsonString())}, the {source} says {want!.ToJsonString()}"; + if (admitsNull && !Admits(got)) return $"{path}: {what} does not admit null: {Cut(got.ToJsonString())}"; + } + } + c.Note(NoteKeys.Compared, source); + return null; + }); + failed ??= f; + } + return failed; + } + + // ============================================================ C13 + /// <summary> + /// C13, a check per obligation on one host: where the options require the constructor-parameter patch, doc-required-where-rejected + /// (a body without it is rejected, and the document lists it in required); elsewhere doc-optional-where-absent (the document leaves it + /// out of required); and absent-is-default for what the patch reads where a body leaves it out, sets it null or gives it a value. + /// </summary> + public static async Task<string?> C13(Row r) + { + var en = Registry.Of(r); Process(r, en); + var ctor = r["declared"] == "ctor-parameter"; + var family = ctor ? "ctor" : "init"; + var dto = ctor ? typeof(CtorPatchDto) : typeof(InitPatchDto); + var cfg = Cfg(r, [ctor ? "Ctor" : "Init", "Boxed"], b => + { + if (ctor) b.MapPost("/s/ctor/m", (CtorPatchDto body) => Results.Text(body.Floor.State.ToString())); + else b.MapPost("/s/init/m", (InitPatchDto body) => Results.Text(body.Floor.State.ToString())); + }, + // The actions answer a rejected body with 400 themselves: the serializer's rejection reaches ModelState under both kinds of + // controller, and ASP.NET's own ValidationProblemDetails is not this claim's (it cannot be written under MaxDepth=3 or a + // source-generated context that does not list it). + s => s.Configure<ApiBehaviorOptions>(o => { o.SuppressModelStateInvalidFilter = true; o.SuppressMapClientErrors = true; })); + WebApplication app; + try { app = await Hx.Start(r, en, cfg); } + catch (Exception ex) when (ex is not Probes.Shapes.Sides.InputsNotEquivalent) { return "host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message); } + await using var _ = app; + var http = HttpOptions(app); + var respect = http.RespectRequiredConstructorParameters; + // "a patch that is a constructor parameter is required" under the option; an init property "keeps its default, which is Absent" + var required = ctor && respect; + var listing = required ? "doc-required-where-rejected" : "doc-optional-where-absent"; + var floor = JsonName(http, dto, "Floor"); + // 3 as the options read an int: an application's own converter for int (json.Converters=own-for-wrapped) reads its own spelling + var three = Reads(http, "3") ? "3" : JsonSerializer.Serialize(3, http); + JsonNode? doc = null; Exception? docFailed = null; + try { doc = await Hx.Doc(app); } + catch (Exception ex) { docFailed = ex; } + // What each side answered to a body that leaves the patch out, sets it null, and gives it a value; where the request threw, MVC's + // validator throws for any nested struct of a body under a small MaxValidationDepth, which the plain twin on the same side shows. + var bodies = new[] { ("absent", "{}"), ("cleared", $"{{\"{floor}\":null}}"), ("set", $"{{\"{floor}\":{three}}}") }; + var answers = new List<(string Path, string Kind, string Body, Hx.Wire Wire, string? TwinThrew)>(); + foreach (var path in Sides(r, family)) + foreach (var (kind, body) in bodies) + { + var w = await Hx.Send(app, HttpMethod.Post, path, body); + var twin = w.Thrown is not null && path.EndsWith("/c", StringComparison.Ordinal) ? await Hx.Send(app, HttpMethod.Post, path.Replace($"/s/{family}/c", "/s/box/c"), "{}") : null; + answers.Add((path, kind, body, w, twin?.Thrown is { } t && t.Split(':')[0] == w.Thrown!.Split(':')[0] ? t.Split(':')[0] : null)); + } + string? failed = null; + foreach (var o in new[] { listing, "absent-is-default" }) + { + var f = await r.Check(o, Baseline.Docs("docs/merge-patches.md#required-constructor-parameters"), async c => + { + var compared = false; // whether the check judged an answer before the document + foreach (var (path, kind, body, w, twinThrew) in answers) + { + // a body without the patch is the listing's premise where the options reject it, else what absent-is-default reads + if ((kind == "absent" && required ? listing : "absent-is-default") != o) continue; + if (twinThrew is not null) + { + // No sentence cites it: MVC's validator throws for the plain twin's nested struct too, so the check cannot tell. + c.Inconclusive($"{path} {body}: MVC's validator throws {twinThrew} for the plain twin's body too"); + continue; + } + compared = true; + if (w.Thrown is not null) return $"{path} {body}: request threw {w.Thrown}"; + switch (kind) + { + case "absent" when required && w.Status != HttpStatusCode.BadRequest: + return $"{path}: a body without the constructor-parameter patch was answered {(int)w.Status} {Cut(w.Body)} under RespectRequiredConstructorParameters, not rejected"; + case "absent" when !required && (w.Status != HttpStatusCode.OK || w.Body != "Absent"): + return $"{path}: a body without the {(ctor ? "constructor-parameter" : "init")} patch was answered {(int)w.Status} {Cut(w.Body)}, not Absent"; + case "cleared" when w.Status != HttpStatusCode.OK || w.Body != "Null": + return $"{path}: a body with {floor}: null was answered {(int)w.Status} {Cut(w.Body)}, not Null"; + case "set" when w.Status != HttpStatusCode.OK || w.Body != "Value": + return $"{path}: a body with {floor}: {three} was answered {(int)w.Status} {Cut(w.Body)}, not Value"; + } + } + if (o != listing) return null; + // a document ASP.NET alone refuses the same way has nothing to check (noted there) + if (docFailed is not null) return await Hx.DocRefusedByAspNetAlone(r, en, cfg, docFailed, compared); + foreach (var path in Sides(r, family)) + { + var op = Op(doc!, path, "post"); + if (op is null) return $"document has no POST {path}"; + if (BodySchema(doc!, op) is not JsonObject body) return $"{path}: no request body schema"; + if (body["properties"]?[floor] is null) return $"{path}: the body schema has no property {floor}: {Cut(body.ToJsonString())}"; + var listed = body["required"]?.AsArray().Any(x => x?.GetValue<string>() == floor) == true; + if (listed != required) + return $"{path}: the {(ctor ? "constructor-parameter" : "init")} patch {floor} is {(listed ? "" : "not ")}in required under RespectRequiredConstructorParameters={respect}, the serializer {(required ? "rejects" : "accepts")} a body without it"; + } + c.Note(NoteKeys.Compared, $"{floor} {(required ? "" : "not ")}required"); + return null; + }); + failed ??= f; + } + return failed; + } + static bool Reads(JsonSerializerOptions o, string json) { try { return JsonSerializer.Deserialize<int>(json, o) == 3; } catch (Exception ex) when (ex is JsonException or InvalidOperationException) { return false; } } + + // ============================================================ C16 + /// <summary> + /// C16, a check per obligation over two hosts (a described type as a property, and one held only inside a patch). Where the host has + /// controllers and only the minimal-API options carry the converter: differing-refused and patch-held-refused (the document refused + /// with an InvalidOperationException naming the type, for the property's host and the patch's), and error-names-type-and-descriptions + /// (the refusal names what the options say of it, for both), against the refusal. Elsewhere the document builds and describes the + /// type as the converter says: minimal-host-not-compared for a host of minimal APIs alone, same-converters-build where both option + /// sets carry the converter and differ only in naming policy or number handling. + /// </summary> + public static async Task<string?> C16(Row r) + { + var en = Registry.Of(r); Process(r, en); + var refuse = r["difference"] == "converter-only-http" && Hx.HasMvc(r); + var runs = new[] { await Agree(r, en, patch: false), await Agree(r, en, patch: true) }; + var built = r["difference"] == "converter-only-http" ? "minimal-host-not-compared" : "same-converters-build"; + (string Obligation, AgreeRun[] Runs)[] checks = refuse + ? [("differing-refused", [runs[0]]), ("patch-held-refused", [runs[1]]), ("error-names-type-and-descriptions", runs)] + : [(built, runs)]; + string? failed = null; + foreach (var (o, mine) in checks) + { + var f = await r.Check(o, refuse ? Baseline.Refusal : Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), async c => + { + foreach (var a in mine) + if ((refuse ? await a.Refusal(c, r, en, names: o == "error-names-type-and-descriptions") : await a.Built(c, r, en)) is { } fail) return fail; + return null; + }); + failed ??= f; + } + return failed; + } + + /// <summary>A naming policy no row sets: the minimal-API options' names then differ from MVC's whatever the row says.</summary> + sealed class PrefixPolicy : JsonNamingPolicy { public override string ConvertName(string name) => "h_" + name; } + + /// <summary>One host of C16: its document or the exception building it threw, judged by <see cref="Refusal"/> or <see cref="Built"/>.</summary> + sealed record AgreeRun(bool Patch, Type Type, HostCfg Cfg, List<string> Sides, string? Down, JsonNode? Doc, Exception? Failed, string? Name) + { + string Said => (Patch ? CoordPConverter.Schema() : CoordConverter.Schema()).ToJsonString(); + JsonNode? Want => Norm(Patch ? CoordPConverter.Schema() : CoordConverter.Schema()); + + /// <summary> + /// The document refused with an InvalidOperationException naming the type, and with <paramref name="names"/> what the minimal-API + /// options say of it; a refusal ASP.NET alone makes the same way has nothing to check. + /// </summary> + public async Task<string?> Refusal(Check c, Row r, Entry en, bool names) + { + if (Down is not null) return Down; + if (Failed is null) return $"{(Patch ? "a type held only inside a patch, " : "")}{Type.Name}: the host has controllers, its minimal-API options alone describe it ({Want!.ToJsonString()}), and the document was built"; + // not the refusal the claim names: judged against ASP.NET alone + if (Failed is not InvalidOperationException || !Failed.Message.Contains($"'{Type}'", StringComparison.Ordinal)) + return await Hx.DocRefusedByAspNetAlone(r, en, Cfg, Failed) is null ? null + : $"{(Patch ? "patch-held " : "")}{Type.Name}: the document was refused otherwise than naming the type and what the options say: {Failed.GetType().Name}: {Cut(Failed.Message)}"; + if (names && !Failed.Message.Contains(Said, StringComparison.Ordinal)) + return await Hx.DocRefusedByAspNetAlone(r, en, Cfg, Failed) is null ? null + : $"{(Patch ? "patch-held " : "")}{Type.Name}: the refusal names the type but not what the options say ({Said}): {Cut(Failed.Message)}"; + c.Note(NoteKeys.Compared, names ? "refused naming the type and the minimal-API options' description" : "refused naming the type"); + return null; + } + + /// <summary>The document built, describing the type as the converter on both option sets, or the minimal-API options alone, says.</summary> + public async Task<string?> Built(Check c, Row r, Entry en) + { + if (Down is not null) return Down; + // a document ASP.NET alone refuses the same way has nothing to check (noted there) + if (Failed is not null) return await Hx.DocRefusedByAspNetAlone(r, en, Cfg, Failed); + foreach (var path in Sides) + { + var op = Op(Doc!, path, "post"); + if (op is null) return $"document has no POST {path}"; + if (BodySchema(Doc!, op) is not JsonObject body) return $"{path}: no request body schema"; + var got = body["properties"]?[Name!]; + if (got is null) return $"{path}: the body schema has no property {Name} (the minimal-API options' name): {Cut(body.ToJsonString())}"; + if (!JsonNode.DeepEquals(Norm(got), Want)) return $"{path}: {Name} is {Cut(got.ToJsonString())}, the converter on both option sets says {Want!.ToJsonString()}"; + } + c.Note(NoteKeys.Compared, $"{Type.Name} described as its converter says"); + return null; + } + } + + static async Task<AgreeRun> Agree(Row r, Entry en, bool patch) + { + var difference = r["difference"]; + var type = patch ? typeof(CoordP) : typeof(Coord); + var dto = patch ? typeof(AgreePatchDto) : typeof(AgreeDto); + JsonConverter Converter() => patch ? new CoordPConverter() : new CoordConverter(); + var cfg = Cfg(r, [patch ? "AgreeP" : "Agree"], + b => { if (patch) b.MapPost("/s/agreep/m", (AgreePatchDto body) => body); else b.MapPost("/s/agree/m", (AgreeDto body) => body); }, + s => + { + s.PostConfigure<HttpJsonOptions>(o => + { + var x = o.SerializerOptions; + x.Converters.Insert(0, Converter()); + switch (difference) + { + case "converter-only-http": break; + case "naming-policy": x.PropertyNamingPolicy = new PrefixPolicy(); break; + case "number-handling": x.NumberHandling = x.NumberHandling == JsonNumberHandling.Strict ? JsonNumberHandling.AllowReadingFromString : JsonNumberHandling.Strict; break; + default: throw new ArgumentException("unknown difference " + difference); + } + }); + if (difference != "converter-only-http") s.PostConfigure<MvcJsonOptions>(o => o.JsonSerializerOptions.Converters.Insert(0, Converter())); + }); + var sides = Sides(r, patch ? "agreep" : "agree"); + WebApplication app; + try { app = await Hx.Start(r, en, cfg); } + catch (Exception ex) when (ex is not Probes.Shapes.Sides.InputsNotEquivalent) { return new(patch, type, cfg, sides, "host did not start: " + ex.GetType().Name + ": " + Cut(ex.Message), null, null, null); } + await using var _ = app; + var name = JsonName(HttpOptions(app), dto, patch ? "P" : "At"); + try { return new(patch, type, cfg, sides, null, await Hx.Doc(app), null, name); } + catch (Exception ex) { return new(patch, type, cfg, sides, null, null, ex, name); } + } + } +} diff --git a/tools/probe/corpus/harness/Shapes.g.cs b/tools/probe/corpus/harness/Shapes.g.cs index b9c3711f..786b9c57 100644 --- a/tools/probe/corpus/harness/Shapes.g.cs +++ b/tools/probe/corpus/harness/Shapes.g.cs @@ -1,6 +1,7 @@ -// <auto-generated/> by gen_shapes.py; probes' shape catalogue. Do not edit. +// <auto-generated/> by tools/probe/generators/shapes.cs; probes' shape catalogue. Do not edit. #nullable enable #pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003 +using System.Collections.Immutable; using CodoMetis.TypeKit; using CodoMetis.TypeKit.ValueObjects; using CodoMetis.TypeKit.ClosedSets; @@ -251,6 +252,7 @@ public sealed partial record class V_validated_class_own_record_class : IValidat public static Result<V_validated_class_own_record_class, ShapeFault> Create(PRec value) => EqualityComparer<PRec>.Default.Equals(value, new PRec { A = "refused", B = 0 }) ? Result.Error(ShapeFault.Refused) : new V_validated_class_own_record_class(value); } +#nullable disable [JsonPolymorphic] [JsonDerivedType(typeof(PD_V_record_struct_string), "d")] public abstract class PB_V_record_struct_string { } public sealed class PD_V_record_struct_string : PB_V_record_struct_string { public V_record_struct_string Value { get; set; } = default!; } [JsonPolymorphic] [JsonDerivedType(typeof(PD_V_record_struct_Guid), "d")] public abstract class PB_V_record_struct_Guid { } @@ -453,8 +455,43 @@ [JsonPolymorphic] [JsonDerivedType(typeof(PDP_NodaTime_Instant), "d")] public ab public sealed class PDP_NodaTime_Instant : PBP_NodaTime_Instant { public global::NodaTime.Instant Value { get; set; } = default!; } [JsonPolymorphic] [JsonDerivedType(typeof(PDP_own_record_class), "d")] public abstract class PBP_own_record_class { } public sealed class PDP_own_record_class : PBP_own_record_class { public PRec Value { get; set; } = default!; } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_Guid_N), "d")] public abstract class PBP_Guid_N { } +public sealed class PDP_Guid_N : PBP_Guid_N { public Guid? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_int_N), "d")] public abstract class PBP_int_N { } +public sealed class PDP_int_N : PBP_int_N { public int? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_long_N), "d")] public abstract class PBP_long_N { } +public sealed class PDP_long_N : PBP_long_N { public long? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_byte_N), "d")] public abstract class PBP_byte_N { } +public sealed class PDP_byte_N : PBP_byte_N { public byte? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_ulong_N), "d")] public abstract class PBP_ulong_N { } +public sealed class PDP_ulong_N : PBP_ulong_N { public ulong? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_decimal_N), "d")] public abstract class PBP_decimal_N { } +public sealed class PDP_decimal_N : PBP_decimal_N { public decimal? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_double_N), "d")] public abstract class PBP_double_N { } +public sealed class PDP_double_N : PBP_double_N { public double? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_bool_N), "d")] public abstract class PBP_bool_N { } +public sealed class PDP_bool_N : PBP_bool_N { public bool? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_char_N), "d")] public abstract class PBP_char_N { } +public sealed class PDP_char_N : PBP_char_N { public char? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_DateTime_N), "d")] public abstract class PBP_DateTime_N { } +public sealed class PDP_DateTime_N : PBP_DateTime_N { public DateTime? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_DateTimeOffset_N), "d")] public abstract class PBP_DateTimeOffset_N { } +public sealed class PDP_DateTimeOffset_N : PBP_DateTimeOffset_N { public DateTimeOffset? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_DateOnly_N), "d")] public abstract class PBP_DateOnly_N { } +public sealed class PDP_DateOnly_N : PBP_DateOnly_N { public DateOnly? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_TimeOnly_N), "d")] public abstract class PBP_TimeOnly_N { } +public sealed class PDP_TimeOnly_N : PBP_TimeOnly_N { public TimeOnly? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_TimeSpan_N), "d")] public abstract class PBP_TimeSpan_N { } +public sealed class PDP_TimeSpan_N : PBP_TimeSpan_N { public TimeSpan? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_enum_N), "d")] public abstract class PBP_enum_N { } +public sealed class PDP_enum_N : PBP_enum_N { public PEnum? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_flags_enum_N), "d")] public abstract class PBP_flags_enum_N { } +public sealed class PDP_flags_enum_N : PBP_flags_enum_N { public PFlags? Value { get; set; } } +[JsonPolymorphic] [JsonDerivedType(typeof(PDP_NodaTime_Instant_N), "d")] public abstract class PBP_NodaTime_Instant_N { } +public sealed class PDP_NodaTime_Instant_N : PBP_NodaTime_Instant_N { public global::NodaTime.Instant? Value { get; set; } } [JsonPolymorphic] [JsonDerivedType(typeof(PDP_string_), "d")] public abstract class PBP_string_ { } public sealed class PDP_string_ : PBP_string_ { public string Value { get; set; } = ""; } +#nullable enable public sealed record Entry(string Kind, string Wrapped, Type Vo, Type Plain, Func<object> Valid, Func<object?> RefusedFn, Type PolyBase, Type PolyDerived, Type PolyBasePlain, Type PolyDerivedPlain); public static partial class Registry { @@ -481,24 +518,24 @@ static partial void Fill(List<Entry> e) e.Add(new Entry("record-struct", "NodaTime.Instant", typeof(V_record_struct_NodaTime_Instant), typeof(global::NodaTime.Instant), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => null, typeof(PB_V_record_struct_NodaTime_Instant), typeof(PD_V_record_struct_NodaTime_Instant), typeof(PBP_NodaTime_Instant), typeof(PDP_NodaTime_Instant))); e.Add(new Entry("record-struct", "own-record-class", typeof(V_record_struct_own_record_class), typeof(PRec), () => new PRec { A = "x", B = 1 }, () => null, typeof(PB_V_record_struct_own_record_class), typeof(PD_V_record_struct_own_record_class), typeof(PBP_own_record_class), typeof(PDP_own_record_class))); e.Add(new Entry("sealed-record-class", "string", typeof(V_sealed_record_class_string), typeof(string), () => "alpha", () => null, typeof(PB_V_sealed_record_class_string), typeof(PD_V_sealed_record_class_string), typeof(PBP_string), typeof(PDP_string))); - e.Add(new Entry("sealed-record-class", "Guid", typeof(V_sealed_record_class_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => null, typeof(PB_V_sealed_record_class_Guid), typeof(PD_V_sealed_record_class_Guid), typeof(PBP_Guid), typeof(PDP_Guid))); - e.Add(new Entry("sealed-record-class", "int", typeof(V_sealed_record_class_int), typeof(int), () => 42, () => null, typeof(PB_V_sealed_record_class_int), typeof(PD_V_sealed_record_class_int), typeof(PBP_int), typeof(PDP_int))); - e.Add(new Entry("sealed-record-class", "long", typeof(V_sealed_record_class_long), typeof(long), () => 4200000000L, () => null, typeof(PB_V_sealed_record_class_long), typeof(PD_V_sealed_record_class_long), typeof(PBP_long), typeof(PDP_long))); - e.Add(new Entry("sealed-record-class", "byte", typeof(V_sealed_record_class_byte), typeof(byte), () => (byte)7, () => null, typeof(PB_V_sealed_record_class_byte), typeof(PD_V_sealed_record_class_byte), typeof(PBP_byte), typeof(PDP_byte))); - e.Add(new Entry("sealed-record-class", "ulong", typeof(V_sealed_record_class_ulong), typeof(ulong), () => 4200000000UL, () => null, typeof(PB_V_sealed_record_class_ulong), typeof(PD_V_sealed_record_class_ulong), typeof(PBP_ulong), typeof(PDP_ulong))); - e.Add(new Entry("sealed-record-class", "decimal", typeof(V_sealed_record_class_decimal), typeof(decimal), () => 7.80m, () => null, typeof(PB_V_sealed_record_class_decimal), typeof(PD_V_sealed_record_class_decimal), typeof(PBP_decimal), typeof(PDP_decimal))); - e.Add(new Entry("sealed-record-class", "double", typeof(V_sealed_record_class_double), typeof(double), () => 1.5, () => null, typeof(PB_V_sealed_record_class_double), typeof(PD_V_sealed_record_class_double), typeof(PBP_double), typeof(PDP_double))); - e.Add(new Entry("sealed-record-class", "bool", typeof(V_sealed_record_class_bool), typeof(bool), () => true, () => null, typeof(PB_V_sealed_record_class_bool), typeof(PD_V_sealed_record_class_bool), typeof(PBP_bool), typeof(PDP_bool))); - e.Add(new Entry("sealed-record-class", "char", typeof(V_sealed_record_class_char), typeof(char), () => 'a', () => null, typeof(PB_V_sealed_record_class_char), typeof(PD_V_sealed_record_class_char), typeof(PBP_char), typeof(PDP_char))); - e.Add(new Entry("sealed-record-class", "DateTime", typeof(V_sealed_record_class_DateTime), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc), () => null, typeof(PB_V_sealed_record_class_DateTime), typeof(PD_V_sealed_record_class_DateTime), typeof(PBP_DateTime), typeof(PDP_DateTime))); - e.Add(new Entry("sealed-record-class", "DateTimeOffset", typeof(V_sealed_record_class_DateTimeOffset), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero), () => null, typeof(PB_V_sealed_record_class_DateTimeOffset), typeof(PD_V_sealed_record_class_DateTimeOffset), typeof(PBP_DateTimeOffset), typeof(PDP_DateTimeOffset))); - e.Add(new Entry("sealed-record-class", "DateOnly", typeof(V_sealed_record_class_DateOnly), typeof(DateOnly), () => new DateOnly(2026,10,15), () => null, typeof(PB_V_sealed_record_class_DateOnly), typeof(PD_V_sealed_record_class_DateOnly), typeof(PBP_DateOnly), typeof(PDP_DateOnly))); - e.Add(new Entry("sealed-record-class", "TimeOnly", typeof(V_sealed_record_class_TimeOnly), typeof(TimeOnly), () => new TimeOnly(8,30,15), () => null, typeof(PB_V_sealed_record_class_TimeOnly), typeof(PD_V_sealed_record_class_TimeOnly), typeof(PBP_TimeOnly), typeof(PDP_TimeOnly))); - e.Add(new Entry("sealed-record-class", "TimeSpan", typeof(V_sealed_record_class_TimeSpan), typeof(TimeSpan), () => TimeSpan.FromMinutes(90), () => null, typeof(PB_V_sealed_record_class_TimeSpan), typeof(PD_V_sealed_record_class_TimeSpan), typeof(PBP_TimeSpan), typeof(PDP_TimeSpan))); - e.Add(new Entry("sealed-record-class", "enum", typeof(V_sealed_record_class_enum), typeof(PEnum), () => PEnum.B, () => null, typeof(PB_V_sealed_record_class_enum), typeof(PD_V_sealed_record_class_enum), typeof(PBP_enum), typeof(PDP_enum))); - e.Add(new Entry("sealed-record-class", "flags-enum", typeof(V_sealed_record_class_flags_enum), typeof(PFlags), () => PFlags.P | PFlags.Q, () => null, typeof(PB_V_sealed_record_class_flags_enum), typeof(PD_V_sealed_record_class_flags_enum), typeof(PBP_flags_enum), typeof(PDP_flags_enum))); + e.Add(new Entry("sealed-record-class", "Guid", typeof(V_sealed_record_class_Guid), typeof(Guid?), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => null, typeof(PB_V_sealed_record_class_Guid), typeof(PD_V_sealed_record_class_Guid), typeof(PBP_Guid_N), typeof(PDP_Guid_N))); + e.Add(new Entry("sealed-record-class", "int", typeof(V_sealed_record_class_int), typeof(int?), () => 42, () => null, typeof(PB_V_sealed_record_class_int), typeof(PD_V_sealed_record_class_int), typeof(PBP_int_N), typeof(PDP_int_N))); + e.Add(new Entry("sealed-record-class", "long", typeof(V_sealed_record_class_long), typeof(long?), () => 4200000000L, () => null, typeof(PB_V_sealed_record_class_long), typeof(PD_V_sealed_record_class_long), typeof(PBP_long_N), typeof(PDP_long_N))); + e.Add(new Entry("sealed-record-class", "byte", typeof(V_sealed_record_class_byte), typeof(byte?), () => (byte)7, () => null, typeof(PB_V_sealed_record_class_byte), typeof(PD_V_sealed_record_class_byte), typeof(PBP_byte_N), typeof(PDP_byte_N))); + e.Add(new Entry("sealed-record-class", "ulong", typeof(V_sealed_record_class_ulong), typeof(ulong?), () => 4200000000UL, () => null, typeof(PB_V_sealed_record_class_ulong), typeof(PD_V_sealed_record_class_ulong), typeof(PBP_ulong_N), typeof(PDP_ulong_N))); + e.Add(new Entry("sealed-record-class", "decimal", typeof(V_sealed_record_class_decimal), typeof(decimal?), () => 7.80m, () => null, typeof(PB_V_sealed_record_class_decimal), typeof(PD_V_sealed_record_class_decimal), typeof(PBP_decimal_N), typeof(PDP_decimal_N))); + e.Add(new Entry("sealed-record-class", "double", typeof(V_sealed_record_class_double), typeof(double?), () => 1.5, () => null, typeof(PB_V_sealed_record_class_double), typeof(PD_V_sealed_record_class_double), typeof(PBP_double_N), typeof(PDP_double_N))); + e.Add(new Entry("sealed-record-class", "bool", typeof(V_sealed_record_class_bool), typeof(bool?), () => true, () => null, typeof(PB_V_sealed_record_class_bool), typeof(PD_V_sealed_record_class_bool), typeof(PBP_bool_N), typeof(PDP_bool_N))); + e.Add(new Entry("sealed-record-class", "char", typeof(V_sealed_record_class_char), typeof(char?), () => 'a', () => null, typeof(PB_V_sealed_record_class_char), typeof(PD_V_sealed_record_class_char), typeof(PBP_char_N), typeof(PDP_char_N))); + e.Add(new Entry("sealed-record-class", "DateTime", typeof(V_sealed_record_class_DateTime), typeof(DateTime?), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc), () => null, typeof(PB_V_sealed_record_class_DateTime), typeof(PD_V_sealed_record_class_DateTime), typeof(PBP_DateTime_N), typeof(PDP_DateTime_N))); + e.Add(new Entry("sealed-record-class", "DateTimeOffset", typeof(V_sealed_record_class_DateTimeOffset), typeof(DateTimeOffset?), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero), () => null, typeof(PB_V_sealed_record_class_DateTimeOffset), typeof(PD_V_sealed_record_class_DateTimeOffset), typeof(PBP_DateTimeOffset_N), typeof(PDP_DateTimeOffset_N))); + e.Add(new Entry("sealed-record-class", "DateOnly", typeof(V_sealed_record_class_DateOnly), typeof(DateOnly?), () => new DateOnly(2026,10,15), () => null, typeof(PB_V_sealed_record_class_DateOnly), typeof(PD_V_sealed_record_class_DateOnly), typeof(PBP_DateOnly_N), typeof(PDP_DateOnly_N))); + e.Add(new Entry("sealed-record-class", "TimeOnly", typeof(V_sealed_record_class_TimeOnly), typeof(TimeOnly?), () => new TimeOnly(8,30,15), () => null, typeof(PB_V_sealed_record_class_TimeOnly), typeof(PD_V_sealed_record_class_TimeOnly), typeof(PBP_TimeOnly_N), typeof(PDP_TimeOnly_N))); + e.Add(new Entry("sealed-record-class", "TimeSpan", typeof(V_sealed_record_class_TimeSpan), typeof(TimeSpan?), () => TimeSpan.FromMinutes(90), () => null, typeof(PB_V_sealed_record_class_TimeSpan), typeof(PD_V_sealed_record_class_TimeSpan), typeof(PBP_TimeSpan_N), typeof(PDP_TimeSpan_N))); + e.Add(new Entry("sealed-record-class", "enum", typeof(V_sealed_record_class_enum), typeof(PEnum?), () => PEnum.B, () => null, typeof(PB_V_sealed_record_class_enum), typeof(PD_V_sealed_record_class_enum), typeof(PBP_enum_N), typeof(PDP_enum_N))); + e.Add(new Entry("sealed-record-class", "flags-enum", typeof(V_sealed_record_class_flags_enum), typeof(PFlags?), () => PFlags.P | PFlags.Q, () => null, typeof(PB_V_sealed_record_class_flags_enum), typeof(PD_V_sealed_record_class_flags_enum), typeof(PBP_flags_enum_N), typeof(PDP_flags_enum_N))); e.Add(new Entry("sealed-record-class", "Uri", typeof(V_sealed_record_class_Uri), typeof(Uri), () => new Uri("https://example.com/a"), () => null, typeof(PB_V_sealed_record_class_Uri), typeof(PD_V_sealed_record_class_Uri), typeof(PBP_Uri), typeof(PDP_Uri))); - e.Add(new Entry("sealed-record-class", "NodaTime.Instant", typeof(V_sealed_record_class_NodaTime_Instant), typeof(global::NodaTime.Instant), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => null, typeof(PB_V_sealed_record_class_NodaTime_Instant), typeof(PD_V_sealed_record_class_NodaTime_Instant), typeof(PBP_NodaTime_Instant), typeof(PDP_NodaTime_Instant))); + e.Add(new Entry("sealed-record-class", "NodaTime.Instant", typeof(V_sealed_record_class_NodaTime_Instant), typeof(global::NodaTime.Instant?), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => null, typeof(PB_V_sealed_record_class_NodaTime_Instant), typeof(PD_V_sealed_record_class_NodaTime_Instant), typeof(PBP_NodaTime_Instant_N), typeof(PDP_NodaTime_Instant_N))); e.Add(new Entry("sealed-record-class", "own-record-class", typeof(V_sealed_record_class_own_record_class), typeof(PRec), () => new PRec { A = "x", B = 1 }, () => null, typeof(PB_V_sealed_record_class_own_record_class), typeof(PD_V_sealed_record_class_own_record_class), typeof(PBP_own_record_class), typeof(PDP_own_record_class))); e.Add(new Entry("validated-struct", "string", typeof(V_validated_struct_string), typeof(string), () => "alpha", () => "SECRET-7f3a", typeof(PB_V_validated_struct_string), typeof(PD_V_validated_struct_string), typeof(PBP_string), typeof(PDP_string))); e.Add(new Entry("validated-struct", "Guid", typeof(V_validated_struct_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => Guid.Empty, typeof(PB_V_validated_struct_Guid), typeof(PD_V_validated_struct_Guid), typeof(PBP_Guid), typeof(PDP_Guid))); @@ -521,233 +558,9002 @@ static partial void Fill(List<Entry> e) e.Add(new Entry("validated-struct", "NodaTime.Instant", typeof(V_validated_struct_NodaTime_Instant), typeof(global::NodaTime.Instant), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => global::NodaTime.Instant.MinValue, typeof(PB_V_validated_struct_NodaTime_Instant), typeof(PD_V_validated_struct_NodaTime_Instant), typeof(PBP_NodaTime_Instant), typeof(PDP_NodaTime_Instant))); e.Add(new Entry("validated-struct", "own-record-class", typeof(V_validated_struct_own_record_class), typeof(PRec), () => new PRec { A = "x", B = 1 }, () => new PRec { A = "refused", B = 0 }, typeof(PB_V_validated_struct_own_record_class), typeof(PD_V_validated_struct_own_record_class), typeof(PBP_own_record_class), typeof(PDP_own_record_class))); e.Add(new Entry("validated-class", "string", typeof(V_validated_class_string), typeof(string), () => "alpha", () => "SECRET-7f3a", typeof(PB_V_validated_class_string), typeof(PD_V_validated_class_string), typeof(PBP_string), typeof(PDP_string))); - e.Add(new Entry("validated-class", "Guid", typeof(V_validated_class_Guid), typeof(Guid), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => Guid.Empty, typeof(PB_V_validated_class_Guid), typeof(PD_V_validated_class_Guid), typeof(PBP_Guid), typeof(PDP_Guid))); - e.Add(new Entry("validated-class", "int", typeof(V_validated_class_int), typeof(int), () => 42, () => -1, typeof(PB_V_validated_class_int), typeof(PD_V_validated_class_int), typeof(PBP_int), typeof(PDP_int))); - e.Add(new Entry("validated-class", "long", typeof(V_validated_class_long), typeof(long), () => 4200000000L, () => -1L, typeof(PB_V_validated_class_long), typeof(PD_V_validated_class_long), typeof(PBP_long), typeof(PDP_long))); - e.Add(new Entry("validated-class", "byte", typeof(V_validated_class_byte), typeof(byte), () => (byte)7, () => (byte)255, typeof(PB_V_validated_class_byte), typeof(PD_V_validated_class_byte), typeof(PBP_byte), typeof(PDP_byte))); - e.Add(new Entry("validated-class", "ulong", typeof(V_validated_class_ulong), typeof(ulong), () => 4200000000UL, () => 0UL, typeof(PB_V_validated_class_ulong), typeof(PD_V_validated_class_ulong), typeof(PBP_ulong), typeof(PDP_ulong))); - e.Add(new Entry("validated-class", "decimal", typeof(V_validated_class_decimal), typeof(decimal), () => 7.80m, () => -1m, typeof(PB_V_validated_class_decimal), typeof(PD_V_validated_class_decimal), typeof(PBP_decimal), typeof(PDP_decimal))); - e.Add(new Entry("validated-class", "double", typeof(V_validated_class_double), typeof(double), () => 1.5, () => -1.0, typeof(PB_V_validated_class_double), typeof(PD_V_validated_class_double), typeof(PBP_double), typeof(PDP_double))); - e.Add(new Entry("validated-class", "bool", typeof(V_validated_class_bool), typeof(bool), () => true, () => false, typeof(PB_V_validated_class_bool), typeof(PD_V_validated_class_bool), typeof(PBP_bool), typeof(PDP_bool))); - e.Add(new Entry("validated-class", "char", typeof(V_validated_class_char), typeof(char), () => 'a', () => 'x', typeof(PB_V_validated_class_char), typeof(PD_V_validated_class_char), typeof(PBP_char), typeof(PDP_char))); - e.Add(new Entry("validated-class", "DateTime", typeof(V_validated_class_DateTime), typeof(DateTime), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc), () => DateTime.MinValue, typeof(PB_V_validated_class_DateTime), typeof(PD_V_validated_class_DateTime), typeof(PBP_DateTime), typeof(PDP_DateTime))); - e.Add(new Entry("validated-class", "DateTimeOffset", typeof(V_validated_class_DateTimeOffset), typeof(DateTimeOffset), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero), () => DateTimeOffset.MinValue, typeof(PB_V_validated_class_DateTimeOffset), typeof(PD_V_validated_class_DateTimeOffset), typeof(PBP_DateTimeOffset), typeof(PDP_DateTimeOffset))); - e.Add(new Entry("validated-class", "DateOnly", typeof(V_validated_class_DateOnly), typeof(DateOnly), () => new DateOnly(2026,10,15), () => DateOnly.MinValue, typeof(PB_V_validated_class_DateOnly), typeof(PD_V_validated_class_DateOnly), typeof(PBP_DateOnly), typeof(PDP_DateOnly))); - e.Add(new Entry("validated-class", "TimeOnly", typeof(V_validated_class_TimeOnly), typeof(TimeOnly), () => new TimeOnly(8,30,15), () => TimeOnly.MinValue, typeof(PB_V_validated_class_TimeOnly), typeof(PD_V_validated_class_TimeOnly), typeof(PBP_TimeOnly), typeof(PDP_TimeOnly))); - e.Add(new Entry("validated-class", "TimeSpan", typeof(V_validated_class_TimeSpan), typeof(TimeSpan), () => TimeSpan.FromMinutes(90), () => TimeSpan.MinValue, typeof(PB_V_validated_class_TimeSpan), typeof(PD_V_validated_class_TimeSpan), typeof(PBP_TimeSpan), typeof(PDP_TimeSpan))); - e.Add(new Entry("validated-class", "enum", typeof(V_validated_class_enum), typeof(PEnum), () => PEnum.B, () => (PEnum)99, typeof(PB_V_validated_class_enum), typeof(PD_V_validated_class_enum), typeof(PBP_enum), typeof(PDP_enum))); - e.Add(new Entry("validated-class", "flags-enum", typeof(V_validated_class_flags_enum), typeof(PFlags), () => PFlags.P | PFlags.Q, () => (PFlags)0, typeof(PB_V_validated_class_flags_enum), typeof(PD_V_validated_class_flags_enum), typeof(PBP_flags_enum), typeof(PDP_flags_enum))); + e.Add(new Entry("validated-class", "Guid", typeof(V_validated_class_Guid), typeof(Guid?), () => Guid.Parse("11111111-1111-1111-1111-111111111111"), () => Guid.Empty, typeof(PB_V_validated_class_Guid), typeof(PD_V_validated_class_Guid), typeof(PBP_Guid_N), typeof(PDP_Guid_N))); + e.Add(new Entry("validated-class", "int", typeof(V_validated_class_int), typeof(int?), () => 42, () => -1, typeof(PB_V_validated_class_int), typeof(PD_V_validated_class_int), typeof(PBP_int_N), typeof(PDP_int_N))); + e.Add(new Entry("validated-class", "long", typeof(V_validated_class_long), typeof(long?), () => 4200000000L, () => -1L, typeof(PB_V_validated_class_long), typeof(PD_V_validated_class_long), typeof(PBP_long_N), typeof(PDP_long_N))); + e.Add(new Entry("validated-class", "byte", typeof(V_validated_class_byte), typeof(byte?), () => (byte)7, () => (byte)255, typeof(PB_V_validated_class_byte), typeof(PD_V_validated_class_byte), typeof(PBP_byte_N), typeof(PDP_byte_N))); + e.Add(new Entry("validated-class", "ulong", typeof(V_validated_class_ulong), typeof(ulong?), () => 4200000000UL, () => 0UL, typeof(PB_V_validated_class_ulong), typeof(PD_V_validated_class_ulong), typeof(PBP_ulong_N), typeof(PDP_ulong_N))); + e.Add(new Entry("validated-class", "decimal", typeof(V_validated_class_decimal), typeof(decimal?), () => 7.80m, () => -1m, typeof(PB_V_validated_class_decimal), typeof(PD_V_validated_class_decimal), typeof(PBP_decimal_N), typeof(PDP_decimal_N))); + e.Add(new Entry("validated-class", "double", typeof(V_validated_class_double), typeof(double?), () => 1.5, () => -1.0, typeof(PB_V_validated_class_double), typeof(PD_V_validated_class_double), typeof(PBP_double_N), typeof(PDP_double_N))); + e.Add(new Entry("validated-class", "bool", typeof(V_validated_class_bool), typeof(bool?), () => true, () => false, typeof(PB_V_validated_class_bool), typeof(PD_V_validated_class_bool), typeof(PBP_bool_N), typeof(PDP_bool_N))); + e.Add(new Entry("validated-class", "char", typeof(V_validated_class_char), typeof(char?), () => 'a', () => 'x', typeof(PB_V_validated_class_char), typeof(PD_V_validated_class_char), typeof(PBP_char_N), typeof(PDP_char_N))); + e.Add(new Entry("validated-class", "DateTime", typeof(V_validated_class_DateTime), typeof(DateTime?), () => new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc), () => DateTime.MinValue, typeof(PB_V_validated_class_DateTime), typeof(PD_V_validated_class_DateTime), typeof(PBP_DateTime_N), typeof(PDP_DateTime_N))); + e.Add(new Entry("validated-class", "DateTimeOffset", typeof(V_validated_class_DateTimeOffset), typeof(DateTimeOffset?), () => new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero), () => DateTimeOffset.MinValue, typeof(PB_V_validated_class_DateTimeOffset), typeof(PD_V_validated_class_DateTimeOffset), typeof(PBP_DateTimeOffset_N), typeof(PDP_DateTimeOffset_N))); + e.Add(new Entry("validated-class", "DateOnly", typeof(V_validated_class_DateOnly), typeof(DateOnly?), () => new DateOnly(2026,10,15), () => DateOnly.MinValue, typeof(PB_V_validated_class_DateOnly), typeof(PD_V_validated_class_DateOnly), typeof(PBP_DateOnly_N), typeof(PDP_DateOnly_N))); + e.Add(new Entry("validated-class", "TimeOnly", typeof(V_validated_class_TimeOnly), typeof(TimeOnly?), () => new TimeOnly(8,30,15), () => TimeOnly.MinValue, typeof(PB_V_validated_class_TimeOnly), typeof(PD_V_validated_class_TimeOnly), typeof(PBP_TimeOnly_N), typeof(PDP_TimeOnly_N))); + e.Add(new Entry("validated-class", "TimeSpan", typeof(V_validated_class_TimeSpan), typeof(TimeSpan?), () => TimeSpan.FromMinutes(90), () => TimeSpan.MinValue, typeof(PB_V_validated_class_TimeSpan), typeof(PD_V_validated_class_TimeSpan), typeof(PBP_TimeSpan_N), typeof(PDP_TimeSpan_N))); + e.Add(new Entry("validated-class", "enum", typeof(V_validated_class_enum), typeof(PEnum?), () => PEnum.B, () => (PEnum)99, typeof(PB_V_validated_class_enum), typeof(PD_V_validated_class_enum), typeof(PBP_enum_N), typeof(PDP_enum_N))); + e.Add(new Entry("validated-class", "flags-enum", typeof(V_validated_class_flags_enum), typeof(PFlags?), () => PFlags.P | PFlags.Q, () => (PFlags)0, typeof(PB_V_validated_class_flags_enum), typeof(PD_V_validated_class_flags_enum), typeof(PBP_flags_enum_N), typeof(PDP_flags_enum_N))); e.Add(new Entry("validated-class", "Uri", typeof(V_validated_class_Uri), typeof(Uri), () => new Uri("https://example.com/a"), () => new Uri("https://example.com/refused"), typeof(PB_V_validated_class_Uri), typeof(PD_V_validated_class_Uri), typeof(PBP_Uri), typeof(PDP_Uri))); - e.Add(new Entry("validated-class", "NodaTime.Instant", typeof(V_validated_class_NodaTime_Instant), typeof(global::NodaTime.Instant), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => global::NodaTime.Instant.MinValue, typeof(PB_V_validated_class_NodaTime_Instant), typeof(PD_V_validated_class_NodaTime_Instant), typeof(PBP_NodaTime_Instant), typeof(PDP_NodaTime_Instant))); + e.Add(new Entry("validated-class", "NodaTime.Instant", typeof(V_validated_class_NodaTime_Instant), typeof(global::NodaTime.Instant?), () => global::NodaTime.Instant.FromUtc(2026,10,15,8,30), () => global::NodaTime.Instant.MinValue, typeof(PB_V_validated_class_NodaTime_Instant), typeof(PD_V_validated_class_NodaTime_Instant), typeof(PBP_NodaTime_Instant_N), typeof(PDP_NodaTime_Instant_N))); e.Add(new Entry("validated-class", "own-record-class", typeof(V_validated_class_own_record_class), typeof(PRec), () => new PRec { A = "x", B = 1 }, () => new PRec { A = "refused", B = 0 }, typeof(PB_V_validated_class_own_record_class), typeof(PD_V_validated_class_own_record_class), typeof(PBP_own_record_class), typeof(PDP_own_record_class))); e.Add(new Entry("closed-set", "-", typeof(PSet), typeof(string), () => "Alpha", () => null, typeof(PB_PSet), typeof(PD_PSet), typeof(PBP_string_), typeof(PDP_string_))); } } [JsonSourceGenerationOptions] [JsonSerializable(typeof(V_record_struct_string))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_string>))] -[JsonSerializable(typeof(string))] -[JsonSerializable(typeof(Cn_class_P<string>))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_string>), TypeInfoPropertyName = "T0")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_string>), TypeInfoPropertyName = "T1")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_string>), TypeInfoPropertyName = "T2")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_string>), TypeInfoPropertyName = "T3")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_string, int>), TypeInfoPropertyName = "T4")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_string?>), TypeInfoPropertyName = "T5")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_string?>), TypeInfoPropertyName = "T6")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_string?>), TypeInfoPropertyName = "T7")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_string?>), TypeInfoPropertyName = "T8")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_string?, int>), TypeInfoPropertyName = "T9")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_string>), TypeInfoPropertyName = "T10")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_string>), TypeInfoPropertyName = "T11")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_string>), TypeInfoPropertyName = "T12")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_string>), TypeInfoPropertyName = "T13")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_string, int>), TypeInfoPropertyName = "T14")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_string>>), TypeInfoPropertyName = "T15")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_string>>), TypeInfoPropertyName = "T16")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_string>>), TypeInfoPropertyName = "T17")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_string>>), TypeInfoPropertyName = "T18")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_string>, int>), TypeInfoPropertyName = "T19")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_string[]>), TypeInfoPropertyName = "T20")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_string[]>), TypeInfoPropertyName = "T21")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_string[]>), TypeInfoPropertyName = "T22")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_string[]>), TypeInfoPropertyName = "T23")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_string[], int>), TypeInfoPropertyName = "T24")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_string>>), TypeInfoPropertyName = "T25")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_string>>), TypeInfoPropertyName = "T26")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_string>>), TypeInfoPropertyName = "T27")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_string>>), TypeInfoPropertyName = "T28")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_string>, int>), TypeInfoPropertyName = "T29")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_string>>), TypeInfoPropertyName = "T30")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_string>>), TypeInfoPropertyName = "T31")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_string>>), TypeInfoPropertyName = "T32")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_string>>), TypeInfoPropertyName = "T33")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_string>, int>), TypeInfoPropertyName = "T34")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_string, int>>), TypeInfoPropertyName = "T35")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_string, int>>), TypeInfoPropertyName = "T36")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_string, int>>), TypeInfoPropertyName = "T37")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_string, int>>), TypeInfoPropertyName = "T38")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_string, int>, int>), TypeInfoPropertyName = "T39")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_string>>), TypeInfoPropertyName = "T40")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_string>>), TypeInfoPropertyName = "T41")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_string>>), TypeInfoPropertyName = "T42")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_string>>), TypeInfoPropertyName = "T43")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_string>, int>), TypeInfoPropertyName = "T44")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_string>), TypeInfoPropertyName = "T45")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_string>), TypeInfoPropertyName = "T46")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_string>), TypeInfoPropertyName = "T47")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_string>), TypeInfoPropertyName = "T48")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_string, int>), TypeInfoPropertyName = "T49")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_string>), TypeInfoPropertyName = "T50")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_string>), TypeInfoPropertyName = "T51")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_string>), TypeInfoPropertyName = "T52")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_string>), TypeInfoPropertyName = "T53")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_string, int>), TypeInfoPropertyName = "T54")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_string>>), TypeInfoPropertyName = "T55")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_string>>), TypeInfoPropertyName = "T56")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_string>>), TypeInfoPropertyName = "T57")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_string>>), TypeInfoPropertyName = "T58")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_string>, int>), TypeInfoPropertyName = "T59")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_string>), TypeInfoPropertyName = "T60")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_string>), TypeInfoPropertyName = "T61")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_string>), TypeInfoPropertyName = "T62")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_string>), TypeInfoPropertyName = "T63")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_string, int>), TypeInfoPropertyName = "T64")] [JsonSerializable(typeof(V_record_struct_Guid))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_Guid>))] -[JsonSerializable(typeof(Guid))] -[JsonSerializable(typeof(Cn_class_P<Guid>))] -[JsonSerializable(typeof(Guid?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Guid>), TypeInfoPropertyName = "T65")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Guid>), TypeInfoPropertyName = "T66")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Guid>), TypeInfoPropertyName = "T67")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Guid>), TypeInfoPropertyName = "T68")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Guid, int>), TypeInfoPropertyName = "T69")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Guid?>), TypeInfoPropertyName = "T70")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Guid?>), TypeInfoPropertyName = "T71")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Guid?>), TypeInfoPropertyName = "T72")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Guid?>), TypeInfoPropertyName = "T73")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Guid?, int>), TypeInfoPropertyName = "T74")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_Guid>), TypeInfoPropertyName = "T75")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_Guid>), TypeInfoPropertyName = "T76")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_Guid>), TypeInfoPropertyName = "T77")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_Guid>), TypeInfoPropertyName = "T78")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_Guid, int>), TypeInfoPropertyName = "T79")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_Guid>>), TypeInfoPropertyName = "T80")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_Guid>>), TypeInfoPropertyName = "T81")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_Guid>>), TypeInfoPropertyName = "T82")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_Guid>>), TypeInfoPropertyName = "T83")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_Guid>, int>), TypeInfoPropertyName = "T84")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Guid[]>), TypeInfoPropertyName = "T85")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Guid[]>), TypeInfoPropertyName = "T86")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Guid[]>), TypeInfoPropertyName = "T87")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Guid[]>), TypeInfoPropertyName = "T88")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Guid[], int>), TypeInfoPropertyName = "T89")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_Guid>>), TypeInfoPropertyName = "T90")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_Guid>>), TypeInfoPropertyName = "T91")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_Guid>>), TypeInfoPropertyName = "T92")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_Guid>>), TypeInfoPropertyName = "T93")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_Guid>, int>), TypeInfoPropertyName = "T94")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_Guid>>), TypeInfoPropertyName = "T95")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_Guid>>), TypeInfoPropertyName = "T96")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_Guid>>), TypeInfoPropertyName = "T97")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_Guid>>), TypeInfoPropertyName = "T98")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_Guid>, int>), TypeInfoPropertyName = "T99")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_Guid, int>>), TypeInfoPropertyName = "T100")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_Guid, int>>), TypeInfoPropertyName = "T101")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_Guid, int>>), TypeInfoPropertyName = "T102")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_Guid, int>>), TypeInfoPropertyName = "T103")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_Guid, int>, int>), TypeInfoPropertyName = "T104")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_Guid>>), TypeInfoPropertyName = "T105")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_Guid>>), TypeInfoPropertyName = "T106")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_Guid>>), TypeInfoPropertyName = "T107")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_Guid>>), TypeInfoPropertyName = "T108")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_Guid>, int>), TypeInfoPropertyName = "T109")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_Guid>), TypeInfoPropertyName = "T110")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_Guid>), TypeInfoPropertyName = "T111")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_Guid>), TypeInfoPropertyName = "T112")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_Guid>), TypeInfoPropertyName = "T113")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_Guid, int>), TypeInfoPropertyName = "T114")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_Guid>), TypeInfoPropertyName = "T115")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_Guid>), TypeInfoPropertyName = "T116")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_Guid>), TypeInfoPropertyName = "T117")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_Guid>), TypeInfoPropertyName = "T118")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_Guid, int>), TypeInfoPropertyName = "T119")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_Guid>>), TypeInfoPropertyName = "T120")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_Guid>>), TypeInfoPropertyName = "T121")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_Guid>>), TypeInfoPropertyName = "T122")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_Guid>>), TypeInfoPropertyName = "T123")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_Guid>, int>), TypeInfoPropertyName = "T124")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_Guid>), TypeInfoPropertyName = "T125")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_Guid>), TypeInfoPropertyName = "T126")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_Guid>), TypeInfoPropertyName = "T127")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_Guid>), TypeInfoPropertyName = "T128")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_Guid, int>), TypeInfoPropertyName = "T129")] [JsonSerializable(typeof(V_record_struct_int))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_int>))] -[JsonSerializable(typeof(int))] -[JsonSerializable(typeof(Cn_class_P<int>))] -[JsonSerializable(typeof(int?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_int>), TypeInfoPropertyName = "T130")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_int>), TypeInfoPropertyName = "T131")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_int>), TypeInfoPropertyName = "T132")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_int>), TypeInfoPropertyName = "T133")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_int, int>), TypeInfoPropertyName = "T134")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_int?>), TypeInfoPropertyName = "T135")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_int?>), TypeInfoPropertyName = "T136")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_int?>), TypeInfoPropertyName = "T137")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_int?>), TypeInfoPropertyName = "T138")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_int?, int>), TypeInfoPropertyName = "T139")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_int>), TypeInfoPropertyName = "T140")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_int>), TypeInfoPropertyName = "T141")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_int>), TypeInfoPropertyName = "T142")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_int>), TypeInfoPropertyName = "T143")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_int, int>), TypeInfoPropertyName = "T144")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_int>>), TypeInfoPropertyName = "T145")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_int>>), TypeInfoPropertyName = "T146")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_int>>), TypeInfoPropertyName = "T147")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_int>>), TypeInfoPropertyName = "T148")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_int>, int>), TypeInfoPropertyName = "T149")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_int[]>), TypeInfoPropertyName = "T150")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_int[]>), TypeInfoPropertyName = "T151")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_int[]>), TypeInfoPropertyName = "T152")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_int[]>), TypeInfoPropertyName = "T153")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_int[], int>), TypeInfoPropertyName = "T154")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_int>>), TypeInfoPropertyName = "T155")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_int>>), TypeInfoPropertyName = "T156")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_int>>), TypeInfoPropertyName = "T157")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_int>>), TypeInfoPropertyName = "T158")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_int>, int>), TypeInfoPropertyName = "T159")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_int>>), TypeInfoPropertyName = "T160")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_int>>), TypeInfoPropertyName = "T161")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_int>>), TypeInfoPropertyName = "T162")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_int>>), TypeInfoPropertyName = "T163")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_int>, int>), TypeInfoPropertyName = "T164")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_int, int>>), TypeInfoPropertyName = "T165")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_int, int>>), TypeInfoPropertyName = "T166")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_int, int>>), TypeInfoPropertyName = "T167")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_int, int>>), TypeInfoPropertyName = "T168")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_int, int>, int>), TypeInfoPropertyName = "T169")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_int>>), TypeInfoPropertyName = "T170")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_int>>), TypeInfoPropertyName = "T171")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_int>>), TypeInfoPropertyName = "T172")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_int>>), TypeInfoPropertyName = "T173")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_int>, int>), TypeInfoPropertyName = "T174")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_int>), TypeInfoPropertyName = "T175")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_int>), TypeInfoPropertyName = "T176")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_int>), TypeInfoPropertyName = "T177")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_int>), TypeInfoPropertyName = "T178")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_int, int>), TypeInfoPropertyName = "T179")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_int>), TypeInfoPropertyName = "T180")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_int>), TypeInfoPropertyName = "T181")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_int>), TypeInfoPropertyName = "T182")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_int>), TypeInfoPropertyName = "T183")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_int, int>), TypeInfoPropertyName = "T184")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_int>>), TypeInfoPropertyName = "T185")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_int>>), TypeInfoPropertyName = "T186")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_int>>), TypeInfoPropertyName = "T187")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_int>>), TypeInfoPropertyName = "T188")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_int>, int>), TypeInfoPropertyName = "T189")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_int>), TypeInfoPropertyName = "T190")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_int>), TypeInfoPropertyName = "T191")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_int>), TypeInfoPropertyName = "T192")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_int>), TypeInfoPropertyName = "T193")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_int, int>), TypeInfoPropertyName = "T194")] [JsonSerializable(typeof(V_record_struct_long))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_long>))] -[JsonSerializable(typeof(long))] -[JsonSerializable(typeof(Cn_class_P<long>))] -[JsonSerializable(typeof(long?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_long>), TypeInfoPropertyName = "T195")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_long>), TypeInfoPropertyName = "T196")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_long>), TypeInfoPropertyName = "T197")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_long>), TypeInfoPropertyName = "T198")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_long, int>), TypeInfoPropertyName = "T199")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_long?>), TypeInfoPropertyName = "T200")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_long?>), TypeInfoPropertyName = "T201")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_long?>), TypeInfoPropertyName = "T202")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_long?>), TypeInfoPropertyName = "T203")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_long?, int>), TypeInfoPropertyName = "T204")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_long>), TypeInfoPropertyName = "T205")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_long>), TypeInfoPropertyName = "T206")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_long>), TypeInfoPropertyName = "T207")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_long>), TypeInfoPropertyName = "T208")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_long, int>), TypeInfoPropertyName = "T209")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_long>>), TypeInfoPropertyName = "T210")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_long>>), TypeInfoPropertyName = "T211")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_long>>), TypeInfoPropertyName = "T212")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_long>>), TypeInfoPropertyName = "T213")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_long>, int>), TypeInfoPropertyName = "T214")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_long[]>), TypeInfoPropertyName = "T215")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_long[]>), TypeInfoPropertyName = "T216")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_long[]>), TypeInfoPropertyName = "T217")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_long[]>), TypeInfoPropertyName = "T218")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_long[], int>), TypeInfoPropertyName = "T219")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_long>>), TypeInfoPropertyName = "T220")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_long>>), TypeInfoPropertyName = "T221")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_long>>), TypeInfoPropertyName = "T222")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_long>>), TypeInfoPropertyName = "T223")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_long>, int>), TypeInfoPropertyName = "T224")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_long>>), TypeInfoPropertyName = "T225")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_long>>), TypeInfoPropertyName = "T226")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_long>>), TypeInfoPropertyName = "T227")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_long>>), TypeInfoPropertyName = "T228")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_long>, int>), TypeInfoPropertyName = "T229")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_long, int>>), TypeInfoPropertyName = "T230")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_long, int>>), TypeInfoPropertyName = "T231")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_long, int>>), TypeInfoPropertyName = "T232")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_long, int>>), TypeInfoPropertyName = "T233")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_long, int>, int>), TypeInfoPropertyName = "T234")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_long>>), TypeInfoPropertyName = "T235")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_long>>), TypeInfoPropertyName = "T236")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_long>>), TypeInfoPropertyName = "T237")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_long>>), TypeInfoPropertyName = "T238")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_long>, int>), TypeInfoPropertyName = "T239")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_long>), TypeInfoPropertyName = "T240")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_long>), TypeInfoPropertyName = "T241")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_long>), TypeInfoPropertyName = "T242")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_long>), TypeInfoPropertyName = "T243")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_long, int>), TypeInfoPropertyName = "T244")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_long>), TypeInfoPropertyName = "T245")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_long>), TypeInfoPropertyName = "T246")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_long>), TypeInfoPropertyName = "T247")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_long>), TypeInfoPropertyName = "T248")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_long, int>), TypeInfoPropertyName = "T249")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_long>>), TypeInfoPropertyName = "T250")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_long>>), TypeInfoPropertyName = "T251")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_long>>), TypeInfoPropertyName = "T252")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_long>>), TypeInfoPropertyName = "T253")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_long>, int>), TypeInfoPropertyName = "T254")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_long>), TypeInfoPropertyName = "T255")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_long>), TypeInfoPropertyName = "T256")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_long>), TypeInfoPropertyName = "T257")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_long>), TypeInfoPropertyName = "T258")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_long, int>), TypeInfoPropertyName = "T259")] [JsonSerializable(typeof(V_record_struct_byte))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_byte>))] -[JsonSerializable(typeof(byte))] -[JsonSerializable(typeof(Cn_class_P<byte>))] -[JsonSerializable(typeof(byte?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_byte>), TypeInfoPropertyName = "T260")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_byte>), TypeInfoPropertyName = "T261")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_byte>), TypeInfoPropertyName = "T262")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_byte>), TypeInfoPropertyName = "T263")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_byte, int>), TypeInfoPropertyName = "T264")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_byte?>), TypeInfoPropertyName = "T265")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_byte?>), TypeInfoPropertyName = "T266")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_byte?>), TypeInfoPropertyName = "T267")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_byte?>), TypeInfoPropertyName = "T268")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_byte?, int>), TypeInfoPropertyName = "T269")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_byte>), TypeInfoPropertyName = "T270")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_byte>), TypeInfoPropertyName = "T271")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_byte>), TypeInfoPropertyName = "T272")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_byte>), TypeInfoPropertyName = "T273")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_byte, int>), TypeInfoPropertyName = "T274")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_byte>>), TypeInfoPropertyName = "T275")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_byte>>), TypeInfoPropertyName = "T276")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_byte>>), TypeInfoPropertyName = "T277")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_byte>>), TypeInfoPropertyName = "T278")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_byte>, int>), TypeInfoPropertyName = "T279")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_byte[]>), TypeInfoPropertyName = "T280")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_byte[]>), TypeInfoPropertyName = "T281")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_byte[]>), TypeInfoPropertyName = "T282")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_byte[]>), TypeInfoPropertyName = "T283")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_byte[], int>), TypeInfoPropertyName = "T284")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_byte>>), TypeInfoPropertyName = "T285")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_byte>>), TypeInfoPropertyName = "T286")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_byte>>), TypeInfoPropertyName = "T287")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_byte>>), TypeInfoPropertyName = "T288")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_byte>, int>), TypeInfoPropertyName = "T289")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_byte>>), TypeInfoPropertyName = "T290")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_byte>>), TypeInfoPropertyName = "T291")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_byte>>), TypeInfoPropertyName = "T292")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_byte>>), TypeInfoPropertyName = "T293")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_byte>, int>), TypeInfoPropertyName = "T294")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_byte, int>>), TypeInfoPropertyName = "T295")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_byte, int>>), TypeInfoPropertyName = "T296")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_byte, int>>), TypeInfoPropertyName = "T297")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_byte, int>>), TypeInfoPropertyName = "T298")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_byte, int>, int>), TypeInfoPropertyName = "T299")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_byte>>), TypeInfoPropertyName = "T300")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_byte>>), TypeInfoPropertyName = "T301")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_byte>>), TypeInfoPropertyName = "T302")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_byte>>), TypeInfoPropertyName = "T303")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_byte>, int>), TypeInfoPropertyName = "T304")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_byte>), TypeInfoPropertyName = "T305")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_byte>), TypeInfoPropertyName = "T306")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_byte>), TypeInfoPropertyName = "T307")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_byte>), TypeInfoPropertyName = "T308")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_byte, int>), TypeInfoPropertyName = "T309")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_byte>), TypeInfoPropertyName = "T310")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_byte>), TypeInfoPropertyName = "T311")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_byte>), TypeInfoPropertyName = "T312")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_byte>), TypeInfoPropertyName = "T313")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_byte, int>), TypeInfoPropertyName = "T314")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_byte>>), TypeInfoPropertyName = "T315")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_byte>>), TypeInfoPropertyName = "T316")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_byte>>), TypeInfoPropertyName = "T317")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_byte>>), TypeInfoPropertyName = "T318")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_byte>, int>), TypeInfoPropertyName = "T319")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_byte>), TypeInfoPropertyName = "T320")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_byte>), TypeInfoPropertyName = "T321")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_byte>), TypeInfoPropertyName = "T322")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_byte>), TypeInfoPropertyName = "T323")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_byte, int>), TypeInfoPropertyName = "T324")] [JsonSerializable(typeof(V_record_struct_ulong))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_ulong>))] -[JsonSerializable(typeof(ulong))] -[JsonSerializable(typeof(Cn_class_P<ulong>))] -[JsonSerializable(typeof(ulong?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_ulong>), TypeInfoPropertyName = "T325")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_ulong>), TypeInfoPropertyName = "T326")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_ulong>), TypeInfoPropertyName = "T327")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_ulong>), TypeInfoPropertyName = "T328")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_ulong, int>), TypeInfoPropertyName = "T329")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_ulong?>), TypeInfoPropertyName = "T330")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_ulong?>), TypeInfoPropertyName = "T331")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_ulong?>), TypeInfoPropertyName = "T332")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_ulong?>), TypeInfoPropertyName = "T333")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_ulong?, int>), TypeInfoPropertyName = "T334")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_ulong>), TypeInfoPropertyName = "T335")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_ulong>), TypeInfoPropertyName = "T336")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_ulong>), TypeInfoPropertyName = "T337")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_ulong>), TypeInfoPropertyName = "T338")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_ulong, int>), TypeInfoPropertyName = "T339")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_ulong>>), TypeInfoPropertyName = "T340")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_ulong>>), TypeInfoPropertyName = "T341")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_ulong>>), TypeInfoPropertyName = "T342")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_ulong>>), TypeInfoPropertyName = "T343")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_ulong>, int>), TypeInfoPropertyName = "T344")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_ulong[]>), TypeInfoPropertyName = "T345")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_ulong[]>), TypeInfoPropertyName = "T346")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_ulong[]>), TypeInfoPropertyName = "T347")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_ulong[]>), TypeInfoPropertyName = "T348")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_ulong[], int>), TypeInfoPropertyName = "T349")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_ulong>>), TypeInfoPropertyName = "T350")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_ulong>>), TypeInfoPropertyName = "T351")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_ulong>>), TypeInfoPropertyName = "T352")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_ulong>>), TypeInfoPropertyName = "T353")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_ulong>, int>), TypeInfoPropertyName = "T354")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_ulong>>), TypeInfoPropertyName = "T355")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_ulong>>), TypeInfoPropertyName = "T356")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_ulong>>), TypeInfoPropertyName = "T357")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_ulong>>), TypeInfoPropertyName = "T358")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_ulong>, int>), TypeInfoPropertyName = "T359")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_ulong, int>>), TypeInfoPropertyName = "T360")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_ulong, int>>), TypeInfoPropertyName = "T361")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_ulong, int>>), TypeInfoPropertyName = "T362")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_ulong, int>>), TypeInfoPropertyName = "T363")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_ulong, int>, int>), TypeInfoPropertyName = "T364")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_ulong>>), TypeInfoPropertyName = "T365")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_ulong>>), TypeInfoPropertyName = "T366")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_ulong>>), TypeInfoPropertyName = "T367")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_ulong>>), TypeInfoPropertyName = "T368")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_ulong>, int>), TypeInfoPropertyName = "T369")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_ulong>), TypeInfoPropertyName = "T370")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_ulong>), TypeInfoPropertyName = "T371")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_ulong>), TypeInfoPropertyName = "T372")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_ulong>), TypeInfoPropertyName = "T373")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_ulong, int>), TypeInfoPropertyName = "T374")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_ulong>), TypeInfoPropertyName = "T375")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_ulong>), TypeInfoPropertyName = "T376")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_ulong>), TypeInfoPropertyName = "T377")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_ulong>), TypeInfoPropertyName = "T378")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_ulong, int>), TypeInfoPropertyName = "T379")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_ulong>>), TypeInfoPropertyName = "T380")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_ulong>>), TypeInfoPropertyName = "T381")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_ulong>>), TypeInfoPropertyName = "T382")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_ulong>>), TypeInfoPropertyName = "T383")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_ulong>, int>), TypeInfoPropertyName = "T384")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_ulong>), TypeInfoPropertyName = "T385")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_ulong>), TypeInfoPropertyName = "T386")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_ulong>), TypeInfoPropertyName = "T387")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_ulong>), TypeInfoPropertyName = "T388")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_ulong, int>), TypeInfoPropertyName = "T389")] [JsonSerializable(typeof(V_record_struct_decimal))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_decimal>))] -[JsonSerializable(typeof(decimal))] -[JsonSerializable(typeof(Cn_class_P<decimal>))] -[JsonSerializable(typeof(decimal?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_decimal>), TypeInfoPropertyName = "T390")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_decimal>), TypeInfoPropertyName = "T391")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_decimal>), TypeInfoPropertyName = "T392")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_decimal>), TypeInfoPropertyName = "T393")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_decimal, int>), TypeInfoPropertyName = "T394")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_decimal?>), TypeInfoPropertyName = "T395")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_decimal?>), TypeInfoPropertyName = "T396")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_decimal?>), TypeInfoPropertyName = "T397")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_decimal?>), TypeInfoPropertyName = "T398")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_decimal?, int>), TypeInfoPropertyName = "T399")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_decimal>), TypeInfoPropertyName = "T400")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_decimal>), TypeInfoPropertyName = "T401")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_decimal>), TypeInfoPropertyName = "T402")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_decimal>), TypeInfoPropertyName = "T403")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_decimal, int>), TypeInfoPropertyName = "T404")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_decimal>>), TypeInfoPropertyName = "T405")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_decimal>>), TypeInfoPropertyName = "T406")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_decimal>>), TypeInfoPropertyName = "T407")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_decimal>>), TypeInfoPropertyName = "T408")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_decimal>, int>), TypeInfoPropertyName = "T409")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_decimal[]>), TypeInfoPropertyName = "T410")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_decimal[]>), TypeInfoPropertyName = "T411")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_decimal[]>), TypeInfoPropertyName = "T412")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_decimal[]>), TypeInfoPropertyName = "T413")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_decimal[], int>), TypeInfoPropertyName = "T414")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_decimal>>), TypeInfoPropertyName = "T415")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_decimal>>), TypeInfoPropertyName = "T416")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_decimal>>), TypeInfoPropertyName = "T417")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_decimal>>), TypeInfoPropertyName = "T418")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_decimal>, int>), TypeInfoPropertyName = "T419")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_decimal>>), TypeInfoPropertyName = "T420")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_decimal>>), TypeInfoPropertyName = "T421")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_decimal>>), TypeInfoPropertyName = "T422")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_decimal>>), TypeInfoPropertyName = "T423")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_decimal>, int>), TypeInfoPropertyName = "T424")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_decimal, int>>), TypeInfoPropertyName = "T425")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_decimal, int>>), TypeInfoPropertyName = "T426")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_decimal, int>>), TypeInfoPropertyName = "T427")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_decimal, int>>), TypeInfoPropertyName = "T428")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_decimal, int>, int>), TypeInfoPropertyName = "T429")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_decimal>>), TypeInfoPropertyName = "T430")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_decimal>>), TypeInfoPropertyName = "T431")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_decimal>>), TypeInfoPropertyName = "T432")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_decimal>>), TypeInfoPropertyName = "T433")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_decimal>, int>), TypeInfoPropertyName = "T434")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_decimal>), TypeInfoPropertyName = "T435")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_decimal>), TypeInfoPropertyName = "T436")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_decimal>), TypeInfoPropertyName = "T437")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_decimal>), TypeInfoPropertyName = "T438")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_decimal, int>), TypeInfoPropertyName = "T439")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_decimal>), TypeInfoPropertyName = "T440")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_decimal>), TypeInfoPropertyName = "T441")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_decimal>), TypeInfoPropertyName = "T442")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_decimal>), TypeInfoPropertyName = "T443")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_decimal, int>), TypeInfoPropertyName = "T444")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_decimal>>), TypeInfoPropertyName = "T445")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_decimal>>), TypeInfoPropertyName = "T446")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_decimal>>), TypeInfoPropertyName = "T447")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_decimal>>), TypeInfoPropertyName = "T448")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_decimal>, int>), TypeInfoPropertyName = "T449")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_decimal>), TypeInfoPropertyName = "T450")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_decimal>), TypeInfoPropertyName = "T451")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_decimal>), TypeInfoPropertyName = "T452")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_decimal>), TypeInfoPropertyName = "T453")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_decimal, int>), TypeInfoPropertyName = "T454")] [JsonSerializable(typeof(V_record_struct_double))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_double>))] -[JsonSerializable(typeof(double))] -[JsonSerializable(typeof(Cn_class_P<double>))] -[JsonSerializable(typeof(double?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_double>), TypeInfoPropertyName = "T455")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_double>), TypeInfoPropertyName = "T456")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_double>), TypeInfoPropertyName = "T457")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_double>), TypeInfoPropertyName = "T458")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_double, int>), TypeInfoPropertyName = "T459")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_double?>), TypeInfoPropertyName = "T460")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_double?>), TypeInfoPropertyName = "T461")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_double?>), TypeInfoPropertyName = "T462")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_double?>), TypeInfoPropertyName = "T463")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_double?, int>), TypeInfoPropertyName = "T464")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_double>), TypeInfoPropertyName = "T465")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_double>), TypeInfoPropertyName = "T466")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_double>), TypeInfoPropertyName = "T467")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_double>), TypeInfoPropertyName = "T468")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_double, int>), TypeInfoPropertyName = "T469")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_double>>), TypeInfoPropertyName = "T470")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_double>>), TypeInfoPropertyName = "T471")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_double>>), TypeInfoPropertyName = "T472")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_double>>), TypeInfoPropertyName = "T473")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_double>, int>), TypeInfoPropertyName = "T474")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_double[]>), TypeInfoPropertyName = "T475")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_double[]>), TypeInfoPropertyName = "T476")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_double[]>), TypeInfoPropertyName = "T477")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_double[]>), TypeInfoPropertyName = "T478")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_double[], int>), TypeInfoPropertyName = "T479")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_double>>), TypeInfoPropertyName = "T480")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_double>>), TypeInfoPropertyName = "T481")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_double>>), TypeInfoPropertyName = "T482")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_double>>), TypeInfoPropertyName = "T483")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_double>, int>), TypeInfoPropertyName = "T484")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_double>>), TypeInfoPropertyName = "T485")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_double>>), TypeInfoPropertyName = "T486")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_double>>), TypeInfoPropertyName = "T487")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_double>>), TypeInfoPropertyName = "T488")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_double>, int>), TypeInfoPropertyName = "T489")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_double, int>>), TypeInfoPropertyName = "T490")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_double, int>>), TypeInfoPropertyName = "T491")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_double, int>>), TypeInfoPropertyName = "T492")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_double, int>>), TypeInfoPropertyName = "T493")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_double, int>, int>), TypeInfoPropertyName = "T494")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_double>>), TypeInfoPropertyName = "T495")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_double>>), TypeInfoPropertyName = "T496")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_double>>), TypeInfoPropertyName = "T497")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_double>>), TypeInfoPropertyName = "T498")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_double>, int>), TypeInfoPropertyName = "T499")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_double>), TypeInfoPropertyName = "T500")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_double>), TypeInfoPropertyName = "T501")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_double>), TypeInfoPropertyName = "T502")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_double>), TypeInfoPropertyName = "T503")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_double, int>), TypeInfoPropertyName = "T504")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_double>), TypeInfoPropertyName = "T505")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_double>), TypeInfoPropertyName = "T506")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_double>), TypeInfoPropertyName = "T507")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_double>), TypeInfoPropertyName = "T508")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_double, int>), TypeInfoPropertyName = "T509")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_double>>), TypeInfoPropertyName = "T510")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_double>>), TypeInfoPropertyName = "T511")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_double>>), TypeInfoPropertyName = "T512")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_double>>), TypeInfoPropertyName = "T513")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_double>, int>), TypeInfoPropertyName = "T514")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_double>), TypeInfoPropertyName = "T515")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_double>), TypeInfoPropertyName = "T516")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_double>), TypeInfoPropertyName = "T517")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_double>), TypeInfoPropertyName = "T518")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_double, int>), TypeInfoPropertyName = "T519")] [JsonSerializable(typeof(V_record_struct_bool))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_bool>))] -[JsonSerializable(typeof(bool))] -[JsonSerializable(typeof(Cn_class_P<bool>))] -[JsonSerializable(typeof(bool?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_bool>), TypeInfoPropertyName = "T520")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_bool>), TypeInfoPropertyName = "T521")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_bool>), TypeInfoPropertyName = "T522")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_bool>), TypeInfoPropertyName = "T523")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_bool, int>), TypeInfoPropertyName = "T524")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_bool?>), TypeInfoPropertyName = "T525")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_bool?>), TypeInfoPropertyName = "T526")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_bool?>), TypeInfoPropertyName = "T527")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_bool?>), TypeInfoPropertyName = "T528")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_bool?, int>), TypeInfoPropertyName = "T529")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_bool>), TypeInfoPropertyName = "T530")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_bool>), TypeInfoPropertyName = "T531")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_bool>), TypeInfoPropertyName = "T532")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_bool>), TypeInfoPropertyName = "T533")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_bool, int>), TypeInfoPropertyName = "T534")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_bool>>), TypeInfoPropertyName = "T535")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_bool>>), TypeInfoPropertyName = "T536")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_bool>>), TypeInfoPropertyName = "T537")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_bool>>), TypeInfoPropertyName = "T538")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_bool>, int>), TypeInfoPropertyName = "T539")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_bool[]>), TypeInfoPropertyName = "T540")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_bool[]>), TypeInfoPropertyName = "T541")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_bool[]>), TypeInfoPropertyName = "T542")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_bool[]>), TypeInfoPropertyName = "T543")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_bool[], int>), TypeInfoPropertyName = "T544")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_bool>>), TypeInfoPropertyName = "T545")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_bool>>), TypeInfoPropertyName = "T546")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_bool>>), TypeInfoPropertyName = "T547")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_bool>>), TypeInfoPropertyName = "T548")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_bool>, int>), TypeInfoPropertyName = "T549")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_bool>>), TypeInfoPropertyName = "T550")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_bool>>), TypeInfoPropertyName = "T551")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_bool>>), TypeInfoPropertyName = "T552")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_bool>>), TypeInfoPropertyName = "T553")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_bool>, int>), TypeInfoPropertyName = "T554")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_bool, int>>), TypeInfoPropertyName = "T555")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_bool, int>>), TypeInfoPropertyName = "T556")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_bool, int>>), TypeInfoPropertyName = "T557")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_bool, int>>), TypeInfoPropertyName = "T558")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_bool, int>, int>), TypeInfoPropertyName = "T559")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_bool>>), TypeInfoPropertyName = "T560")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_bool>>), TypeInfoPropertyName = "T561")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_bool>>), TypeInfoPropertyName = "T562")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_bool>>), TypeInfoPropertyName = "T563")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_bool>, int>), TypeInfoPropertyName = "T564")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_bool>), TypeInfoPropertyName = "T565")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_bool>), TypeInfoPropertyName = "T566")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_bool>), TypeInfoPropertyName = "T567")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_bool>), TypeInfoPropertyName = "T568")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_bool, int>), TypeInfoPropertyName = "T569")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_bool>), TypeInfoPropertyName = "T570")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_bool>), TypeInfoPropertyName = "T571")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_bool>), TypeInfoPropertyName = "T572")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_bool>), TypeInfoPropertyName = "T573")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_bool, int>), TypeInfoPropertyName = "T574")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_bool>>), TypeInfoPropertyName = "T575")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_bool>>), TypeInfoPropertyName = "T576")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_bool>>), TypeInfoPropertyName = "T577")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_bool>>), TypeInfoPropertyName = "T578")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_bool>, int>), TypeInfoPropertyName = "T579")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_bool>), TypeInfoPropertyName = "T580")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_bool>), TypeInfoPropertyName = "T581")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_bool>), TypeInfoPropertyName = "T582")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_bool>), TypeInfoPropertyName = "T583")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_bool, int>), TypeInfoPropertyName = "T584")] [JsonSerializable(typeof(V_record_struct_char))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_char>))] -[JsonSerializable(typeof(char))] -[JsonSerializable(typeof(Cn_class_P<char>))] -[JsonSerializable(typeof(char?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_char>), TypeInfoPropertyName = "T585")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_char>), TypeInfoPropertyName = "T586")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_char>), TypeInfoPropertyName = "T587")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_char>), TypeInfoPropertyName = "T588")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_char, int>), TypeInfoPropertyName = "T589")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_char?>), TypeInfoPropertyName = "T590")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_char?>), TypeInfoPropertyName = "T591")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_char?>), TypeInfoPropertyName = "T592")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_char?>), TypeInfoPropertyName = "T593")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_char?, int>), TypeInfoPropertyName = "T594")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_char>), TypeInfoPropertyName = "T595")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_char>), TypeInfoPropertyName = "T596")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_char>), TypeInfoPropertyName = "T597")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_char>), TypeInfoPropertyName = "T598")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_char, int>), TypeInfoPropertyName = "T599")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_char>>), TypeInfoPropertyName = "T600")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_char>>), TypeInfoPropertyName = "T601")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_char>>), TypeInfoPropertyName = "T602")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_char>>), TypeInfoPropertyName = "T603")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_char>, int>), TypeInfoPropertyName = "T604")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_char[]>), TypeInfoPropertyName = "T605")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_char[]>), TypeInfoPropertyName = "T606")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_char[]>), TypeInfoPropertyName = "T607")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_char[]>), TypeInfoPropertyName = "T608")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_char[], int>), TypeInfoPropertyName = "T609")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_char>>), TypeInfoPropertyName = "T610")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_char>>), TypeInfoPropertyName = "T611")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_char>>), TypeInfoPropertyName = "T612")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_char>>), TypeInfoPropertyName = "T613")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_char>, int>), TypeInfoPropertyName = "T614")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_char>>), TypeInfoPropertyName = "T615")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_char>>), TypeInfoPropertyName = "T616")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_char>>), TypeInfoPropertyName = "T617")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_char>>), TypeInfoPropertyName = "T618")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_char>, int>), TypeInfoPropertyName = "T619")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_char, int>>), TypeInfoPropertyName = "T620")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_char, int>>), TypeInfoPropertyName = "T621")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_char, int>>), TypeInfoPropertyName = "T622")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_char, int>>), TypeInfoPropertyName = "T623")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_char, int>, int>), TypeInfoPropertyName = "T624")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_char>>), TypeInfoPropertyName = "T625")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_char>>), TypeInfoPropertyName = "T626")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_char>>), TypeInfoPropertyName = "T627")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_char>>), TypeInfoPropertyName = "T628")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_char>, int>), TypeInfoPropertyName = "T629")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_char>), TypeInfoPropertyName = "T630")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_char>), TypeInfoPropertyName = "T631")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_char>), TypeInfoPropertyName = "T632")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_char>), TypeInfoPropertyName = "T633")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_char, int>), TypeInfoPropertyName = "T634")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_char>), TypeInfoPropertyName = "T635")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_char>), TypeInfoPropertyName = "T636")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_char>), TypeInfoPropertyName = "T637")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_char>), TypeInfoPropertyName = "T638")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_char, int>), TypeInfoPropertyName = "T639")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_char>>), TypeInfoPropertyName = "T640")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_char>>), TypeInfoPropertyName = "T641")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_char>>), TypeInfoPropertyName = "T642")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_char>>), TypeInfoPropertyName = "T643")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_char>, int>), TypeInfoPropertyName = "T644")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_char>), TypeInfoPropertyName = "T645")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_char>), TypeInfoPropertyName = "T646")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_char>), TypeInfoPropertyName = "T647")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_char>), TypeInfoPropertyName = "T648")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_char, int>), TypeInfoPropertyName = "T649")] [JsonSerializable(typeof(V_record_struct_DateTime))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTime>))] -[JsonSerializable(typeof(DateTime))] -[JsonSerializable(typeof(Cn_class_P<DateTime>))] -[JsonSerializable(typeof(DateTime?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTime>), TypeInfoPropertyName = "T650")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTime>), TypeInfoPropertyName = "T651")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTime>), TypeInfoPropertyName = "T652")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTime>), TypeInfoPropertyName = "T653")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTime, int>), TypeInfoPropertyName = "T654")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTime?>), TypeInfoPropertyName = "T655")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTime?>), TypeInfoPropertyName = "T656")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTime?>), TypeInfoPropertyName = "T657")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTime?>), TypeInfoPropertyName = "T658")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTime?, int>), TypeInfoPropertyName = "T659")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateTime>), TypeInfoPropertyName = "T660")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateTime>), TypeInfoPropertyName = "T661")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateTime>), TypeInfoPropertyName = "T662")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateTime>), TypeInfoPropertyName = "T663")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateTime, int>), TypeInfoPropertyName = "T664")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateTime>>), TypeInfoPropertyName = "T665")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateTime>>), TypeInfoPropertyName = "T666")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateTime>>), TypeInfoPropertyName = "T667")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateTime>>), TypeInfoPropertyName = "T668")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateTime>, int>), TypeInfoPropertyName = "T669")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTime[]>), TypeInfoPropertyName = "T670")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTime[]>), TypeInfoPropertyName = "T671")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTime[]>), TypeInfoPropertyName = "T672")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTime[]>), TypeInfoPropertyName = "T673")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTime[], int>), TypeInfoPropertyName = "T674")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateTime>>), TypeInfoPropertyName = "T675")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateTime>>), TypeInfoPropertyName = "T676")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateTime>>), TypeInfoPropertyName = "T677")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateTime>>), TypeInfoPropertyName = "T678")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateTime>, int>), TypeInfoPropertyName = "T679")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateTime>>), TypeInfoPropertyName = "T680")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateTime>>), TypeInfoPropertyName = "T681")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateTime>>), TypeInfoPropertyName = "T682")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateTime>>), TypeInfoPropertyName = "T683")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateTime>, int>), TypeInfoPropertyName = "T684")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_DateTime, int>>), TypeInfoPropertyName = "T685")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_DateTime, int>>), TypeInfoPropertyName = "T686")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_DateTime, int>>), TypeInfoPropertyName = "T687")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_DateTime, int>>), TypeInfoPropertyName = "T688")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_DateTime, int>, int>), TypeInfoPropertyName = "T689")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateTime>>), TypeInfoPropertyName = "T690")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateTime>>), TypeInfoPropertyName = "T691")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateTime>>), TypeInfoPropertyName = "T692")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateTime>>), TypeInfoPropertyName = "T693")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateTime>, int>), TypeInfoPropertyName = "T694")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateTime>), TypeInfoPropertyName = "T695")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateTime>), TypeInfoPropertyName = "T696")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateTime>), TypeInfoPropertyName = "T697")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateTime>), TypeInfoPropertyName = "T698")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateTime, int>), TypeInfoPropertyName = "T699")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateTime>), TypeInfoPropertyName = "T700")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateTime>), TypeInfoPropertyName = "T701")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateTime>), TypeInfoPropertyName = "T702")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateTime>), TypeInfoPropertyName = "T703")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateTime, int>), TypeInfoPropertyName = "T704")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_DateTime>>), TypeInfoPropertyName = "T705")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_DateTime>>), TypeInfoPropertyName = "T706")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_DateTime>>), TypeInfoPropertyName = "T707")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_DateTime>>), TypeInfoPropertyName = "T708")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_DateTime>, int>), TypeInfoPropertyName = "T709")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_DateTime>), TypeInfoPropertyName = "T710")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_DateTime>), TypeInfoPropertyName = "T711")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_DateTime>), TypeInfoPropertyName = "T712")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_DateTime>), TypeInfoPropertyName = "T713")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_DateTime, int>), TypeInfoPropertyName = "T714")] [JsonSerializable(typeof(V_record_struct_DateTimeOffset))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTimeOffset>))] -[JsonSerializable(typeof(DateTimeOffset))] -[JsonSerializable(typeof(Cn_class_P<DateTimeOffset>))] -[JsonSerializable(typeof(DateTimeOffset?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T715")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T716")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T717")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T718")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T719")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T720")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T721")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T722")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T723")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T724")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T725")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T726")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T727")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T728")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T729")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T730")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T731")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T732")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T733")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T734")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T735")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T736")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T737")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T738")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTimeOffset[], int>), TypeInfoPropertyName = "T739")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T740")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T741")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T742")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T743")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T744")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T745")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T746")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T747")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T748")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T749")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T750")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T751")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T752")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T753")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_DateTimeOffset, int>, int>), TypeInfoPropertyName = "T754")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T755")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T756")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T757")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T758")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T759")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T760")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T761")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T762")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T763")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T764")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T765")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T766")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T767")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T768")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T769")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T770")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T771")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T772")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_DateTimeOffset>>), TypeInfoPropertyName = "T773")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T774")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T775")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T776")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T777")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_DateTimeOffset>), TypeInfoPropertyName = "T778")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T779")] [JsonSerializable(typeof(V_record_struct_DateOnly))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateOnly>))] -[JsonSerializable(typeof(DateOnly))] -[JsonSerializable(typeof(Cn_class_P<DateOnly>))] -[JsonSerializable(typeof(DateOnly?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateOnly>), TypeInfoPropertyName = "T780")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateOnly>), TypeInfoPropertyName = "T781")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateOnly>), TypeInfoPropertyName = "T782")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateOnly>), TypeInfoPropertyName = "T783")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateOnly, int>), TypeInfoPropertyName = "T784")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T785")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T786")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T787")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T788")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateOnly?, int>), TypeInfoPropertyName = "T789")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateOnly>), TypeInfoPropertyName = "T790")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateOnly>), TypeInfoPropertyName = "T791")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateOnly>), TypeInfoPropertyName = "T792")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateOnly>), TypeInfoPropertyName = "T793")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateOnly, int>), TypeInfoPropertyName = "T794")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T795")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T796")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T797")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T798")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateOnly>, int>), TypeInfoPropertyName = "T799")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateOnly[]>), TypeInfoPropertyName = "T800")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateOnly[]>), TypeInfoPropertyName = "T801")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateOnly[]>), TypeInfoPropertyName = "T802")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateOnly[]>), TypeInfoPropertyName = "T803")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateOnly[], int>), TypeInfoPropertyName = "T804")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T805")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T806")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T807")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T808")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateOnly>, int>), TypeInfoPropertyName = "T809")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T810")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T811")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T812")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T813")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateOnly>, int>), TypeInfoPropertyName = "T814")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_DateOnly, int>>), TypeInfoPropertyName = "T815")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_DateOnly, int>>), TypeInfoPropertyName = "T816")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_DateOnly, int>>), TypeInfoPropertyName = "T817")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_DateOnly, int>>), TypeInfoPropertyName = "T818")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_DateOnly, int>, int>), TypeInfoPropertyName = "T819")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateOnly>>), TypeInfoPropertyName = "T820")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateOnly>>), TypeInfoPropertyName = "T821")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateOnly>>), TypeInfoPropertyName = "T822")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateOnly>>), TypeInfoPropertyName = "T823")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateOnly>, int>), TypeInfoPropertyName = "T824")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateOnly>), TypeInfoPropertyName = "T825")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateOnly>), TypeInfoPropertyName = "T826")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateOnly>), TypeInfoPropertyName = "T827")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateOnly>), TypeInfoPropertyName = "T828")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateOnly, int>), TypeInfoPropertyName = "T829")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateOnly>), TypeInfoPropertyName = "T830")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateOnly>), TypeInfoPropertyName = "T831")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateOnly>), TypeInfoPropertyName = "T832")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateOnly>), TypeInfoPropertyName = "T833")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateOnly, int>), TypeInfoPropertyName = "T834")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T835")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T836")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T837")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_DateOnly>>), TypeInfoPropertyName = "T838")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_DateOnly>, int>), TypeInfoPropertyName = "T839")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_DateOnly>), TypeInfoPropertyName = "T840")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_DateOnly>), TypeInfoPropertyName = "T841")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_DateOnly>), TypeInfoPropertyName = "T842")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_DateOnly>), TypeInfoPropertyName = "T843")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_DateOnly, int>), TypeInfoPropertyName = "T844")] [JsonSerializable(typeof(V_record_struct_TimeOnly))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeOnly>))] -[JsonSerializable(typeof(TimeOnly))] -[JsonSerializable(typeof(Cn_class_P<TimeOnly>))] -[JsonSerializable(typeof(TimeOnly?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T845")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T846")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T847")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T848")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeOnly, int>), TypeInfoPropertyName = "T849")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T850")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T851")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T852")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T853")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeOnly?, int>), TypeInfoPropertyName = "T854")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T855")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T856")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T857")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T858")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_TimeOnly, int>), TypeInfoPropertyName = "T859")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T860")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T861")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T862")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T863")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_TimeOnly>, int>), TypeInfoPropertyName = "T864")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeOnly[]>), TypeInfoPropertyName = "T865")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeOnly[]>), TypeInfoPropertyName = "T866")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeOnly[]>), TypeInfoPropertyName = "T867")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeOnly[]>), TypeInfoPropertyName = "T868")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeOnly[], int>), TypeInfoPropertyName = "T869")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T870")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T871")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T872")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T873")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_TimeOnly>, int>), TypeInfoPropertyName = "T874")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T875")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T876")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T877")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T878")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_TimeOnly>, int>), TypeInfoPropertyName = "T879")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_TimeOnly, int>>), TypeInfoPropertyName = "T880")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_TimeOnly, int>>), TypeInfoPropertyName = "T881")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_TimeOnly, int>>), TypeInfoPropertyName = "T882")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_TimeOnly, int>>), TypeInfoPropertyName = "T883")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_TimeOnly, int>, int>), TypeInfoPropertyName = "T884")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T885")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T886")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T887")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T888")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_TimeOnly>, int>), TypeInfoPropertyName = "T889")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T890")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T891")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T892")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T893")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_TimeOnly, int>), TypeInfoPropertyName = "T894")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T895")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T896")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T897")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_TimeOnly>), TypeInfoPropertyName = "T898")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_TimeOnly, int>), TypeInfoPropertyName = "T899")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T900")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T901")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T902")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_TimeOnly>>), TypeInfoPropertyName = "T903")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_TimeOnly>, int>), TypeInfoPropertyName = "T904")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_TimeOnly>), TypeInfoPropertyName = "T905")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_TimeOnly>), TypeInfoPropertyName = "T906")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_TimeOnly>), TypeInfoPropertyName = "T907")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_TimeOnly>), TypeInfoPropertyName = "T908")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_TimeOnly, int>), TypeInfoPropertyName = "T909")] [JsonSerializable(typeof(V_record_struct_TimeSpan))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeSpan>))] -[JsonSerializable(typeof(TimeSpan))] -[JsonSerializable(typeof(Cn_class_P<TimeSpan>))] -[JsonSerializable(typeof(TimeSpan?))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T910")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T911")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T912")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T913")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeSpan, int>), TypeInfoPropertyName = "T914")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T915")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T916")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T917")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T918")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeSpan?, int>), TypeInfoPropertyName = "T919")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T920")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T921")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T922")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T923")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_TimeSpan, int>), TypeInfoPropertyName = "T924")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T925")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T926")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T927")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T928")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_TimeSpan>, int>), TypeInfoPropertyName = "T929")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeSpan[]>), TypeInfoPropertyName = "T930")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeSpan[]>), TypeInfoPropertyName = "T931")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeSpan[]>), TypeInfoPropertyName = "T932")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeSpan[]>), TypeInfoPropertyName = "T933")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeSpan[], int>), TypeInfoPropertyName = "T934")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T935")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T936")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T937")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T938")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_TimeSpan>, int>), TypeInfoPropertyName = "T939")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T940")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T941")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T942")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T943")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_TimeSpan>, int>), TypeInfoPropertyName = "T944")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_TimeSpan, int>>), TypeInfoPropertyName = "T945")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_TimeSpan, int>>), TypeInfoPropertyName = "T946")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_TimeSpan, int>>), TypeInfoPropertyName = "T947")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_TimeSpan, int>>), TypeInfoPropertyName = "T948")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_TimeSpan, int>, int>), TypeInfoPropertyName = "T949")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T950")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T951")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T952")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T953")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_TimeSpan>, int>), TypeInfoPropertyName = "T954")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T955")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T956")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T957")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T958")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_TimeSpan, int>), TypeInfoPropertyName = "T959")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T960")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T961")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T962")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_TimeSpan>), TypeInfoPropertyName = "T963")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_TimeSpan, int>), TypeInfoPropertyName = "T964")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T965")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T966")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T967")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_TimeSpan>>), TypeInfoPropertyName = "T968")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_TimeSpan>, int>), TypeInfoPropertyName = "T969")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_TimeSpan>), TypeInfoPropertyName = "T970")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_TimeSpan>), TypeInfoPropertyName = "T971")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_TimeSpan>), TypeInfoPropertyName = "T972")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_TimeSpan>), TypeInfoPropertyName = "T973")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_TimeSpan, int>), TypeInfoPropertyName = "T974")] [JsonSerializable(typeof(V_record_struct_enum))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_enum>))] -[JsonSerializable(typeof(PEnum))] -[JsonSerializable(typeof(Cn_class_P<PEnum>))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_enum>), TypeInfoPropertyName = "T975")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_enum>), TypeInfoPropertyName = "T976")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_enum>), TypeInfoPropertyName = "T977")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_enum>), TypeInfoPropertyName = "T978")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_enum, int>), TypeInfoPropertyName = "T979")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_enum?>), TypeInfoPropertyName = "T980")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_enum?>), TypeInfoPropertyName = "T981")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_enum?>), TypeInfoPropertyName = "T982")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_enum?>), TypeInfoPropertyName = "T983")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_enum?, int>), TypeInfoPropertyName = "T984")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_enum>), TypeInfoPropertyName = "T985")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_enum>), TypeInfoPropertyName = "T986")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_enum>), TypeInfoPropertyName = "T987")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_enum>), TypeInfoPropertyName = "T988")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_enum, int>), TypeInfoPropertyName = "T989")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_enum>>), TypeInfoPropertyName = "T990")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_enum>>), TypeInfoPropertyName = "T991")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_enum>>), TypeInfoPropertyName = "T992")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_enum>>), TypeInfoPropertyName = "T993")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_enum>, int>), TypeInfoPropertyName = "T994")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_enum[]>), TypeInfoPropertyName = "T995")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_enum[]>), TypeInfoPropertyName = "T996")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_enum[]>), TypeInfoPropertyName = "T997")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_enum[]>), TypeInfoPropertyName = "T998")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_enum[], int>), TypeInfoPropertyName = "T999")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_enum>>), TypeInfoPropertyName = "T1000")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_enum>>), TypeInfoPropertyName = "T1001")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_enum>>), TypeInfoPropertyName = "T1002")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_enum>>), TypeInfoPropertyName = "T1003")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_enum>, int>), TypeInfoPropertyName = "T1004")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_enum>>), TypeInfoPropertyName = "T1005")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_enum>>), TypeInfoPropertyName = "T1006")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_enum>>), TypeInfoPropertyName = "T1007")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_enum>>), TypeInfoPropertyName = "T1008")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_enum>, int>), TypeInfoPropertyName = "T1009")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_enum, int>>), TypeInfoPropertyName = "T1010")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_enum, int>>), TypeInfoPropertyName = "T1011")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_enum, int>>), TypeInfoPropertyName = "T1012")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_enum, int>>), TypeInfoPropertyName = "T1013")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_enum, int>, int>), TypeInfoPropertyName = "T1014")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_enum>>), TypeInfoPropertyName = "T1015")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_enum>>), TypeInfoPropertyName = "T1016")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_enum>>), TypeInfoPropertyName = "T1017")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_enum>>), TypeInfoPropertyName = "T1018")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_enum>, int>), TypeInfoPropertyName = "T1019")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_enum>), TypeInfoPropertyName = "T1020")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_enum>), TypeInfoPropertyName = "T1021")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_enum>), TypeInfoPropertyName = "T1022")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_enum>), TypeInfoPropertyName = "T1023")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_enum, int>), TypeInfoPropertyName = "T1024")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_enum>), TypeInfoPropertyName = "T1025")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_enum>), TypeInfoPropertyName = "T1026")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_enum>), TypeInfoPropertyName = "T1027")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_enum>), TypeInfoPropertyName = "T1028")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_enum, int>), TypeInfoPropertyName = "T1029")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_enum>>), TypeInfoPropertyName = "T1030")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_enum>>), TypeInfoPropertyName = "T1031")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_enum>>), TypeInfoPropertyName = "T1032")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_enum>>), TypeInfoPropertyName = "T1033")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_enum>, int>), TypeInfoPropertyName = "T1034")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_enum>), TypeInfoPropertyName = "T1035")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_enum>), TypeInfoPropertyName = "T1036")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_enum>), TypeInfoPropertyName = "T1037")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_enum>), TypeInfoPropertyName = "T1038")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_enum, int>), TypeInfoPropertyName = "T1039")] [JsonSerializable(typeof(V_record_struct_flags_enum))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_flags_enum>))] -[JsonSerializable(typeof(PFlags))] -[JsonSerializable(typeof(Cn_class_P<PFlags>))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1040")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1041")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1042")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1043")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_flags_enum, int>), TypeInfoPropertyName = "T1044")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T1045")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T1046")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T1047")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T1048")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_flags_enum?, int>), TypeInfoPropertyName = "T1049")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1050")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1051")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1052")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1053")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_flags_enum, int>), TypeInfoPropertyName = "T1054")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1055")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1056")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1057")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1058")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_flags_enum>, int>), TypeInfoPropertyName = "T1059")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_flags_enum[]>), TypeInfoPropertyName = "T1060")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_flags_enum[]>), TypeInfoPropertyName = "T1061")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_flags_enum[]>), TypeInfoPropertyName = "T1062")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_flags_enum[]>), TypeInfoPropertyName = "T1063")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_flags_enum[], int>), TypeInfoPropertyName = "T1064")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1065")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1066")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1067")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1068")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_flags_enum>, int>), TypeInfoPropertyName = "T1069")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1070")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1071")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1072")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1073")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_flags_enum>, int>), TypeInfoPropertyName = "T1074")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_flags_enum, int>>), TypeInfoPropertyName = "T1075")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_flags_enum, int>>), TypeInfoPropertyName = "T1076")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_flags_enum, int>>), TypeInfoPropertyName = "T1077")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_flags_enum, int>>), TypeInfoPropertyName = "T1078")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_flags_enum, int>, int>), TypeInfoPropertyName = "T1079")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1080")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1081")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1082")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1083")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_flags_enum>, int>), TypeInfoPropertyName = "T1084")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1085")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1086")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1087")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1088")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_flags_enum, int>), TypeInfoPropertyName = "T1089")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1090")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1091")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1092")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_flags_enum>), TypeInfoPropertyName = "T1093")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_flags_enum, int>), TypeInfoPropertyName = "T1094")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1095")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1096")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1097")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_flags_enum>>), TypeInfoPropertyName = "T1098")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_flags_enum>, int>), TypeInfoPropertyName = "T1099")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_flags_enum>), TypeInfoPropertyName = "T1100")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_flags_enum>), TypeInfoPropertyName = "T1101")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_flags_enum>), TypeInfoPropertyName = "T1102")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_flags_enum>), TypeInfoPropertyName = "T1103")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_flags_enum, int>), TypeInfoPropertyName = "T1104")] [JsonSerializable(typeof(V_record_struct_Uri))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_Uri>))] -[JsonSerializable(typeof(Uri))] -[JsonSerializable(typeof(Cn_class_P<Uri>))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Uri>), TypeInfoPropertyName = "T1105")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Uri>), TypeInfoPropertyName = "T1106")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Uri>), TypeInfoPropertyName = "T1107")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Uri>), TypeInfoPropertyName = "T1108")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Uri, int>), TypeInfoPropertyName = "T1109")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Uri?>), TypeInfoPropertyName = "T1110")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Uri?>), TypeInfoPropertyName = "T1111")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Uri?>), TypeInfoPropertyName = "T1112")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Uri?>), TypeInfoPropertyName = "T1113")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Uri?, int>), TypeInfoPropertyName = "T1114")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_Uri>), TypeInfoPropertyName = "T1115")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_Uri>), TypeInfoPropertyName = "T1116")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_Uri>), TypeInfoPropertyName = "T1117")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_Uri>), TypeInfoPropertyName = "T1118")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_Uri, int>), TypeInfoPropertyName = "T1119")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_Uri>>), TypeInfoPropertyName = "T1120")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_Uri>>), TypeInfoPropertyName = "T1121")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_Uri>>), TypeInfoPropertyName = "T1122")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_Uri>>), TypeInfoPropertyName = "T1123")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_Uri>, int>), TypeInfoPropertyName = "T1124")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Uri[]>), TypeInfoPropertyName = "T1125")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Uri[]>), TypeInfoPropertyName = "T1126")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Uri[]>), TypeInfoPropertyName = "T1127")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Uri[]>), TypeInfoPropertyName = "T1128")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Uri[], int>), TypeInfoPropertyName = "T1129")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_Uri>>), TypeInfoPropertyName = "T1130")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_Uri>>), TypeInfoPropertyName = "T1131")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_Uri>>), TypeInfoPropertyName = "T1132")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_Uri>>), TypeInfoPropertyName = "T1133")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_Uri>, int>), TypeInfoPropertyName = "T1134")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_Uri>>), TypeInfoPropertyName = "T1135")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_Uri>>), TypeInfoPropertyName = "T1136")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_Uri>>), TypeInfoPropertyName = "T1137")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_Uri>>), TypeInfoPropertyName = "T1138")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_Uri>, int>), TypeInfoPropertyName = "T1139")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_Uri, int>>), TypeInfoPropertyName = "T1140")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_Uri, int>>), TypeInfoPropertyName = "T1141")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_Uri, int>>), TypeInfoPropertyName = "T1142")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_Uri, int>>), TypeInfoPropertyName = "T1143")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_Uri, int>, int>), TypeInfoPropertyName = "T1144")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_Uri>>), TypeInfoPropertyName = "T1145")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_Uri>>), TypeInfoPropertyName = "T1146")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_Uri>>), TypeInfoPropertyName = "T1147")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_Uri>>), TypeInfoPropertyName = "T1148")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_Uri>, int>), TypeInfoPropertyName = "T1149")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_Uri>), TypeInfoPropertyName = "T1150")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_Uri>), TypeInfoPropertyName = "T1151")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_Uri>), TypeInfoPropertyName = "T1152")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_Uri>), TypeInfoPropertyName = "T1153")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_Uri, int>), TypeInfoPropertyName = "T1154")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_Uri>), TypeInfoPropertyName = "T1155")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_Uri>), TypeInfoPropertyName = "T1156")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_Uri>), TypeInfoPropertyName = "T1157")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_Uri>), TypeInfoPropertyName = "T1158")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_Uri, int>), TypeInfoPropertyName = "T1159")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_Uri>>), TypeInfoPropertyName = "T1160")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_Uri>>), TypeInfoPropertyName = "T1161")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_Uri>>), TypeInfoPropertyName = "T1162")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_Uri>>), TypeInfoPropertyName = "T1163")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_Uri>, int>), TypeInfoPropertyName = "T1164")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_Uri>), TypeInfoPropertyName = "T1165")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_Uri>), TypeInfoPropertyName = "T1166")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_Uri>), TypeInfoPropertyName = "T1167")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_Uri>), TypeInfoPropertyName = "T1168")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_Uri, int>), TypeInfoPropertyName = "T1169")] +[JsonSerializable(typeof(V_record_struct_NodaTime_Instant))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1170")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1171")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1172")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1173")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T1174")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T1175")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T1176")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T1177")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T1178")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T1179")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1180")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1181")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1182")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1183")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T1184")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1185")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1186")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1187")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1188")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T1189")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T1190")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T1191")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T1192")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T1193")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_NodaTime_Instant[], int>), TypeInfoPropertyName = "T1194")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1195")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1196")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1197")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1198")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T1199")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1200")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1201")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1202")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1203")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T1204")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T1205")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T1206")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T1207")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T1208")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_NodaTime_Instant, int>, int>), TypeInfoPropertyName = "T1209")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1210")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1211")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1212")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1213")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T1214")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1215")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1216")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1217")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1218")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T1219")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1220")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1221")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1222")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1223")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T1224")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1225")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1226")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1227")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T1228")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T1229")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1230")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1231")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1232")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_NodaTime_Instant>), TypeInfoPropertyName = "T1233")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T1234")] [JsonSerializable(typeof(V_record_struct_own_record_class))] -[JsonSerializable(typeof(Cn_class_P<V_record_struct_own_record_class>))] -[JsonSerializable(typeof(PRec))] -[JsonSerializable(typeof(Cn_class_P<PRec>))] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1235")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1236")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1237")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1238")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_own_record_class, int>), TypeInfoPropertyName = "T1239")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T1240")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T1241")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T1242")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T1243")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_own_record_class?, int>), TypeInfoPropertyName = "T1244")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1245")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1246")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1247")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1248")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_own_record_class, int>), TypeInfoPropertyName = "T1249")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1250")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1251")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1252")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1253")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_own_record_class>, int>), TypeInfoPropertyName = "T1254")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_own_record_class[]>), TypeInfoPropertyName = "T1255")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_own_record_class[]>), TypeInfoPropertyName = "T1256")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_own_record_class[]>), TypeInfoPropertyName = "T1257")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_own_record_class[]>), TypeInfoPropertyName = "T1258")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_own_record_class[], int>), TypeInfoPropertyName = "T1259")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1260")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1261")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1262")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1263")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_own_record_class>, int>), TypeInfoPropertyName = "T1264")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1265")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1266")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1267")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1268")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_own_record_class>, int>), TypeInfoPropertyName = "T1269")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_record_struct_own_record_class, int>>), TypeInfoPropertyName = "T1270")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_record_struct_own_record_class, int>>), TypeInfoPropertyName = "T1271")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_record_struct_own_record_class, int>>), TypeInfoPropertyName = "T1272")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_record_struct_own_record_class, int>>), TypeInfoPropertyName = "T1273")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_record_struct_own_record_class, int>, int>), TypeInfoPropertyName = "T1274")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1275")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1276")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1277")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1278")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_own_record_class>, int>), TypeInfoPropertyName = "T1279")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1280")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1281")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1282")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1283")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_own_record_class, int>), TypeInfoPropertyName = "T1284")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1285")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1286")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1287")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_own_record_class>), TypeInfoPropertyName = "T1288")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_own_record_class, int>), TypeInfoPropertyName = "T1289")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1290")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1291")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1292")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_record_struct_own_record_class>>), TypeInfoPropertyName = "T1293")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_record_struct_own_record_class>, int>), TypeInfoPropertyName = "T1294")] +[JsonSerializable(typeof(Cn_class_P<PB_V_record_struct_own_record_class>), TypeInfoPropertyName = "T1295")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_record_struct_own_record_class>), TypeInfoPropertyName = "T1296")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_record_struct_own_record_class>), TypeInfoPropertyName = "T1297")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_record_struct_own_record_class>), TypeInfoPropertyName = "T1298")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_record_struct_own_record_class, int>), TypeInfoPropertyName = "T1299")] [JsonSerializable(typeof(V_sealed_record_class_string))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_string>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_string>), TypeInfoPropertyName = "T1300")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_string>), TypeInfoPropertyName = "T1301")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_string>), TypeInfoPropertyName = "T1302")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_string>), TypeInfoPropertyName = "T1303")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_string, int>), TypeInfoPropertyName = "T1304")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_string>), TypeInfoPropertyName = "T1305")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_string>), TypeInfoPropertyName = "T1306")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_string>), TypeInfoPropertyName = "T1307")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_string>), TypeInfoPropertyName = "T1308")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_string, int>), TypeInfoPropertyName = "T1309")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1310")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1311")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1312")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1313")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_string>, int>), TypeInfoPropertyName = "T1314")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_string[]>), TypeInfoPropertyName = "T1315")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_string[]>), TypeInfoPropertyName = "T1316")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_string[]>), TypeInfoPropertyName = "T1317")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_string[]>), TypeInfoPropertyName = "T1318")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_string[], int>), TypeInfoPropertyName = "T1319")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1320")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1321")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1322")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1323")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_string>, int>), TypeInfoPropertyName = "T1324")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1325")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1326")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1327")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1328")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_string>, int>), TypeInfoPropertyName = "T1329")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_string, int>>), TypeInfoPropertyName = "T1330")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_string, int>>), TypeInfoPropertyName = "T1331")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_string, int>>), TypeInfoPropertyName = "T1332")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_string, int>>), TypeInfoPropertyName = "T1333")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_string, int>, int>), TypeInfoPropertyName = "T1334")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_string>>), TypeInfoPropertyName = "T1335")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_string>>), TypeInfoPropertyName = "T1336")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_string>>), TypeInfoPropertyName = "T1337")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_string>>), TypeInfoPropertyName = "T1338")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_string>, int>), TypeInfoPropertyName = "T1339")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_string>), TypeInfoPropertyName = "T1340")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_string>), TypeInfoPropertyName = "T1341")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_string>), TypeInfoPropertyName = "T1342")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_string>), TypeInfoPropertyName = "T1343")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_string, int>), TypeInfoPropertyName = "T1344")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_string>), TypeInfoPropertyName = "T1345")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_string>), TypeInfoPropertyName = "T1346")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_string>), TypeInfoPropertyName = "T1347")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_string>), TypeInfoPropertyName = "T1348")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_string, int>), TypeInfoPropertyName = "T1349")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1350")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1351")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1352")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_string>>), TypeInfoPropertyName = "T1353")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_string>, int>), TypeInfoPropertyName = "T1354")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_string>), TypeInfoPropertyName = "T1355")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_string>), TypeInfoPropertyName = "T1356")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_string>), TypeInfoPropertyName = "T1357")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_string>), TypeInfoPropertyName = "T1358")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_string, int>), TypeInfoPropertyName = "T1359")] [JsonSerializable(typeof(V_sealed_record_class_Guid))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Guid>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1360")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1361")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1362")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1363")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_Guid, int>), TypeInfoPropertyName = "T1364")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1365")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1366")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1367")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1368")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_Guid, int>), TypeInfoPropertyName = "T1369")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1370")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1371")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1372")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1373")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_Guid>, int>), TypeInfoPropertyName = "T1374")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Guid[]>), TypeInfoPropertyName = "T1375")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_Guid[]>), TypeInfoPropertyName = "T1376")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_Guid[]>), TypeInfoPropertyName = "T1377")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_Guid[]>), TypeInfoPropertyName = "T1378")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_Guid[], int>), TypeInfoPropertyName = "T1379")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1380")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1381")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1382")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1383")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_Guid>, int>), TypeInfoPropertyName = "T1384")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1385")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1386")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1387")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1388")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_Guid>, int>), TypeInfoPropertyName = "T1389")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_Guid, int>>), TypeInfoPropertyName = "T1390")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_Guid, int>>), TypeInfoPropertyName = "T1391")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_Guid, int>>), TypeInfoPropertyName = "T1392")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_Guid, int>>), TypeInfoPropertyName = "T1393")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_Guid, int>, int>), TypeInfoPropertyName = "T1394")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1395")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1396")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1397")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1398")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_Guid>, int>), TypeInfoPropertyName = "T1399")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1400")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1401")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1402")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1403")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_Guid, int>), TypeInfoPropertyName = "T1404")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1405")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1406")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1407")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1408")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_Guid, int>), TypeInfoPropertyName = "T1409")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1410")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1411")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1412")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_Guid>>), TypeInfoPropertyName = "T1413")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_Guid>, int>), TypeInfoPropertyName = "T1414")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1415")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1416")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1417")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_Guid>), TypeInfoPropertyName = "T1418")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_Guid, int>), TypeInfoPropertyName = "T1419")] [JsonSerializable(typeof(V_sealed_record_class_int))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_int>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_int>), TypeInfoPropertyName = "T1420")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_int>), TypeInfoPropertyName = "T1421")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_int>), TypeInfoPropertyName = "T1422")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_int>), TypeInfoPropertyName = "T1423")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_int, int>), TypeInfoPropertyName = "T1424")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_int>), TypeInfoPropertyName = "T1425")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_int>), TypeInfoPropertyName = "T1426")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_int>), TypeInfoPropertyName = "T1427")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_int>), TypeInfoPropertyName = "T1428")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_int, int>), TypeInfoPropertyName = "T1429")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1430")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1431")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1432")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1433")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_int>, int>), TypeInfoPropertyName = "T1434")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_int[]>), TypeInfoPropertyName = "T1435")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_int[]>), TypeInfoPropertyName = "T1436")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_int[]>), TypeInfoPropertyName = "T1437")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_int[]>), TypeInfoPropertyName = "T1438")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_int[], int>), TypeInfoPropertyName = "T1439")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1440")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1441")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1442")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1443")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_int>, int>), TypeInfoPropertyName = "T1444")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1445")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1446")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1447")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1448")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_int>, int>), TypeInfoPropertyName = "T1449")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_int, int>>), TypeInfoPropertyName = "T1450")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_int, int>>), TypeInfoPropertyName = "T1451")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_int, int>>), TypeInfoPropertyName = "T1452")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_int, int>>), TypeInfoPropertyName = "T1453")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_int, int>, int>), TypeInfoPropertyName = "T1454")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_int>>), TypeInfoPropertyName = "T1455")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_int>>), TypeInfoPropertyName = "T1456")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_int>>), TypeInfoPropertyName = "T1457")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_int>>), TypeInfoPropertyName = "T1458")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_int>, int>), TypeInfoPropertyName = "T1459")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_int>), TypeInfoPropertyName = "T1460")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_int>), TypeInfoPropertyName = "T1461")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_int>), TypeInfoPropertyName = "T1462")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_int>), TypeInfoPropertyName = "T1463")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_int, int>), TypeInfoPropertyName = "T1464")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_int>), TypeInfoPropertyName = "T1465")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_int>), TypeInfoPropertyName = "T1466")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_int>), TypeInfoPropertyName = "T1467")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_int>), TypeInfoPropertyName = "T1468")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_int, int>), TypeInfoPropertyName = "T1469")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1470")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1471")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1472")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_int>>), TypeInfoPropertyName = "T1473")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_int>, int>), TypeInfoPropertyName = "T1474")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_int>), TypeInfoPropertyName = "T1475")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_int>), TypeInfoPropertyName = "T1476")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_int>), TypeInfoPropertyName = "T1477")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_int>), TypeInfoPropertyName = "T1478")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_int, int>), TypeInfoPropertyName = "T1479")] [JsonSerializable(typeof(V_sealed_record_class_long))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_long>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_long>), TypeInfoPropertyName = "T1480")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_long>), TypeInfoPropertyName = "T1481")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_long>), TypeInfoPropertyName = "T1482")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_long>), TypeInfoPropertyName = "T1483")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_long, int>), TypeInfoPropertyName = "T1484")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_long>), TypeInfoPropertyName = "T1485")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_long>), TypeInfoPropertyName = "T1486")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_long>), TypeInfoPropertyName = "T1487")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_long>), TypeInfoPropertyName = "T1488")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_long, int>), TypeInfoPropertyName = "T1489")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1490")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1491")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1492")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1493")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_long>, int>), TypeInfoPropertyName = "T1494")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_long[]>), TypeInfoPropertyName = "T1495")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_long[]>), TypeInfoPropertyName = "T1496")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_long[]>), TypeInfoPropertyName = "T1497")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_long[]>), TypeInfoPropertyName = "T1498")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_long[], int>), TypeInfoPropertyName = "T1499")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1500")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1501")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1502")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1503")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_long>, int>), TypeInfoPropertyName = "T1504")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1505")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1506")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1507")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1508")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_long>, int>), TypeInfoPropertyName = "T1509")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_long, int>>), TypeInfoPropertyName = "T1510")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_long, int>>), TypeInfoPropertyName = "T1511")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_long, int>>), TypeInfoPropertyName = "T1512")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_long, int>>), TypeInfoPropertyName = "T1513")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_long, int>, int>), TypeInfoPropertyName = "T1514")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_long>>), TypeInfoPropertyName = "T1515")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_long>>), TypeInfoPropertyName = "T1516")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_long>>), TypeInfoPropertyName = "T1517")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_long>>), TypeInfoPropertyName = "T1518")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_long>, int>), TypeInfoPropertyName = "T1519")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_long>), TypeInfoPropertyName = "T1520")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_long>), TypeInfoPropertyName = "T1521")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_long>), TypeInfoPropertyName = "T1522")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_long>), TypeInfoPropertyName = "T1523")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_long, int>), TypeInfoPropertyName = "T1524")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_long>), TypeInfoPropertyName = "T1525")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_long>), TypeInfoPropertyName = "T1526")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_long>), TypeInfoPropertyName = "T1527")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_long>), TypeInfoPropertyName = "T1528")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_long, int>), TypeInfoPropertyName = "T1529")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1530")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1531")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1532")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_long>>), TypeInfoPropertyName = "T1533")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_long>, int>), TypeInfoPropertyName = "T1534")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_long>), TypeInfoPropertyName = "T1535")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_long>), TypeInfoPropertyName = "T1536")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_long>), TypeInfoPropertyName = "T1537")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_long>), TypeInfoPropertyName = "T1538")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_long, int>), TypeInfoPropertyName = "T1539")] [JsonSerializable(typeof(V_sealed_record_class_byte))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_byte>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1540")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1541")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1542")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1543")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_byte, int>), TypeInfoPropertyName = "T1544")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1545")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1546")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1547")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1548")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_byte, int>), TypeInfoPropertyName = "T1549")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1550")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1551")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1552")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1553")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_byte>, int>), TypeInfoPropertyName = "T1554")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_byte[]>), TypeInfoPropertyName = "T1555")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_byte[]>), TypeInfoPropertyName = "T1556")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_byte[]>), TypeInfoPropertyName = "T1557")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_byte[]>), TypeInfoPropertyName = "T1558")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_byte[], int>), TypeInfoPropertyName = "T1559")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1560")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1561")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1562")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1563")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_byte>, int>), TypeInfoPropertyName = "T1564")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1565")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1566")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1567")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1568")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_byte>, int>), TypeInfoPropertyName = "T1569")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_byte, int>>), TypeInfoPropertyName = "T1570")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_byte, int>>), TypeInfoPropertyName = "T1571")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_byte, int>>), TypeInfoPropertyName = "T1572")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_byte, int>>), TypeInfoPropertyName = "T1573")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_byte, int>, int>), TypeInfoPropertyName = "T1574")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1575")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1576")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1577")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1578")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_byte>, int>), TypeInfoPropertyName = "T1579")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1580")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1581")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1582")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1583")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_byte, int>), TypeInfoPropertyName = "T1584")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1585")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1586")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1587")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_byte>), TypeInfoPropertyName = "T1588")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_byte, int>), TypeInfoPropertyName = "T1589")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1590")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1591")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1592")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_byte>>), TypeInfoPropertyName = "T1593")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_byte>, int>), TypeInfoPropertyName = "T1594")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_byte>), TypeInfoPropertyName = "T1595")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_byte>), TypeInfoPropertyName = "T1596")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_byte>), TypeInfoPropertyName = "T1597")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_byte>), TypeInfoPropertyName = "T1598")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_byte, int>), TypeInfoPropertyName = "T1599")] [JsonSerializable(typeof(V_sealed_record_class_ulong))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_ulong>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1600")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1601")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1602")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1603")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_ulong, int>), TypeInfoPropertyName = "T1604")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1605")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1606")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1607")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1608")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_ulong, int>), TypeInfoPropertyName = "T1609")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1610")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1611")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1612")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1613")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_ulong>, int>), TypeInfoPropertyName = "T1614")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_ulong[]>), TypeInfoPropertyName = "T1615")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_ulong[]>), TypeInfoPropertyName = "T1616")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_ulong[]>), TypeInfoPropertyName = "T1617")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_ulong[]>), TypeInfoPropertyName = "T1618")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_ulong[], int>), TypeInfoPropertyName = "T1619")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1620")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1621")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1622")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1623")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_ulong>, int>), TypeInfoPropertyName = "T1624")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1625")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1626")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1627")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1628")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_ulong>, int>), TypeInfoPropertyName = "T1629")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_ulong, int>>), TypeInfoPropertyName = "T1630")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_ulong, int>>), TypeInfoPropertyName = "T1631")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_ulong, int>>), TypeInfoPropertyName = "T1632")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_ulong, int>>), TypeInfoPropertyName = "T1633")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_ulong, int>, int>), TypeInfoPropertyName = "T1634")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1635")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1636")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1637")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1638")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_ulong>, int>), TypeInfoPropertyName = "T1639")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1640")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1641")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1642")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1643")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_ulong, int>), TypeInfoPropertyName = "T1644")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1645")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1646")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1647")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1648")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_ulong, int>), TypeInfoPropertyName = "T1649")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1650")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1651")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1652")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_ulong>>), TypeInfoPropertyName = "T1653")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_ulong>, int>), TypeInfoPropertyName = "T1654")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1655")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1656")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1657")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_ulong>), TypeInfoPropertyName = "T1658")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_ulong, int>), TypeInfoPropertyName = "T1659")] [JsonSerializable(typeof(V_sealed_record_class_decimal))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_decimal>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1660")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1661")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1662")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1663")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_decimal, int>), TypeInfoPropertyName = "T1664")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1665")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1666")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1667")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1668")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_decimal, int>), TypeInfoPropertyName = "T1669")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1670")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1671")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1672")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1673")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_decimal>, int>), TypeInfoPropertyName = "T1674")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_decimal[]>), TypeInfoPropertyName = "T1675")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_decimal[]>), TypeInfoPropertyName = "T1676")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_decimal[]>), TypeInfoPropertyName = "T1677")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_decimal[]>), TypeInfoPropertyName = "T1678")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_decimal[], int>), TypeInfoPropertyName = "T1679")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1680")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1681")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1682")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1683")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_decimal>, int>), TypeInfoPropertyName = "T1684")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1685")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1686")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1687")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1688")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_decimal>, int>), TypeInfoPropertyName = "T1689")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_decimal, int>>), TypeInfoPropertyName = "T1690")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_decimal, int>>), TypeInfoPropertyName = "T1691")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_decimal, int>>), TypeInfoPropertyName = "T1692")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_decimal, int>>), TypeInfoPropertyName = "T1693")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_decimal, int>, int>), TypeInfoPropertyName = "T1694")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1695")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1696")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1697")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1698")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_decimal>, int>), TypeInfoPropertyName = "T1699")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1700")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1701")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1702")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1703")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_decimal, int>), TypeInfoPropertyName = "T1704")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1705")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1706")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1707")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1708")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_decimal, int>), TypeInfoPropertyName = "T1709")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1710")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1711")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1712")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_decimal>>), TypeInfoPropertyName = "T1713")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_decimal>, int>), TypeInfoPropertyName = "T1714")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1715")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1716")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1717")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_decimal>), TypeInfoPropertyName = "T1718")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_decimal, int>), TypeInfoPropertyName = "T1719")] [JsonSerializable(typeof(V_sealed_record_class_double))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_double>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_double>), TypeInfoPropertyName = "T1720")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_double>), TypeInfoPropertyName = "T1721")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_double>), TypeInfoPropertyName = "T1722")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_double>), TypeInfoPropertyName = "T1723")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_double, int>), TypeInfoPropertyName = "T1724")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_double>), TypeInfoPropertyName = "T1725")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_double>), TypeInfoPropertyName = "T1726")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_double>), TypeInfoPropertyName = "T1727")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_double>), TypeInfoPropertyName = "T1728")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_double, int>), TypeInfoPropertyName = "T1729")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1730")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1731")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1732")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1733")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_double>, int>), TypeInfoPropertyName = "T1734")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_double[]>), TypeInfoPropertyName = "T1735")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_double[]>), TypeInfoPropertyName = "T1736")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_double[]>), TypeInfoPropertyName = "T1737")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_double[]>), TypeInfoPropertyName = "T1738")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_double[], int>), TypeInfoPropertyName = "T1739")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1740")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1741")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1742")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1743")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_double>, int>), TypeInfoPropertyName = "T1744")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1745")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1746")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1747")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1748")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_double>, int>), TypeInfoPropertyName = "T1749")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_double, int>>), TypeInfoPropertyName = "T1750")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_double, int>>), TypeInfoPropertyName = "T1751")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_double, int>>), TypeInfoPropertyName = "T1752")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_double, int>>), TypeInfoPropertyName = "T1753")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_double, int>, int>), TypeInfoPropertyName = "T1754")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_double>>), TypeInfoPropertyName = "T1755")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_double>>), TypeInfoPropertyName = "T1756")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_double>>), TypeInfoPropertyName = "T1757")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_double>>), TypeInfoPropertyName = "T1758")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_double>, int>), TypeInfoPropertyName = "T1759")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_double>), TypeInfoPropertyName = "T1760")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_double>), TypeInfoPropertyName = "T1761")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_double>), TypeInfoPropertyName = "T1762")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_double>), TypeInfoPropertyName = "T1763")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_double, int>), TypeInfoPropertyName = "T1764")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_double>), TypeInfoPropertyName = "T1765")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_double>), TypeInfoPropertyName = "T1766")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_double>), TypeInfoPropertyName = "T1767")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_double>), TypeInfoPropertyName = "T1768")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_double, int>), TypeInfoPropertyName = "T1769")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1770")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1771")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1772")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_double>>), TypeInfoPropertyName = "T1773")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_double>, int>), TypeInfoPropertyName = "T1774")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_double>), TypeInfoPropertyName = "T1775")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_double>), TypeInfoPropertyName = "T1776")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_double>), TypeInfoPropertyName = "T1777")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_double>), TypeInfoPropertyName = "T1778")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_double, int>), TypeInfoPropertyName = "T1779")] [JsonSerializable(typeof(V_sealed_record_class_bool))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_bool>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1780")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1781")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1782")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1783")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_bool, int>), TypeInfoPropertyName = "T1784")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1785")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1786")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1787")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1788")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_bool, int>), TypeInfoPropertyName = "T1789")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1790")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1791")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1792")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1793")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_bool>, int>), TypeInfoPropertyName = "T1794")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_bool[]>), TypeInfoPropertyName = "T1795")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_bool[]>), TypeInfoPropertyName = "T1796")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_bool[]>), TypeInfoPropertyName = "T1797")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_bool[]>), TypeInfoPropertyName = "T1798")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_bool[], int>), TypeInfoPropertyName = "T1799")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1800")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1801")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1802")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1803")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_bool>, int>), TypeInfoPropertyName = "T1804")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1805")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1806")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1807")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1808")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_bool>, int>), TypeInfoPropertyName = "T1809")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_bool, int>>), TypeInfoPropertyName = "T1810")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_bool, int>>), TypeInfoPropertyName = "T1811")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_bool, int>>), TypeInfoPropertyName = "T1812")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_bool, int>>), TypeInfoPropertyName = "T1813")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_bool, int>, int>), TypeInfoPropertyName = "T1814")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1815")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1816")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1817")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1818")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_bool>, int>), TypeInfoPropertyName = "T1819")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1820")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1821")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1822")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1823")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_bool, int>), TypeInfoPropertyName = "T1824")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1825")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1826")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1827")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_bool>), TypeInfoPropertyName = "T1828")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_bool, int>), TypeInfoPropertyName = "T1829")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1830")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1831")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1832")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_bool>>), TypeInfoPropertyName = "T1833")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_bool>, int>), TypeInfoPropertyName = "T1834")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_bool>), TypeInfoPropertyName = "T1835")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_bool>), TypeInfoPropertyName = "T1836")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_bool>), TypeInfoPropertyName = "T1837")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_bool>), TypeInfoPropertyName = "T1838")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_bool, int>), TypeInfoPropertyName = "T1839")] [JsonSerializable(typeof(V_sealed_record_class_char))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_char>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_char>), TypeInfoPropertyName = "T1840")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_char>), TypeInfoPropertyName = "T1841")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_char>), TypeInfoPropertyName = "T1842")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_char>), TypeInfoPropertyName = "T1843")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_char, int>), TypeInfoPropertyName = "T1844")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_char>), TypeInfoPropertyName = "T1845")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_char>), TypeInfoPropertyName = "T1846")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_char>), TypeInfoPropertyName = "T1847")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_char>), TypeInfoPropertyName = "T1848")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_char, int>), TypeInfoPropertyName = "T1849")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1850")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1851")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1852")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1853")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_char>, int>), TypeInfoPropertyName = "T1854")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_char[]>), TypeInfoPropertyName = "T1855")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_char[]>), TypeInfoPropertyName = "T1856")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_char[]>), TypeInfoPropertyName = "T1857")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_char[]>), TypeInfoPropertyName = "T1858")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_char[], int>), TypeInfoPropertyName = "T1859")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1860")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1861")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1862")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1863")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_char>, int>), TypeInfoPropertyName = "T1864")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1865")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1866")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1867")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1868")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_char>, int>), TypeInfoPropertyName = "T1869")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_char, int>>), TypeInfoPropertyName = "T1870")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_char, int>>), TypeInfoPropertyName = "T1871")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_char, int>>), TypeInfoPropertyName = "T1872")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_char, int>>), TypeInfoPropertyName = "T1873")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_char, int>, int>), TypeInfoPropertyName = "T1874")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_char>>), TypeInfoPropertyName = "T1875")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_char>>), TypeInfoPropertyName = "T1876")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_char>>), TypeInfoPropertyName = "T1877")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_char>>), TypeInfoPropertyName = "T1878")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_char>, int>), TypeInfoPropertyName = "T1879")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_char>), TypeInfoPropertyName = "T1880")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_char>), TypeInfoPropertyName = "T1881")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_char>), TypeInfoPropertyName = "T1882")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_char>), TypeInfoPropertyName = "T1883")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_char, int>), TypeInfoPropertyName = "T1884")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_char>), TypeInfoPropertyName = "T1885")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_char>), TypeInfoPropertyName = "T1886")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_char>), TypeInfoPropertyName = "T1887")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_char>), TypeInfoPropertyName = "T1888")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_char, int>), TypeInfoPropertyName = "T1889")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1890")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1891")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1892")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_char>>), TypeInfoPropertyName = "T1893")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_char>, int>), TypeInfoPropertyName = "T1894")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_char>), TypeInfoPropertyName = "T1895")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_char>), TypeInfoPropertyName = "T1896")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_char>), TypeInfoPropertyName = "T1897")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_char>), TypeInfoPropertyName = "T1898")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_char, int>), TypeInfoPropertyName = "T1899")] [JsonSerializable(typeof(V_sealed_record_class_DateTime))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTime>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1900")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1901")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1902")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1903")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateTime, int>), TypeInfoPropertyName = "T1904")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1905")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1906")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1907")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1908")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_DateTime, int>), TypeInfoPropertyName = "T1909")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1910")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1911")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1912")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1913")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_DateTime>, int>), TypeInfoPropertyName = "T1914")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTime[]>), TypeInfoPropertyName = "T1915")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateTime[]>), TypeInfoPropertyName = "T1916")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateTime[]>), TypeInfoPropertyName = "T1917")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateTime[]>), TypeInfoPropertyName = "T1918")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateTime[], int>), TypeInfoPropertyName = "T1919")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1920")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1921")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1922")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1923")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_DateTime>, int>), TypeInfoPropertyName = "T1924")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1925")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1926")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1927")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1928")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_DateTime>, int>), TypeInfoPropertyName = "T1929")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_DateTime, int>>), TypeInfoPropertyName = "T1930")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_DateTime, int>>), TypeInfoPropertyName = "T1931")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_DateTime, int>>), TypeInfoPropertyName = "T1932")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_DateTime, int>>), TypeInfoPropertyName = "T1933")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_DateTime, int>, int>), TypeInfoPropertyName = "T1934")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1935")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1936")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1937")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1938")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_DateTime>, int>), TypeInfoPropertyName = "T1939")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1940")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1941")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1942")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1943")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_DateTime, int>), TypeInfoPropertyName = "T1944")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1945")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1946")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1947")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1948")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_DateTime, int>), TypeInfoPropertyName = "T1949")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1950")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1951")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1952")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_DateTime>>), TypeInfoPropertyName = "T1953")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_DateTime>, int>), TypeInfoPropertyName = "T1954")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1955")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1956")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1957")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_DateTime>), TypeInfoPropertyName = "T1958")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_DateTime, int>), TypeInfoPropertyName = "T1959")] [JsonSerializable(typeof(V_sealed_record_class_DateTimeOffset))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTimeOffset>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1960")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1961")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1962")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1963")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateTimeOffset, int>), TypeInfoPropertyName = "T1964")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1965")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1966")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1967")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T1968")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_DateTimeOffset, int>), TypeInfoPropertyName = "T1969")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1970")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1971")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1972")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1973")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T1974")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateTimeOffset[]>), TypeInfoPropertyName = "T1975")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateTimeOffset[]>), TypeInfoPropertyName = "T1976")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateTimeOffset[]>), TypeInfoPropertyName = "T1977")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateTimeOffset[]>), TypeInfoPropertyName = "T1978")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateTimeOffset[], int>), TypeInfoPropertyName = "T1979")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1980")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1981")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1982")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1983")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T1984")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1985")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1986")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1987")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1988")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T1989")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T1990")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T1991")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T1992")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T1993")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_DateTimeOffset, int>, int>), TypeInfoPropertyName = "T1994")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1995")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1996")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1997")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T1998")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T1999")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2000")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2001")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2002")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2003")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_DateTimeOffset, int>), TypeInfoPropertyName = "T2004")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2005")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2006")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2007")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2008")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_DateTimeOffset, int>), TypeInfoPropertyName = "T2009")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T2010")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T2011")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T2012")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_DateTimeOffset>>), TypeInfoPropertyName = "T2013")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T2014")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2015")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2016")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2017")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_DateTimeOffset>), TypeInfoPropertyName = "T2018")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_DateTimeOffset, int>), TypeInfoPropertyName = "T2019")] [JsonSerializable(typeof(V_sealed_record_class_DateOnly))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2020")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2021")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2022")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2023")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateOnly, int>), TypeInfoPropertyName = "T2024")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2025")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2026")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2027")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2028")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_DateOnly, int>), TypeInfoPropertyName = "T2029")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2030")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2031")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2032")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2033")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_DateOnly>, int>), TypeInfoPropertyName = "T2034")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_DateOnly[]>), TypeInfoPropertyName = "T2035")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_DateOnly[]>), TypeInfoPropertyName = "T2036")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_DateOnly[]>), TypeInfoPropertyName = "T2037")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_DateOnly[]>), TypeInfoPropertyName = "T2038")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_DateOnly[], int>), TypeInfoPropertyName = "T2039")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2040")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2041")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2042")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2043")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_DateOnly>, int>), TypeInfoPropertyName = "T2044")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2045")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2046")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2047")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2048")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_DateOnly>, int>), TypeInfoPropertyName = "T2049")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_DateOnly, int>>), TypeInfoPropertyName = "T2050")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_DateOnly, int>>), TypeInfoPropertyName = "T2051")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_DateOnly, int>>), TypeInfoPropertyName = "T2052")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_DateOnly, int>>), TypeInfoPropertyName = "T2053")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_DateOnly, int>, int>), TypeInfoPropertyName = "T2054")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2055")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2056")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2057")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2058")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_DateOnly>, int>), TypeInfoPropertyName = "T2059")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2060")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2061")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2062")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2063")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_DateOnly, int>), TypeInfoPropertyName = "T2064")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2065")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2066")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2067")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2068")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_DateOnly, int>), TypeInfoPropertyName = "T2069")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2070")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2071")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2072")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_DateOnly>>), TypeInfoPropertyName = "T2073")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_DateOnly>, int>), TypeInfoPropertyName = "T2074")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2075")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2076")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2077")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_DateOnly>), TypeInfoPropertyName = "T2078")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_DateOnly, int>), TypeInfoPropertyName = "T2079")] [JsonSerializable(typeof(V_sealed_record_class_TimeOnly))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2080")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2081")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2082")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2083")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_TimeOnly, int>), TypeInfoPropertyName = "T2084")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2085")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2086")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2087")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2088")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_TimeOnly, int>), TypeInfoPropertyName = "T2089")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2090")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2091")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2092")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2093")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_TimeOnly>, int>), TypeInfoPropertyName = "T2094")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeOnly[]>), TypeInfoPropertyName = "T2095")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_TimeOnly[]>), TypeInfoPropertyName = "T2096")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_TimeOnly[]>), TypeInfoPropertyName = "T2097")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_TimeOnly[]>), TypeInfoPropertyName = "T2098")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_TimeOnly[], int>), TypeInfoPropertyName = "T2099")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2100")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2101")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2102")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2103")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_TimeOnly>, int>), TypeInfoPropertyName = "T2104")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2105")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2106")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2107")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2108")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_TimeOnly>, int>), TypeInfoPropertyName = "T2109")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_TimeOnly, int>>), TypeInfoPropertyName = "T2110")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_TimeOnly, int>>), TypeInfoPropertyName = "T2111")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_TimeOnly, int>>), TypeInfoPropertyName = "T2112")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_TimeOnly, int>>), TypeInfoPropertyName = "T2113")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_TimeOnly, int>, int>), TypeInfoPropertyName = "T2114")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2115")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2116")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2117")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2118")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_TimeOnly>, int>), TypeInfoPropertyName = "T2119")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2120")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2121")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2122")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2123")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_TimeOnly, int>), TypeInfoPropertyName = "T2124")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2125")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2126")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2127")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2128")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_TimeOnly, int>), TypeInfoPropertyName = "T2129")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2130")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2131")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2132")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_TimeOnly>>), TypeInfoPropertyName = "T2133")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_TimeOnly>, int>), TypeInfoPropertyName = "T2134")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2135")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2136")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2137")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_TimeOnly>), TypeInfoPropertyName = "T2138")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_TimeOnly, int>), TypeInfoPropertyName = "T2139")] [JsonSerializable(typeof(V_sealed_record_class_TimeSpan))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeSpan>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2140")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2141")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2142")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2143")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_TimeSpan, int>), TypeInfoPropertyName = "T2144")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2145")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2146")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2147")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2148")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_TimeSpan, int>), TypeInfoPropertyName = "T2149")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2150")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2151")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2152")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2153")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_TimeSpan>, int>), TypeInfoPropertyName = "T2154")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_TimeSpan[]>), TypeInfoPropertyName = "T2155")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_TimeSpan[]>), TypeInfoPropertyName = "T2156")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_TimeSpan[]>), TypeInfoPropertyName = "T2157")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_TimeSpan[]>), TypeInfoPropertyName = "T2158")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_TimeSpan[], int>), TypeInfoPropertyName = "T2159")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2160")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2161")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2162")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2163")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_TimeSpan>, int>), TypeInfoPropertyName = "T2164")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2165")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2166")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2167")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2168")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_TimeSpan>, int>), TypeInfoPropertyName = "T2169")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_TimeSpan, int>>), TypeInfoPropertyName = "T2170")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_TimeSpan, int>>), TypeInfoPropertyName = "T2171")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_TimeSpan, int>>), TypeInfoPropertyName = "T2172")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_TimeSpan, int>>), TypeInfoPropertyName = "T2173")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_TimeSpan, int>, int>), TypeInfoPropertyName = "T2174")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2175")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2176")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2177")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2178")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_TimeSpan>, int>), TypeInfoPropertyName = "T2179")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2180")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2181")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2182")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2183")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_TimeSpan, int>), TypeInfoPropertyName = "T2184")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2185")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2186")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2187")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2188")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_TimeSpan, int>), TypeInfoPropertyName = "T2189")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2190")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2191")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2192")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_TimeSpan>>), TypeInfoPropertyName = "T2193")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_TimeSpan>, int>), TypeInfoPropertyName = "T2194")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2195")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2196")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2197")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_TimeSpan>), TypeInfoPropertyName = "T2198")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_TimeSpan, int>), TypeInfoPropertyName = "T2199")] [JsonSerializable(typeof(V_sealed_record_class_enum))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2200")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2201")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2202")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2203")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_enum, int>), TypeInfoPropertyName = "T2204")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2205")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2206")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2207")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2208")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_enum, int>), TypeInfoPropertyName = "T2209")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2210")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2211")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2212")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2213")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_enum>, int>), TypeInfoPropertyName = "T2214")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_enum[]>), TypeInfoPropertyName = "T2215")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_enum[]>), TypeInfoPropertyName = "T2216")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_enum[]>), TypeInfoPropertyName = "T2217")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_enum[]>), TypeInfoPropertyName = "T2218")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_enum[], int>), TypeInfoPropertyName = "T2219")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2220")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2221")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2222")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2223")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_enum>, int>), TypeInfoPropertyName = "T2224")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2225")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2226")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2227")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2228")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_enum>, int>), TypeInfoPropertyName = "T2229")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_enum, int>>), TypeInfoPropertyName = "T2230")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_enum, int>>), TypeInfoPropertyName = "T2231")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_enum, int>>), TypeInfoPropertyName = "T2232")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_enum, int>>), TypeInfoPropertyName = "T2233")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_enum, int>, int>), TypeInfoPropertyName = "T2234")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2235")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2236")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2237")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2238")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_enum>, int>), TypeInfoPropertyName = "T2239")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2240")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2241")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2242")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2243")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_enum, int>), TypeInfoPropertyName = "T2244")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2245")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2246")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2247")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_enum>), TypeInfoPropertyName = "T2248")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_enum, int>), TypeInfoPropertyName = "T2249")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2250")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2251")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2252")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_enum>>), TypeInfoPropertyName = "T2253")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_enum>, int>), TypeInfoPropertyName = "T2254")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_enum>), TypeInfoPropertyName = "T2255")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_enum>), TypeInfoPropertyName = "T2256")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_enum>), TypeInfoPropertyName = "T2257")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_enum>), TypeInfoPropertyName = "T2258")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_enum, int>), TypeInfoPropertyName = "T2259")] [JsonSerializable(typeof(V_sealed_record_class_flags_enum))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_flags_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2260")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2261")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2262")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2263")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_flags_enum, int>), TypeInfoPropertyName = "T2264")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2265")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2266")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2267")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2268")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_flags_enum, int>), TypeInfoPropertyName = "T2269")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2270")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2271")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2272")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2273")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_flags_enum>, int>), TypeInfoPropertyName = "T2274")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_flags_enum[]>), TypeInfoPropertyName = "T2275")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_flags_enum[]>), TypeInfoPropertyName = "T2276")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_flags_enum[]>), TypeInfoPropertyName = "T2277")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_flags_enum[]>), TypeInfoPropertyName = "T2278")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_flags_enum[], int>), TypeInfoPropertyName = "T2279")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2280")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2281")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2282")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2283")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_flags_enum>, int>), TypeInfoPropertyName = "T2284")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2285")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2286")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2287")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2288")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_flags_enum>, int>), TypeInfoPropertyName = "T2289")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_flags_enum, int>>), TypeInfoPropertyName = "T2290")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_flags_enum, int>>), TypeInfoPropertyName = "T2291")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_flags_enum, int>>), TypeInfoPropertyName = "T2292")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_flags_enum, int>>), TypeInfoPropertyName = "T2293")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_flags_enum, int>, int>), TypeInfoPropertyName = "T2294")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2295")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2296")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2297")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2298")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_flags_enum>, int>), TypeInfoPropertyName = "T2299")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2300")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2301")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2302")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2303")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_flags_enum, int>), TypeInfoPropertyName = "T2304")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2305")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2306")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2307")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2308")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_flags_enum, int>), TypeInfoPropertyName = "T2309")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2310")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2311")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2312")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T2313")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_flags_enum>, int>), TypeInfoPropertyName = "T2314")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2315")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2316")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2317")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T2318")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_flags_enum, int>), TypeInfoPropertyName = "T2319")] [JsonSerializable(typeof(V_sealed_record_class_Uri))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Uri>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2320")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2321")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2322")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2323")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_Uri, int>), TypeInfoPropertyName = "T2324")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2325")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2326")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2327")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2328")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_Uri, int>), TypeInfoPropertyName = "T2329")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2330")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2331")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2332")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2333")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_Uri>, int>), TypeInfoPropertyName = "T2334")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_Uri[]>), TypeInfoPropertyName = "T2335")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_Uri[]>), TypeInfoPropertyName = "T2336")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_Uri[]>), TypeInfoPropertyName = "T2337")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_Uri[]>), TypeInfoPropertyName = "T2338")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_Uri[], int>), TypeInfoPropertyName = "T2339")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2340")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2341")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2342")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2343")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_Uri>, int>), TypeInfoPropertyName = "T2344")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2345")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2346")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2347")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2348")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_Uri>, int>), TypeInfoPropertyName = "T2349")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_Uri, int>>), TypeInfoPropertyName = "T2350")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_Uri, int>>), TypeInfoPropertyName = "T2351")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_Uri, int>>), TypeInfoPropertyName = "T2352")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_Uri, int>>), TypeInfoPropertyName = "T2353")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_Uri, int>, int>), TypeInfoPropertyName = "T2354")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2355")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2356")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2357")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2358")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_Uri>, int>), TypeInfoPropertyName = "T2359")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2360")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2361")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2362")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2363")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_Uri, int>), TypeInfoPropertyName = "T2364")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2365")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2366")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2367")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2368")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_Uri, int>), TypeInfoPropertyName = "T2369")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2370")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2371")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2372")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_Uri>>), TypeInfoPropertyName = "T2373")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_Uri>, int>), TypeInfoPropertyName = "T2374")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2375")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2376")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2377")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_Uri>), TypeInfoPropertyName = "T2378")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_Uri, int>), TypeInfoPropertyName = "T2379")] +[JsonSerializable(typeof(V_sealed_record_class_NodaTime_Instant))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2380")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2381")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2382")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2383")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T2384")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2385")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2386")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2387")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2388")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T2389")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2390")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2391")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2392")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2393")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T2394")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T2395")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T2396")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T2397")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T2398")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_NodaTime_Instant[], int>), TypeInfoPropertyName = "T2399")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2400")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2401")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2402")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2403")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T2404")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2405")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2406")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2407")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2408")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T2409")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T2410")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T2411")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T2412")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T2413")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_NodaTime_Instant, int>, int>), TypeInfoPropertyName = "T2414")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2415")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2416")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2417")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2418")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T2419")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2420")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2421")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2422")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2423")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T2424")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2425")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2426")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2427")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2428")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T2429")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2430")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2431")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2432")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_NodaTime_Instant>>), TypeInfoPropertyName = "T2433")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T2434")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2435")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2436")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2437")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_NodaTime_Instant>), TypeInfoPropertyName = "T2438")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T2439")] [JsonSerializable(typeof(V_sealed_record_class_own_record_class))] -[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_own_record_class>))] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2440")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2441")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2442")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2443")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_own_record_class, int>), TypeInfoPropertyName = "T2444")] +[JsonSerializable(typeof(Cn_class_F<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2445")] +[JsonSerializable(typeof(Cn_record_class_F<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2446")] +[JsonSerializable(typeof(Cn_struct_F<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2447")] +[JsonSerializable(typeof(Cn_record_struct_F<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2448")] +[JsonSerializable(typeof(Cn_generic_type_F<V_sealed_record_class_own_record_class, int>), TypeInfoPropertyName = "T2449")] +[JsonSerializable(typeof(Cn_class_P<List<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2450")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2451")] +[JsonSerializable(typeof(Cn_struct_P<List<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2452")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2453")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_sealed_record_class_own_record_class>, int>), TypeInfoPropertyName = "T2454")] +[JsonSerializable(typeof(Cn_class_P<V_sealed_record_class_own_record_class[]>), TypeInfoPropertyName = "T2455")] +[JsonSerializable(typeof(Cn_record_class_P<V_sealed_record_class_own_record_class[]>), TypeInfoPropertyName = "T2456")] +[JsonSerializable(typeof(Cn_struct_P<V_sealed_record_class_own_record_class[]>), TypeInfoPropertyName = "T2457")] +[JsonSerializable(typeof(Cn_record_struct_P<V_sealed_record_class_own_record_class[]>), TypeInfoPropertyName = "T2458")] +[JsonSerializable(typeof(Cn_generic_type_P<V_sealed_record_class_own_record_class[], int>), TypeInfoPropertyName = "T2459")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2460")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2461")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2462")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2463")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_sealed_record_class_own_record_class>, int>), TypeInfoPropertyName = "T2464")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2465")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2466")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2467")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2468")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_sealed_record_class_own_record_class>, int>), TypeInfoPropertyName = "T2469")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_sealed_record_class_own_record_class, int>>), TypeInfoPropertyName = "T2470")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_sealed_record_class_own_record_class, int>>), TypeInfoPropertyName = "T2471")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_sealed_record_class_own_record_class, int>>), TypeInfoPropertyName = "T2472")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_sealed_record_class_own_record_class, int>>), TypeInfoPropertyName = "T2473")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_sealed_record_class_own_record_class, int>, int>), TypeInfoPropertyName = "T2474")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2475")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2476")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2477")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2478")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_sealed_record_class_own_record_class>, int>), TypeInfoPropertyName = "T2479")] +[JsonSerializable(typeof(Cn_class_C<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2480")] +[JsonSerializable(typeof(Cn_record_class_C<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2481")] +[JsonSerializable(typeof(Cn_struct_C<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2482")] +[JsonSerializable(typeof(Cn_record_struct_C<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2483")] +[JsonSerializable(typeof(Cn_generic_type_C<V_sealed_record_class_own_record_class, int>), TypeInfoPropertyName = "T2484")] +[JsonSerializable(typeof(Cn_class_R<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2485")] +[JsonSerializable(typeof(Cn_record_class_R<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2486")] +[JsonSerializable(typeof(Cn_struct_R<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2487")] +[JsonSerializable(typeof(Cn_record_struct_R<V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2488")] +[JsonSerializable(typeof(Cn_generic_type_R<V_sealed_record_class_own_record_class, int>), TypeInfoPropertyName = "T2489")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2490")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2491")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2492")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_sealed_record_class_own_record_class>>), TypeInfoPropertyName = "T2493")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_sealed_record_class_own_record_class>, int>), TypeInfoPropertyName = "T2494")] +[JsonSerializable(typeof(Cn_class_P<PB_V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2495")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2496")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2497")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_sealed_record_class_own_record_class>), TypeInfoPropertyName = "T2498")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_sealed_record_class_own_record_class, int>), TypeInfoPropertyName = "T2499")] [JsonSerializable(typeof(V_validated_struct_string))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_string>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_string>), TypeInfoPropertyName = "T2500")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_string>), TypeInfoPropertyName = "T2501")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_string>), TypeInfoPropertyName = "T2502")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_string>), TypeInfoPropertyName = "T2503")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_string, int>), TypeInfoPropertyName = "T2504")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_string?>), TypeInfoPropertyName = "T2505")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_string?>), TypeInfoPropertyName = "T2506")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_string?>), TypeInfoPropertyName = "T2507")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_string?>), TypeInfoPropertyName = "T2508")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_string?, int>), TypeInfoPropertyName = "T2509")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_string>), TypeInfoPropertyName = "T2510")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_string>), TypeInfoPropertyName = "T2511")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_string>), TypeInfoPropertyName = "T2512")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_string>), TypeInfoPropertyName = "T2513")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_string, int>), TypeInfoPropertyName = "T2514")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_string>>), TypeInfoPropertyName = "T2515")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_string>>), TypeInfoPropertyName = "T2516")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_string>>), TypeInfoPropertyName = "T2517")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_string>>), TypeInfoPropertyName = "T2518")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_string>, int>), TypeInfoPropertyName = "T2519")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_string[]>), TypeInfoPropertyName = "T2520")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_string[]>), TypeInfoPropertyName = "T2521")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_string[]>), TypeInfoPropertyName = "T2522")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_string[]>), TypeInfoPropertyName = "T2523")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_string[], int>), TypeInfoPropertyName = "T2524")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_string>>), TypeInfoPropertyName = "T2525")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_string>>), TypeInfoPropertyName = "T2526")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_string>>), TypeInfoPropertyName = "T2527")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_string>>), TypeInfoPropertyName = "T2528")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_string>, int>), TypeInfoPropertyName = "T2529")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_string>>), TypeInfoPropertyName = "T2530")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_string>>), TypeInfoPropertyName = "T2531")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_string>>), TypeInfoPropertyName = "T2532")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_string>>), TypeInfoPropertyName = "T2533")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_string>, int>), TypeInfoPropertyName = "T2534")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_string, int>>), TypeInfoPropertyName = "T2535")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_string, int>>), TypeInfoPropertyName = "T2536")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_string, int>>), TypeInfoPropertyName = "T2537")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_string, int>>), TypeInfoPropertyName = "T2538")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_string, int>, int>), TypeInfoPropertyName = "T2539")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_string>>), TypeInfoPropertyName = "T2540")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_string>>), TypeInfoPropertyName = "T2541")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_string>>), TypeInfoPropertyName = "T2542")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_string>>), TypeInfoPropertyName = "T2543")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_string>, int>), TypeInfoPropertyName = "T2544")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_string>), TypeInfoPropertyName = "T2545")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_string>), TypeInfoPropertyName = "T2546")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_string>), TypeInfoPropertyName = "T2547")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_string>), TypeInfoPropertyName = "T2548")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_string, int>), TypeInfoPropertyName = "T2549")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_string>), TypeInfoPropertyName = "T2550")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_string>), TypeInfoPropertyName = "T2551")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_string>), TypeInfoPropertyName = "T2552")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_string>), TypeInfoPropertyName = "T2553")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_string, int>), TypeInfoPropertyName = "T2554")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_string>>), TypeInfoPropertyName = "T2555")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_string>>), TypeInfoPropertyName = "T2556")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_string>>), TypeInfoPropertyName = "T2557")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_string>>), TypeInfoPropertyName = "T2558")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_string>, int>), TypeInfoPropertyName = "T2559")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_string>), TypeInfoPropertyName = "T2560")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_string>), TypeInfoPropertyName = "T2561")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_string>), TypeInfoPropertyName = "T2562")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_string>), TypeInfoPropertyName = "T2563")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_string, int>), TypeInfoPropertyName = "T2564")] [JsonSerializable(typeof(V_validated_struct_Guid))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Guid>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Guid>), TypeInfoPropertyName = "T2565")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Guid>), TypeInfoPropertyName = "T2566")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Guid>), TypeInfoPropertyName = "T2567")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Guid>), TypeInfoPropertyName = "T2568")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Guid, int>), TypeInfoPropertyName = "T2569")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Guid?>), TypeInfoPropertyName = "T2570")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Guid?>), TypeInfoPropertyName = "T2571")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Guid?>), TypeInfoPropertyName = "T2572")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Guid?>), TypeInfoPropertyName = "T2573")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Guid?, int>), TypeInfoPropertyName = "T2574")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_Guid>), TypeInfoPropertyName = "T2575")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_Guid>), TypeInfoPropertyName = "T2576")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_Guid>), TypeInfoPropertyName = "T2577")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_Guid>), TypeInfoPropertyName = "T2578")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_Guid, int>), TypeInfoPropertyName = "T2579")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2580")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2581")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2582")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2583")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_Guid>, int>), TypeInfoPropertyName = "T2584")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Guid[]>), TypeInfoPropertyName = "T2585")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Guid[]>), TypeInfoPropertyName = "T2586")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Guid[]>), TypeInfoPropertyName = "T2587")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Guid[]>), TypeInfoPropertyName = "T2588")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Guid[], int>), TypeInfoPropertyName = "T2589")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2590")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2591")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2592")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2593")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_Guid>, int>), TypeInfoPropertyName = "T2594")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2595")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2596")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2597")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2598")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_Guid>, int>), TypeInfoPropertyName = "T2599")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_Guid, int>>), TypeInfoPropertyName = "T2600")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_Guid, int>>), TypeInfoPropertyName = "T2601")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_Guid, int>>), TypeInfoPropertyName = "T2602")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_Guid, int>>), TypeInfoPropertyName = "T2603")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_Guid, int>, int>), TypeInfoPropertyName = "T2604")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_Guid>>), TypeInfoPropertyName = "T2605")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_Guid>>), TypeInfoPropertyName = "T2606")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_Guid>>), TypeInfoPropertyName = "T2607")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_Guid>>), TypeInfoPropertyName = "T2608")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_Guid>, int>), TypeInfoPropertyName = "T2609")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_Guid>), TypeInfoPropertyName = "T2610")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_Guid>), TypeInfoPropertyName = "T2611")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_Guid>), TypeInfoPropertyName = "T2612")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_Guid>), TypeInfoPropertyName = "T2613")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_Guid, int>), TypeInfoPropertyName = "T2614")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_Guid>), TypeInfoPropertyName = "T2615")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_Guid>), TypeInfoPropertyName = "T2616")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_Guid>), TypeInfoPropertyName = "T2617")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_Guid>), TypeInfoPropertyName = "T2618")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_Guid, int>), TypeInfoPropertyName = "T2619")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2620")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2621")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2622")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_Guid>>), TypeInfoPropertyName = "T2623")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_Guid>, int>), TypeInfoPropertyName = "T2624")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_Guid>), TypeInfoPropertyName = "T2625")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_Guid>), TypeInfoPropertyName = "T2626")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_Guid>), TypeInfoPropertyName = "T2627")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_Guid>), TypeInfoPropertyName = "T2628")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_Guid, int>), TypeInfoPropertyName = "T2629")] [JsonSerializable(typeof(V_validated_struct_int))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_int>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_int>), TypeInfoPropertyName = "T2630")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_int>), TypeInfoPropertyName = "T2631")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_int>), TypeInfoPropertyName = "T2632")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_int>), TypeInfoPropertyName = "T2633")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_int, int>), TypeInfoPropertyName = "T2634")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_int?>), TypeInfoPropertyName = "T2635")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_int?>), TypeInfoPropertyName = "T2636")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_int?>), TypeInfoPropertyName = "T2637")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_int?>), TypeInfoPropertyName = "T2638")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_int?, int>), TypeInfoPropertyName = "T2639")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_int>), TypeInfoPropertyName = "T2640")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_int>), TypeInfoPropertyName = "T2641")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_int>), TypeInfoPropertyName = "T2642")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_int>), TypeInfoPropertyName = "T2643")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_int, int>), TypeInfoPropertyName = "T2644")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_int>>), TypeInfoPropertyName = "T2645")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_int>>), TypeInfoPropertyName = "T2646")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_int>>), TypeInfoPropertyName = "T2647")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_int>>), TypeInfoPropertyName = "T2648")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_int>, int>), TypeInfoPropertyName = "T2649")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_int[]>), TypeInfoPropertyName = "T2650")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_int[]>), TypeInfoPropertyName = "T2651")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_int[]>), TypeInfoPropertyName = "T2652")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_int[]>), TypeInfoPropertyName = "T2653")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_int[], int>), TypeInfoPropertyName = "T2654")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_int>>), TypeInfoPropertyName = "T2655")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_int>>), TypeInfoPropertyName = "T2656")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_int>>), TypeInfoPropertyName = "T2657")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_int>>), TypeInfoPropertyName = "T2658")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_int>, int>), TypeInfoPropertyName = "T2659")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_int>>), TypeInfoPropertyName = "T2660")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_int>>), TypeInfoPropertyName = "T2661")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_int>>), TypeInfoPropertyName = "T2662")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_int>>), TypeInfoPropertyName = "T2663")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_int>, int>), TypeInfoPropertyName = "T2664")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_int, int>>), TypeInfoPropertyName = "T2665")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_int, int>>), TypeInfoPropertyName = "T2666")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_int, int>>), TypeInfoPropertyName = "T2667")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_int, int>>), TypeInfoPropertyName = "T2668")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_int, int>, int>), TypeInfoPropertyName = "T2669")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_int>>), TypeInfoPropertyName = "T2670")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_int>>), TypeInfoPropertyName = "T2671")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_int>>), TypeInfoPropertyName = "T2672")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_int>>), TypeInfoPropertyName = "T2673")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_int>, int>), TypeInfoPropertyName = "T2674")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_int>), TypeInfoPropertyName = "T2675")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_int>), TypeInfoPropertyName = "T2676")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_int>), TypeInfoPropertyName = "T2677")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_int>), TypeInfoPropertyName = "T2678")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_int, int>), TypeInfoPropertyName = "T2679")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_int>), TypeInfoPropertyName = "T2680")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_int>), TypeInfoPropertyName = "T2681")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_int>), TypeInfoPropertyName = "T2682")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_int>), TypeInfoPropertyName = "T2683")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_int, int>), TypeInfoPropertyName = "T2684")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_int>>), TypeInfoPropertyName = "T2685")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_int>>), TypeInfoPropertyName = "T2686")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_int>>), TypeInfoPropertyName = "T2687")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_int>>), TypeInfoPropertyName = "T2688")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_int>, int>), TypeInfoPropertyName = "T2689")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_int>), TypeInfoPropertyName = "T2690")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_int>), TypeInfoPropertyName = "T2691")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_int>), TypeInfoPropertyName = "T2692")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_int>), TypeInfoPropertyName = "T2693")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_int, int>), TypeInfoPropertyName = "T2694")] [JsonSerializable(typeof(V_validated_struct_long))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_long>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_long>), TypeInfoPropertyName = "T2695")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_long>), TypeInfoPropertyName = "T2696")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_long>), TypeInfoPropertyName = "T2697")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_long>), TypeInfoPropertyName = "T2698")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_long, int>), TypeInfoPropertyName = "T2699")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_long?>), TypeInfoPropertyName = "T2700")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_long?>), TypeInfoPropertyName = "T2701")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_long?>), TypeInfoPropertyName = "T2702")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_long?>), TypeInfoPropertyName = "T2703")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_long?, int>), TypeInfoPropertyName = "T2704")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_long>), TypeInfoPropertyName = "T2705")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_long>), TypeInfoPropertyName = "T2706")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_long>), TypeInfoPropertyName = "T2707")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_long>), TypeInfoPropertyName = "T2708")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_long, int>), TypeInfoPropertyName = "T2709")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_long>>), TypeInfoPropertyName = "T2710")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_long>>), TypeInfoPropertyName = "T2711")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_long>>), TypeInfoPropertyName = "T2712")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_long>>), TypeInfoPropertyName = "T2713")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_long>, int>), TypeInfoPropertyName = "T2714")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_long[]>), TypeInfoPropertyName = "T2715")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_long[]>), TypeInfoPropertyName = "T2716")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_long[]>), TypeInfoPropertyName = "T2717")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_long[]>), TypeInfoPropertyName = "T2718")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_long[], int>), TypeInfoPropertyName = "T2719")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_long>>), TypeInfoPropertyName = "T2720")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_long>>), TypeInfoPropertyName = "T2721")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_long>>), TypeInfoPropertyName = "T2722")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_long>>), TypeInfoPropertyName = "T2723")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_long>, int>), TypeInfoPropertyName = "T2724")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_long>>), TypeInfoPropertyName = "T2725")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_long>>), TypeInfoPropertyName = "T2726")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_long>>), TypeInfoPropertyName = "T2727")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_long>>), TypeInfoPropertyName = "T2728")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_long>, int>), TypeInfoPropertyName = "T2729")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_long, int>>), TypeInfoPropertyName = "T2730")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_long, int>>), TypeInfoPropertyName = "T2731")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_long, int>>), TypeInfoPropertyName = "T2732")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_long, int>>), TypeInfoPropertyName = "T2733")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_long, int>, int>), TypeInfoPropertyName = "T2734")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_long>>), TypeInfoPropertyName = "T2735")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_long>>), TypeInfoPropertyName = "T2736")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_long>>), TypeInfoPropertyName = "T2737")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_long>>), TypeInfoPropertyName = "T2738")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_long>, int>), TypeInfoPropertyName = "T2739")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_long>), TypeInfoPropertyName = "T2740")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_long>), TypeInfoPropertyName = "T2741")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_long>), TypeInfoPropertyName = "T2742")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_long>), TypeInfoPropertyName = "T2743")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_long, int>), TypeInfoPropertyName = "T2744")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_long>), TypeInfoPropertyName = "T2745")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_long>), TypeInfoPropertyName = "T2746")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_long>), TypeInfoPropertyName = "T2747")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_long>), TypeInfoPropertyName = "T2748")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_long, int>), TypeInfoPropertyName = "T2749")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_long>>), TypeInfoPropertyName = "T2750")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_long>>), TypeInfoPropertyName = "T2751")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_long>>), TypeInfoPropertyName = "T2752")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_long>>), TypeInfoPropertyName = "T2753")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_long>, int>), TypeInfoPropertyName = "T2754")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_long>), TypeInfoPropertyName = "T2755")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_long>), TypeInfoPropertyName = "T2756")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_long>), TypeInfoPropertyName = "T2757")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_long>), TypeInfoPropertyName = "T2758")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_long, int>), TypeInfoPropertyName = "T2759")] [JsonSerializable(typeof(V_validated_struct_byte))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_byte>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_byte>), TypeInfoPropertyName = "T2760")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_byte>), TypeInfoPropertyName = "T2761")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_byte>), TypeInfoPropertyName = "T2762")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_byte>), TypeInfoPropertyName = "T2763")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_byte, int>), TypeInfoPropertyName = "T2764")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_byte?>), TypeInfoPropertyName = "T2765")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_byte?>), TypeInfoPropertyName = "T2766")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_byte?>), TypeInfoPropertyName = "T2767")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_byte?>), TypeInfoPropertyName = "T2768")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_byte?, int>), TypeInfoPropertyName = "T2769")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_byte>), TypeInfoPropertyName = "T2770")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_byte>), TypeInfoPropertyName = "T2771")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_byte>), TypeInfoPropertyName = "T2772")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_byte>), TypeInfoPropertyName = "T2773")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_byte, int>), TypeInfoPropertyName = "T2774")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_byte>>), TypeInfoPropertyName = "T2775")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_byte>>), TypeInfoPropertyName = "T2776")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_byte>>), TypeInfoPropertyName = "T2777")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_byte>>), TypeInfoPropertyName = "T2778")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_byte>, int>), TypeInfoPropertyName = "T2779")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_byte[]>), TypeInfoPropertyName = "T2780")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_byte[]>), TypeInfoPropertyName = "T2781")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_byte[]>), TypeInfoPropertyName = "T2782")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_byte[]>), TypeInfoPropertyName = "T2783")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_byte[], int>), TypeInfoPropertyName = "T2784")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_byte>>), TypeInfoPropertyName = "T2785")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_byte>>), TypeInfoPropertyName = "T2786")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_byte>>), TypeInfoPropertyName = "T2787")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_byte>>), TypeInfoPropertyName = "T2788")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_byte>, int>), TypeInfoPropertyName = "T2789")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_byte>>), TypeInfoPropertyName = "T2790")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_byte>>), TypeInfoPropertyName = "T2791")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_byte>>), TypeInfoPropertyName = "T2792")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_byte>>), TypeInfoPropertyName = "T2793")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_byte>, int>), TypeInfoPropertyName = "T2794")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_byte, int>>), TypeInfoPropertyName = "T2795")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_byte, int>>), TypeInfoPropertyName = "T2796")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_byte, int>>), TypeInfoPropertyName = "T2797")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_byte, int>>), TypeInfoPropertyName = "T2798")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_byte, int>, int>), TypeInfoPropertyName = "T2799")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_byte>>), TypeInfoPropertyName = "T2800")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_byte>>), TypeInfoPropertyName = "T2801")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_byte>>), TypeInfoPropertyName = "T2802")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_byte>>), TypeInfoPropertyName = "T2803")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_byte>, int>), TypeInfoPropertyName = "T2804")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_byte>), TypeInfoPropertyName = "T2805")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_byte>), TypeInfoPropertyName = "T2806")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_byte>), TypeInfoPropertyName = "T2807")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_byte>), TypeInfoPropertyName = "T2808")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_byte, int>), TypeInfoPropertyName = "T2809")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_byte>), TypeInfoPropertyName = "T2810")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_byte>), TypeInfoPropertyName = "T2811")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_byte>), TypeInfoPropertyName = "T2812")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_byte>), TypeInfoPropertyName = "T2813")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_byte, int>), TypeInfoPropertyName = "T2814")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_byte>>), TypeInfoPropertyName = "T2815")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_byte>>), TypeInfoPropertyName = "T2816")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_byte>>), TypeInfoPropertyName = "T2817")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_byte>>), TypeInfoPropertyName = "T2818")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_byte>, int>), TypeInfoPropertyName = "T2819")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_byte>), TypeInfoPropertyName = "T2820")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_byte>), TypeInfoPropertyName = "T2821")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_byte>), TypeInfoPropertyName = "T2822")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_byte>), TypeInfoPropertyName = "T2823")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_byte, int>), TypeInfoPropertyName = "T2824")] [JsonSerializable(typeof(V_validated_struct_ulong))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_ulong>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_ulong>), TypeInfoPropertyName = "T2825")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_ulong>), TypeInfoPropertyName = "T2826")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_ulong>), TypeInfoPropertyName = "T2827")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_ulong>), TypeInfoPropertyName = "T2828")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_ulong, int>), TypeInfoPropertyName = "T2829")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_ulong?>), TypeInfoPropertyName = "T2830")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_ulong?>), TypeInfoPropertyName = "T2831")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_ulong?>), TypeInfoPropertyName = "T2832")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_ulong?>), TypeInfoPropertyName = "T2833")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_ulong?, int>), TypeInfoPropertyName = "T2834")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_ulong>), TypeInfoPropertyName = "T2835")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_ulong>), TypeInfoPropertyName = "T2836")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_ulong>), TypeInfoPropertyName = "T2837")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_ulong>), TypeInfoPropertyName = "T2838")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_ulong, int>), TypeInfoPropertyName = "T2839")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2840")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2841")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2842")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2843")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_ulong>, int>), TypeInfoPropertyName = "T2844")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_ulong[]>), TypeInfoPropertyName = "T2845")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_ulong[]>), TypeInfoPropertyName = "T2846")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_ulong[]>), TypeInfoPropertyName = "T2847")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_ulong[]>), TypeInfoPropertyName = "T2848")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_ulong[], int>), TypeInfoPropertyName = "T2849")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2850")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2851")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2852")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2853")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_ulong>, int>), TypeInfoPropertyName = "T2854")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2855")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2856")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2857")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2858")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_ulong>, int>), TypeInfoPropertyName = "T2859")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_ulong, int>>), TypeInfoPropertyName = "T2860")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_ulong, int>>), TypeInfoPropertyName = "T2861")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_ulong, int>>), TypeInfoPropertyName = "T2862")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_ulong, int>>), TypeInfoPropertyName = "T2863")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_ulong, int>, int>), TypeInfoPropertyName = "T2864")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_ulong>>), TypeInfoPropertyName = "T2865")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_ulong>>), TypeInfoPropertyName = "T2866")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_ulong>>), TypeInfoPropertyName = "T2867")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_ulong>>), TypeInfoPropertyName = "T2868")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_ulong>, int>), TypeInfoPropertyName = "T2869")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_ulong>), TypeInfoPropertyName = "T2870")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_ulong>), TypeInfoPropertyName = "T2871")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_ulong>), TypeInfoPropertyName = "T2872")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_ulong>), TypeInfoPropertyName = "T2873")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_ulong, int>), TypeInfoPropertyName = "T2874")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_ulong>), TypeInfoPropertyName = "T2875")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_ulong>), TypeInfoPropertyName = "T2876")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_ulong>), TypeInfoPropertyName = "T2877")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_ulong>), TypeInfoPropertyName = "T2878")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_ulong, int>), TypeInfoPropertyName = "T2879")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2880")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2881")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2882")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_ulong>>), TypeInfoPropertyName = "T2883")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_ulong>, int>), TypeInfoPropertyName = "T2884")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_ulong>), TypeInfoPropertyName = "T2885")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_ulong>), TypeInfoPropertyName = "T2886")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_ulong>), TypeInfoPropertyName = "T2887")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_ulong>), TypeInfoPropertyName = "T2888")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_ulong, int>), TypeInfoPropertyName = "T2889")] [JsonSerializable(typeof(V_validated_struct_decimal))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_decimal>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_decimal>), TypeInfoPropertyName = "T2890")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_decimal>), TypeInfoPropertyName = "T2891")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_decimal>), TypeInfoPropertyName = "T2892")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_decimal>), TypeInfoPropertyName = "T2893")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_decimal, int>), TypeInfoPropertyName = "T2894")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_decimal?>), TypeInfoPropertyName = "T2895")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_decimal?>), TypeInfoPropertyName = "T2896")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_decimal?>), TypeInfoPropertyName = "T2897")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_decimal?>), TypeInfoPropertyName = "T2898")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_decimal?, int>), TypeInfoPropertyName = "T2899")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_decimal>), TypeInfoPropertyName = "T2900")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_decimal>), TypeInfoPropertyName = "T2901")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_decimal>), TypeInfoPropertyName = "T2902")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_decimal>), TypeInfoPropertyName = "T2903")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_decimal, int>), TypeInfoPropertyName = "T2904")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2905")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2906")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2907")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2908")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_decimal>, int>), TypeInfoPropertyName = "T2909")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_decimal[]>), TypeInfoPropertyName = "T2910")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_decimal[]>), TypeInfoPropertyName = "T2911")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_decimal[]>), TypeInfoPropertyName = "T2912")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_decimal[]>), TypeInfoPropertyName = "T2913")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_decimal[], int>), TypeInfoPropertyName = "T2914")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2915")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2916")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2917")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2918")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_decimal>, int>), TypeInfoPropertyName = "T2919")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2920")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2921")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2922")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2923")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_decimal>, int>), TypeInfoPropertyName = "T2924")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_decimal, int>>), TypeInfoPropertyName = "T2925")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_decimal, int>>), TypeInfoPropertyName = "T2926")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_decimal, int>>), TypeInfoPropertyName = "T2927")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_decimal, int>>), TypeInfoPropertyName = "T2928")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_decimal, int>, int>), TypeInfoPropertyName = "T2929")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_decimal>>), TypeInfoPropertyName = "T2930")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_decimal>>), TypeInfoPropertyName = "T2931")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_decimal>>), TypeInfoPropertyName = "T2932")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_decimal>>), TypeInfoPropertyName = "T2933")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_decimal>, int>), TypeInfoPropertyName = "T2934")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_decimal>), TypeInfoPropertyName = "T2935")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_decimal>), TypeInfoPropertyName = "T2936")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_decimal>), TypeInfoPropertyName = "T2937")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_decimal>), TypeInfoPropertyName = "T2938")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_decimal, int>), TypeInfoPropertyName = "T2939")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_decimal>), TypeInfoPropertyName = "T2940")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_decimal>), TypeInfoPropertyName = "T2941")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_decimal>), TypeInfoPropertyName = "T2942")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_decimal>), TypeInfoPropertyName = "T2943")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_decimal, int>), TypeInfoPropertyName = "T2944")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2945")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2946")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2947")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_decimal>>), TypeInfoPropertyName = "T2948")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_decimal>, int>), TypeInfoPropertyName = "T2949")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_decimal>), TypeInfoPropertyName = "T2950")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_decimal>), TypeInfoPropertyName = "T2951")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_decimal>), TypeInfoPropertyName = "T2952")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_decimal>), TypeInfoPropertyName = "T2953")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_decimal, int>), TypeInfoPropertyName = "T2954")] [JsonSerializable(typeof(V_validated_struct_double))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_double>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_double>), TypeInfoPropertyName = "T2955")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_double>), TypeInfoPropertyName = "T2956")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_double>), TypeInfoPropertyName = "T2957")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_double>), TypeInfoPropertyName = "T2958")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_double, int>), TypeInfoPropertyName = "T2959")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_double?>), TypeInfoPropertyName = "T2960")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_double?>), TypeInfoPropertyName = "T2961")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_double?>), TypeInfoPropertyName = "T2962")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_double?>), TypeInfoPropertyName = "T2963")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_double?, int>), TypeInfoPropertyName = "T2964")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_double>), TypeInfoPropertyName = "T2965")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_double>), TypeInfoPropertyName = "T2966")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_double>), TypeInfoPropertyName = "T2967")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_double>), TypeInfoPropertyName = "T2968")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_double, int>), TypeInfoPropertyName = "T2969")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_double>>), TypeInfoPropertyName = "T2970")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_double>>), TypeInfoPropertyName = "T2971")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_double>>), TypeInfoPropertyName = "T2972")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_double>>), TypeInfoPropertyName = "T2973")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_double>, int>), TypeInfoPropertyName = "T2974")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_double[]>), TypeInfoPropertyName = "T2975")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_double[]>), TypeInfoPropertyName = "T2976")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_double[]>), TypeInfoPropertyName = "T2977")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_double[]>), TypeInfoPropertyName = "T2978")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_double[], int>), TypeInfoPropertyName = "T2979")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_double>>), TypeInfoPropertyName = "T2980")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_double>>), TypeInfoPropertyName = "T2981")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_double>>), TypeInfoPropertyName = "T2982")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_double>>), TypeInfoPropertyName = "T2983")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_double>, int>), TypeInfoPropertyName = "T2984")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_double>>), TypeInfoPropertyName = "T2985")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_double>>), TypeInfoPropertyName = "T2986")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_double>>), TypeInfoPropertyName = "T2987")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_double>>), TypeInfoPropertyName = "T2988")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_double>, int>), TypeInfoPropertyName = "T2989")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_double, int>>), TypeInfoPropertyName = "T2990")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_double, int>>), TypeInfoPropertyName = "T2991")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_double, int>>), TypeInfoPropertyName = "T2992")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_double, int>>), TypeInfoPropertyName = "T2993")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_double, int>, int>), TypeInfoPropertyName = "T2994")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_double>>), TypeInfoPropertyName = "T2995")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_double>>), TypeInfoPropertyName = "T2996")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_double>>), TypeInfoPropertyName = "T2997")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_double>>), TypeInfoPropertyName = "T2998")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_double>, int>), TypeInfoPropertyName = "T2999")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_double>), TypeInfoPropertyName = "T3000")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_double>), TypeInfoPropertyName = "T3001")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_double>), TypeInfoPropertyName = "T3002")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_double>), TypeInfoPropertyName = "T3003")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_double, int>), TypeInfoPropertyName = "T3004")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_double>), TypeInfoPropertyName = "T3005")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_double>), TypeInfoPropertyName = "T3006")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_double>), TypeInfoPropertyName = "T3007")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_double>), TypeInfoPropertyName = "T3008")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_double, int>), TypeInfoPropertyName = "T3009")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_double>>), TypeInfoPropertyName = "T3010")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_double>>), TypeInfoPropertyName = "T3011")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_double>>), TypeInfoPropertyName = "T3012")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_double>>), TypeInfoPropertyName = "T3013")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_double>, int>), TypeInfoPropertyName = "T3014")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_double>), TypeInfoPropertyName = "T3015")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_double>), TypeInfoPropertyName = "T3016")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_double>), TypeInfoPropertyName = "T3017")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_double>), TypeInfoPropertyName = "T3018")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_double, int>), TypeInfoPropertyName = "T3019")] [JsonSerializable(typeof(V_validated_struct_bool))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_bool>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_bool>), TypeInfoPropertyName = "T3020")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_bool>), TypeInfoPropertyName = "T3021")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_bool>), TypeInfoPropertyName = "T3022")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_bool>), TypeInfoPropertyName = "T3023")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_bool, int>), TypeInfoPropertyName = "T3024")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_bool?>), TypeInfoPropertyName = "T3025")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_bool?>), TypeInfoPropertyName = "T3026")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_bool?>), TypeInfoPropertyName = "T3027")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_bool?>), TypeInfoPropertyName = "T3028")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_bool?, int>), TypeInfoPropertyName = "T3029")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_bool>), TypeInfoPropertyName = "T3030")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_bool>), TypeInfoPropertyName = "T3031")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_bool>), TypeInfoPropertyName = "T3032")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_bool>), TypeInfoPropertyName = "T3033")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_bool, int>), TypeInfoPropertyName = "T3034")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_bool>>), TypeInfoPropertyName = "T3035")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_bool>>), TypeInfoPropertyName = "T3036")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_bool>>), TypeInfoPropertyName = "T3037")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_bool>>), TypeInfoPropertyName = "T3038")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_bool>, int>), TypeInfoPropertyName = "T3039")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_bool[]>), TypeInfoPropertyName = "T3040")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_bool[]>), TypeInfoPropertyName = "T3041")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_bool[]>), TypeInfoPropertyName = "T3042")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_bool[]>), TypeInfoPropertyName = "T3043")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_bool[], int>), TypeInfoPropertyName = "T3044")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_bool>>), TypeInfoPropertyName = "T3045")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_bool>>), TypeInfoPropertyName = "T3046")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_bool>>), TypeInfoPropertyName = "T3047")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_bool>>), TypeInfoPropertyName = "T3048")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_bool>, int>), TypeInfoPropertyName = "T3049")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_bool>>), TypeInfoPropertyName = "T3050")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_bool>>), TypeInfoPropertyName = "T3051")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_bool>>), TypeInfoPropertyName = "T3052")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_bool>>), TypeInfoPropertyName = "T3053")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_bool>, int>), TypeInfoPropertyName = "T3054")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_bool, int>>), TypeInfoPropertyName = "T3055")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_bool, int>>), TypeInfoPropertyName = "T3056")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_bool, int>>), TypeInfoPropertyName = "T3057")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_bool, int>>), TypeInfoPropertyName = "T3058")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_bool, int>, int>), TypeInfoPropertyName = "T3059")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_bool>>), TypeInfoPropertyName = "T3060")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_bool>>), TypeInfoPropertyName = "T3061")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_bool>>), TypeInfoPropertyName = "T3062")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_bool>>), TypeInfoPropertyName = "T3063")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_bool>, int>), TypeInfoPropertyName = "T3064")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_bool>), TypeInfoPropertyName = "T3065")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_bool>), TypeInfoPropertyName = "T3066")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_bool>), TypeInfoPropertyName = "T3067")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_bool>), TypeInfoPropertyName = "T3068")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_bool, int>), TypeInfoPropertyName = "T3069")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_bool>), TypeInfoPropertyName = "T3070")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_bool>), TypeInfoPropertyName = "T3071")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_bool>), TypeInfoPropertyName = "T3072")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_bool>), TypeInfoPropertyName = "T3073")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_bool, int>), TypeInfoPropertyName = "T3074")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_bool>>), TypeInfoPropertyName = "T3075")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_bool>>), TypeInfoPropertyName = "T3076")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_bool>>), TypeInfoPropertyName = "T3077")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_bool>>), TypeInfoPropertyName = "T3078")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_bool>, int>), TypeInfoPropertyName = "T3079")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_bool>), TypeInfoPropertyName = "T3080")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_bool>), TypeInfoPropertyName = "T3081")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_bool>), TypeInfoPropertyName = "T3082")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_bool>), TypeInfoPropertyName = "T3083")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_bool, int>), TypeInfoPropertyName = "T3084")] [JsonSerializable(typeof(V_validated_struct_char))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_char>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_char>), TypeInfoPropertyName = "T3085")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_char>), TypeInfoPropertyName = "T3086")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_char>), TypeInfoPropertyName = "T3087")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_char>), TypeInfoPropertyName = "T3088")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_char, int>), TypeInfoPropertyName = "T3089")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_char?>), TypeInfoPropertyName = "T3090")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_char?>), TypeInfoPropertyName = "T3091")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_char?>), TypeInfoPropertyName = "T3092")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_char?>), TypeInfoPropertyName = "T3093")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_char?, int>), TypeInfoPropertyName = "T3094")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_char>), TypeInfoPropertyName = "T3095")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_char>), TypeInfoPropertyName = "T3096")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_char>), TypeInfoPropertyName = "T3097")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_char>), TypeInfoPropertyName = "T3098")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_char, int>), TypeInfoPropertyName = "T3099")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_char>>), TypeInfoPropertyName = "T3100")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_char>>), TypeInfoPropertyName = "T3101")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_char>>), TypeInfoPropertyName = "T3102")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_char>>), TypeInfoPropertyName = "T3103")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_char>, int>), TypeInfoPropertyName = "T3104")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_char[]>), TypeInfoPropertyName = "T3105")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_char[]>), TypeInfoPropertyName = "T3106")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_char[]>), TypeInfoPropertyName = "T3107")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_char[]>), TypeInfoPropertyName = "T3108")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_char[], int>), TypeInfoPropertyName = "T3109")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_char>>), TypeInfoPropertyName = "T3110")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_char>>), TypeInfoPropertyName = "T3111")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_char>>), TypeInfoPropertyName = "T3112")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_char>>), TypeInfoPropertyName = "T3113")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_char>, int>), TypeInfoPropertyName = "T3114")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_char>>), TypeInfoPropertyName = "T3115")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_char>>), TypeInfoPropertyName = "T3116")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_char>>), TypeInfoPropertyName = "T3117")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_char>>), TypeInfoPropertyName = "T3118")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_char>, int>), TypeInfoPropertyName = "T3119")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_char, int>>), TypeInfoPropertyName = "T3120")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_char, int>>), TypeInfoPropertyName = "T3121")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_char, int>>), TypeInfoPropertyName = "T3122")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_char, int>>), TypeInfoPropertyName = "T3123")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_char, int>, int>), TypeInfoPropertyName = "T3124")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_char>>), TypeInfoPropertyName = "T3125")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_char>>), TypeInfoPropertyName = "T3126")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_char>>), TypeInfoPropertyName = "T3127")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_char>>), TypeInfoPropertyName = "T3128")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_char>, int>), TypeInfoPropertyName = "T3129")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_char>), TypeInfoPropertyName = "T3130")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_char>), TypeInfoPropertyName = "T3131")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_char>), TypeInfoPropertyName = "T3132")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_char>), TypeInfoPropertyName = "T3133")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_char, int>), TypeInfoPropertyName = "T3134")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_char>), TypeInfoPropertyName = "T3135")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_char>), TypeInfoPropertyName = "T3136")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_char>), TypeInfoPropertyName = "T3137")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_char>), TypeInfoPropertyName = "T3138")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_char, int>), TypeInfoPropertyName = "T3139")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_char>>), TypeInfoPropertyName = "T3140")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_char>>), TypeInfoPropertyName = "T3141")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_char>>), TypeInfoPropertyName = "T3142")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_char>>), TypeInfoPropertyName = "T3143")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_char>, int>), TypeInfoPropertyName = "T3144")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_char>), TypeInfoPropertyName = "T3145")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_char>), TypeInfoPropertyName = "T3146")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_char>), TypeInfoPropertyName = "T3147")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_char>), TypeInfoPropertyName = "T3148")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_char, int>), TypeInfoPropertyName = "T3149")] [JsonSerializable(typeof(V_validated_struct_DateTime))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTime>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3150")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3151")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3152")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3153")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTime, int>), TypeInfoPropertyName = "T3154")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T3155")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T3156")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T3157")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T3158")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTime?, int>), TypeInfoPropertyName = "T3159")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3160")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3161")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3162")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3163")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateTime, int>), TypeInfoPropertyName = "T3164")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3165")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3166")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3167")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3168")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateTime>, int>), TypeInfoPropertyName = "T3169")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTime[]>), TypeInfoPropertyName = "T3170")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTime[]>), TypeInfoPropertyName = "T3171")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTime[]>), TypeInfoPropertyName = "T3172")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTime[]>), TypeInfoPropertyName = "T3173")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTime[], int>), TypeInfoPropertyName = "T3174")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3175")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3176")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3177")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3178")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateTime>, int>), TypeInfoPropertyName = "T3179")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3180")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3181")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3182")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3183")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateTime>, int>), TypeInfoPropertyName = "T3184")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_DateTime, int>>), TypeInfoPropertyName = "T3185")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_DateTime, int>>), TypeInfoPropertyName = "T3186")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_DateTime, int>>), TypeInfoPropertyName = "T3187")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_DateTime, int>>), TypeInfoPropertyName = "T3188")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_DateTime, int>, int>), TypeInfoPropertyName = "T3189")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3190")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3191")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3192")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3193")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateTime>, int>), TypeInfoPropertyName = "T3194")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3195")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3196")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3197")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3198")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateTime, int>), TypeInfoPropertyName = "T3199")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3200")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3201")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3202")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateTime>), TypeInfoPropertyName = "T3203")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateTime, int>), TypeInfoPropertyName = "T3204")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3205")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3206")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3207")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_DateTime>>), TypeInfoPropertyName = "T3208")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_DateTime>, int>), TypeInfoPropertyName = "T3209")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_DateTime>), TypeInfoPropertyName = "T3210")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_DateTime>), TypeInfoPropertyName = "T3211")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_DateTime>), TypeInfoPropertyName = "T3212")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_DateTime>), TypeInfoPropertyName = "T3213")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_DateTime, int>), TypeInfoPropertyName = "T3214")] [JsonSerializable(typeof(V_validated_struct_DateTimeOffset))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTimeOffset>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3215")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3216")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3217")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3218")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T3219")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T3220")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T3221")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T3222")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T3223")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T3224")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3225")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3226")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3227")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3228")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T3229")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3230")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3231")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3232")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3233")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T3234")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T3235")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T3236")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T3237")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTimeOffset[]>), TypeInfoPropertyName = "T3238")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTimeOffset[], int>), TypeInfoPropertyName = "T3239")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3240")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3241")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3242")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3243")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T3244")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3245")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3246")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3247")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3248")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T3249")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T3250")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T3251")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T3252")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_DateTimeOffset, int>>), TypeInfoPropertyName = "T3253")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_DateTimeOffset, int>, int>), TypeInfoPropertyName = "T3254")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3255")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3256")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3257")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3258")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T3259")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3260")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3261")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3262")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3263")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T3264")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3265")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3266")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3267")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3268")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T3269")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3270")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3271")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3272")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_DateTimeOffset>>), TypeInfoPropertyName = "T3273")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_DateTimeOffset>, int>), TypeInfoPropertyName = "T3274")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3275")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3276")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3277")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_DateTimeOffset>), TypeInfoPropertyName = "T3278")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_DateTimeOffset, int>), TypeInfoPropertyName = "T3279")] [JsonSerializable(typeof(V_validated_struct_DateOnly))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3280")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3281")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3282")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3283")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateOnly, int>), TypeInfoPropertyName = "T3284")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T3285")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T3286")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T3287")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T3288")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateOnly?, int>), TypeInfoPropertyName = "T3289")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3290")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3291")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3292")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3293")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateOnly, int>), TypeInfoPropertyName = "T3294")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3295")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3296")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3297")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3298")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateOnly>, int>), TypeInfoPropertyName = "T3299")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateOnly[]>), TypeInfoPropertyName = "T3300")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateOnly[]>), TypeInfoPropertyName = "T3301")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateOnly[]>), TypeInfoPropertyName = "T3302")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateOnly[]>), TypeInfoPropertyName = "T3303")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateOnly[], int>), TypeInfoPropertyName = "T3304")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3305")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3306")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3307")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3308")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateOnly>, int>), TypeInfoPropertyName = "T3309")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3310")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3311")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3312")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3313")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateOnly>, int>), TypeInfoPropertyName = "T3314")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_DateOnly, int>>), TypeInfoPropertyName = "T3315")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_DateOnly, int>>), TypeInfoPropertyName = "T3316")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_DateOnly, int>>), TypeInfoPropertyName = "T3317")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_DateOnly, int>>), TypeInfoPropertyName = "T3318")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_DateOnly, int>, int>), TypeInfoPropertyName = "T3319")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3320")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3321")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3322")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3323")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateOnly>, int>), TypeInfoPropertyName = "T3324")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3325")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3326")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3327")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3328")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateOnly, int>), TypeInfoPropertyName = "T3329")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3330")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3331")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3332")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3333")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateOnly, int>), TypeInfoPropertyName = "T3334")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3335")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3336")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3337")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_DateOnly>>), TypeInfoPropertyName = "T3338")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_DateOnly>, int>), TypeInfoPropertyName = "T3339")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3340")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3341")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3342")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_DateOnly>), TypeInfoPropertyName = "T3343")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_DateOnly, int>), TypeInfoPropertyName = "T3344")] [JsonSerializable(typeof(V_validated_struct_TimeOnly))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3345")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3346")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3347")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3348")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeOnly, int>), TypeInfoPropertyName = "T3349")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T3350")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T3351")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T3352")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T3353")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeOnly?, int>), TypeInfoPropertyName = "T3354")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3355")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3356")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3357")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3358")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_TimeOnly, int>), TypeInfoPropertyName = "T3359")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3360")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3361")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3362")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3363")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_TimeOnly>, int>), TypeInfoPropertyName = "T3364")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeOnly[]>), TypeInfoPropertyName = "T3365")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeOnly[]>), TypeInfoPropertyName = "T3366")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeOnly[]>), TypeInfoPropertyName = "T3367")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeOnly[]>), TypeInfoPropertyName = "T3368")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeOnly[], int>), TypeInfoPropertyName = "T3369")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3370")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3371")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3372")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3373")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_TimeOnly>, int>), TypeInfoPropertyName = "T3374")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3375")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3376")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3377")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3378")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_TimeOnly>, int>), TypeInfoPropertyName = "T3379")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_TimeOnly, int>>), TypeInfoPropertyName = "T3380")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_TimeOnly, int>>), TypeInfoPropertyName = "T3381")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_TimeOnly, int>>), TypeInfoPropertyName = "T3382")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_TimeOnly, int>>), TypeInfoPropertyName = "T3383")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_TimeOnly, int>, int>), TypeInfoPropertyName = "T3384")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3385")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3386")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3387")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3388")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_TimeOnly>, int>), TypeInfoPropertyName = "T3389")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3390")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3391")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3392")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3393")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_TimeOnly, int>), TypeInfoPropertyName = "T3394")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3395")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3396")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3397")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3398")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_TimeOnly, int>), TypeInfoPropertyName = "T3399")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3400")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3401")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3402")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_TimeOnly>>), TypeInfoPropertyName = "T3403")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_TimeOnly>, int>), TypeInfoPropertyName = "T3404")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3405")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3406")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3407")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_TimeOnly>), TypeInfoPropertyName = "T3408")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_TimeOnly, int>), TypeInfoPropertyName = "T3409")] [JsonSerializable(typeof(V_validated_struct_TimeSpan))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeSpan>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3410")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3411")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3412")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3413")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeSpan, int>), TypeInfoPropertyName = "T3414")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T3415")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T3416")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T3417")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T3418")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeSpan?, int>), TypeInfoPropertyName = "T3419")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3420")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3421")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3422")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3423")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_TimeSpan, int>), TypeInfoPropertyName = "T3424")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3425")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3426")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3427")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3428")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_TimeSpan>, int>), TypeInfoPropertyName = "T3429")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeSpan[]>), TypeInfoPropertyName = "T3430")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeSpan[]>), TypeInfoPropertyName = "T3431")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeSpan[]>), TypeInfoPropertyName = "T3432")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeSpan[]>), TypeInfoPropertyName = "T3433")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeSpan[], int>), TypeInfoPropertyName = "T3434")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3435")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3436")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3437")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3438")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_TimeSpan>, int>), TypeInfoPropertyName = "T3439")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3440")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3441")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3442")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3443")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_TimeSpan>, int>), TypeInfoPropertyName = "T3444")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_TimeSpan, int>>), TypeInfoPropertyName = "T3445")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_TimeSpan, int>>), TypeInfoPropertyName = "T3446")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_TimeSpan, int>>), TypeInfoPropertyName = "T3447")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_TimeSpan, int>>), TypeInfoPropertyName = "T3448")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_TimeSpan, int>, int>), TypeInfoPropertyName = "T3449")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3450")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3451")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3452")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3453")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_TimeSpan>, int>), TypeInfoPropertyName = "T3454")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3455")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3456")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3457")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3458")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_TimeSpan, int>), TypeInfoPropertyName = "T3459")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3460")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3461")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3462")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3463")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_TimeSpan, int>), TypeInfoPropertyName = "T3464")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3465")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3466")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3467")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_TimeSpan>>), TypeInfoPropertyName = "T3468")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_TimeSpan>, int>), TypeInfoPropertyName = "T3469")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3470")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3471")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3472")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_TimeSpan>), TypeInfoPropertyName = "T3473")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_TimeSpan, int>), TypeInfoPropertyName = "T3474")] [JsonSerializable(typeof(V_validated_struct_enum))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_enum>), TypeInfoPropertyName = "T3475")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_enum>), TypeInfoPropertyName = "T3476")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_enum>), TypeInfoPropertyName = "T3477")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_enum>), TypeInfoPropertyName = "T3478")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_enum, int>), TypeInfoPropertyName = "T3479")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_enum?>), TypeInfoPropertyName = "T3480")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_enum?>), TypeInfoPropertyName = "T3481")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_enum?>), TypeInfoPropertyName = "T3482")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_enum?>), TypeInfoPropertyName = "T3483")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_enum?, int>), TypeInfoPropertyName = "T3484")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_enum>), TypeInfoPropertyName = "T3485")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_enum>), TypeInfoPropertyName = "T3486")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_enum>), TypeInfoPropertyName = "T3487")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_enum>), TypeInfoPropertyName = "T3488")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_enum, int>), TypeInfoPropertyName = "T3489")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_enum>>), TypeInfoPropertyName = "T3490")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_enum>>), TypeInfoPropertyName = "T3491")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_enum>>), TypeInfoPropertyName = "T3492")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_enum>>), TypeInfoPropertyName = "T3493")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_enum>, int>), TypeInfoPropertyName = "T3494")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_enum[]>), TypeInfoPropertyName = "T3495")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_enum[]>), TypeInfoPropertyName = "T3496")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_enum[]>), TypeInfoPropertyName = "T3497")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_enum[]>), TypeInfoPropertyName = "T3498")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_enum[], int>), TypeInfoPropertyName = "T3499")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_enum>>), TypeInfoPropertyName = "T3500")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_enum>>), TypeInfoPropertyName = "T3501")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_enum>>), TypeInfoPropertyName = "T3502")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_enum>>), TypeInfoPropertyName = "T3503")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_enum>, int>), TypeInfoPropertyName = "T3504")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_enum>>), TypeInfoPropertyName = "T3505")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_enum>>), TypeInfoPropertyName = "T3506")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_enum>>), TypeInfoPropertyName = "T3507")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_enum>>), TypeInfoPropertyName = "T3508")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_enum>, int>), TypeInfoPropertyName = "T3509")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_enum, int>>), TypeInfoPropertyName = "T3510")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_enum, int>>), TypeInfoPropertyName = "T3511")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_enum, int>>), TypeInfoPropertyName = "T3512")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_enum, int>>), TypeInfoPropertyName = "T3513")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_enum, int>, int>), TypeInfoPropertyName = "T3514")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_enum>>), TypeInfoPropertyName = "T3515")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_enum>>), TypeInfoPropertyName = "T3516")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_enum>>), TypeInfoPropertyName = "T3517")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_enum>>), TypeInfoPropertyName = "T3518")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_enum>, int>), TypeInfoPropertyName = "T3519")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_enum>), TypeInfoPropertyName = "T3520")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_enum>), TypeInfoPropertyName = "T3521")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_enum>), TypeInfoPropertyName = "T3522")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_enum>), TypeInfoPropertyName = "T3523")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_enum, int>), TypeInfoPropertyName = "T3524")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_enum>), TypeInfoPropertyName = "T3525")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_enum>), TypeInfoPropertyName = "T3526")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_enum>), TypeInfoPropertyName = "T3527")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_enum>), TypeInfoPropertyName = "T3528")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_enum, int>), TypeInfoPropertyName = "T3529")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_enum>>), TypeInfoPropertyName = "T3530")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_enum>>), TypeInfoPropertyName = "T3531")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_enum>>), TypeInfoPropertyName = "T3532")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_enum>>), TypeInfoPropertyName = "T3533")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_enum>, int>), TypeInfoPropertyName = "T3534")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_enum>), TypeInfoPropertyName = "T3535")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_enum>), TypeInfoPropertyName = "T3536")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_enum>), TypeInfoPropertyName = "T3537")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_enum>), TypeInfoPropertyName = "T3538")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_enum, int>), TypeInfoPropertyName = "T3539")] [JsonSerializable(typeof(V_validated_struct_flags_enum))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_flags_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3540")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3541")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3542")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3543")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_flags_enum, int>), TypeInfoPropertyName = "T3544")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T3545")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T3546")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T3547")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T3548")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_flags_enum?, int>), TypeInfoPropertyName = "T3549")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3550")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3551")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3552")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3553")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_flags_enum, int>), TypeInfoPropertyName = "T3554")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3555")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3556")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3557")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3558")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_flags_enum>, int>), TypeInfoPropertyName = "T3559")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_flags_enum[]>), TypeInfoPropertyName = "T3560")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_flags_enum[]>), TypeInfoPropertyName = "T3561")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_flags_enum[]>), TypeInfoPropertyName = "T3562")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_flags_enum[]>), TypeInfoPropertyName = "T3563")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_flags_enum[], int>), TypeInfoPropertyName = "T3564")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3565")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3566")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3567")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3568")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_flags_enum>, int>), TypeInfoPropertyName = "T3569")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3570")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3571")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3572")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3573")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_flags_enum>, int>), TypeInfoPropertyName = "T3574")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_flags_enum, int>>), TypeInfoPropertyName = "T3575")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_flags_enum, int>>), TypeInfoPropertyName = "T3576")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_flags_enum, int>>), TypeInfoPropertyName = "T3577")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_flags_enum, int>>), TypeInfoPropertyName = "T3578")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_flags_enum, int>, int>), TypeInfoPropertyName = "T3579")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3580")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3581")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3582")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3583")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_flags_enum>, int>), TypeInfoPropertyName = "T3584")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3585")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3586")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3587")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3588")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_flags_enum, int>), TypeInfoPropertyName = "T3589")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3590")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3591")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3592")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3593")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_flags_enum, int>), TypeInfoPropertyName = "T3594")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3595")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3596")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3597")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_flags_enum>>), TypeInfoPropertyName = "T3598")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_flags_enum>, int>), TypeInfoPropertyName = "T3599")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3600")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3601")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3602")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_flags_enum>), TypeInfoPropertyName = "T3603")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_flags_enum, int>), TypeInfoPropertyName = "T3604")] [JsonSerializable(typeof(V_validated_struct_Uri))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Uri>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Uri>), TypeInfoPropertyName = "T3605")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Uri>), TypeInfoPropertyName = "T3606")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Uri>), TypeInfoPropertyName = "T3607")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Uri>), TypeInfoPropertyName = "T3608")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Uri, int>), TypeInfoPropertyName = "T3609")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Uri?>), TypeInfoPropertyName = "T3610")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Uri?>), TypeInfoPropertyName = "T3611")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Uri?>), TypeInfoPropertyName = "T3612")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Uri?>), TypeInfoPropertyName = "T3613")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Uri?, int>), TypeInfoPropertyName = "T3614")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_Uri>), TypeInfoPropertyName = "T3615")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_Uri>), TypeInfoPropertyName = "T3616")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_Uri>), TypeInfoPropertyName = "T3617")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_Uri>), TypeInfoPropertyName = "T3618")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_Uri, int>), TypeInfoPropertyName = "T3619")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3620")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3621")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3622")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3623")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_Uri>, int>), TypeInfoPropertyName = "T3624")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Uri[]>), TypeInfoPropertyName = "T3625")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Uri[]>), TypeInfoPropertyName = "T3626")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Uri[]>), TypeInfoPropertyName = "T3627")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Uri[]>), TypeInfoPropertyName = "T3628")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Uri[], int>), TypeInfoPropertyName = "T3629")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3630")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3631")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3632")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3633")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_Uri>, int>), TypeInfoPropertyName = "T3634")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3635")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3636")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3637")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3638")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_Uri>, int>), TypeInfoPropertyName = "T3639")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_Uri, int>>), TypeInfoPropertyName = "T3640")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_Uri, int>>), TypeInfoPropertyName = "T3641")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_Uri, int>>), TypeInfoPropertyName = "T3642")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_Uri, int>>), TypeInfoPropertyName = "T3643")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_Uri, int>, int>), TypeInfoPropertyName = "T3644")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_Uri>>), TypeInfoPropertyName = "T3645")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_Uri>>), TypeInfoPropertyName = "T3646")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_Uri>>), TypeInfoPropertyName = "T3647")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_Uri>>), TypeInfoPropertyName = "T3648")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_Uri>, int>), TypeInfoPropertyName = "T3649")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_Uri>), TypeInfoPropertyName = "T3650")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_Uri>), TypeInfoPropertyName = "T3651")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_Uri>), TypeInfoPropertyName = "T3652")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_Uri>), TypeInfoPropertyName = "T3653")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_Uri, int>), TypeInfoPropertyName = "T3654")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_Uri>), TypeInfoPropertyName = "T3655")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_Uri>), TypeInfoPropertyName = "T3656")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_Uri>), TypeInfoPropertyName = "T3657")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_Uri>), TypeInfoPropertyName = "T3658")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_Uri, int>), TypeInfoPropertyName = "T3659")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3660")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3661")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3662")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_Uri>>), TypeInfoPropertyName = "T3663")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_Uri>, int>), TypeInfoPropertyName = "T3664")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_Uri>), TypeInfoPropertyName = "T3665")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_Uri>), TypeInfoPropertyName = "T3666")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_Uri>), TypeInfoPropertyName = "T3667")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_Uri>), TypeInfoPropertyName = "T3668")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_Uri, int>), TypeInfoPropertyName = "T3669")] +[JsonSerializable(typeof(V_validated_struct_NodaTime_Instant))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3670")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3671")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3672")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3673")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T3674")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T3675")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T3676")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T3677")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T3678")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T3679")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3680")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3681")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3682")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3683")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T3684")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3685")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3686")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3687")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3688")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T3689")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T3690")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T3691")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T3692")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_NodaTime_Instant[]>), TypeInfoPropertyName = "T3693")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_NodaTime_Instant[], int>), TypeInfoPropertyName = "T3694")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3695")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3696")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3697")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3698")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T3699")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3700")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3701")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3702")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3703")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T3704")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T3705")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T3706")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T3707")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_NodaTime_Instant, int>>), TypeInfoPropertyName = "T3708")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_NodaTime_Instant, int>, int>), TypeInfoPropertyName = "T3709")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3710")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3711")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3712")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3713")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T3714")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3715")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3716")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3717")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3718")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T3719")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3720")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3721")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3722")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3723")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T3724")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3725")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3726")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3727")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_NodaTime_Instant>>), TypeInfoPropertyName = "T3728")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_NodaTime_Instant>, int>), TypeInfoPropertyName = "T3729")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3730")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3731")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3732")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_NodaTime_Instant>), TypeInfoPropertyName = "T3733")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_NodaTime_Instant, int>), TypeInfoPropertyName = "T3734")] [JsonSerializable(typeof(V_validated_struct_own_record_class))] -[JsonSerializable(typeof(Cn_class_P<V_validated_struct_own_record_class>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3735")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3736")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3737")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3738")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_own_record_class, int>), TypeInfoPropertyName = "T3739")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T3740")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T3741")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T3742")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T3743")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_own_record_class?, int>), TypeInfoPropertyName = "T3744")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3745")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3746")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3747")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3748")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_own_record_class, int>), TypeInfoPropertyName = "T3749")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3750")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3751")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3752")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3753")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_own_record_class>, int>), TypeInfoPropertyName = "T3754")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_own_record_class[]>), TypeInfoPropertyName = "T3755")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_own_record_class[]>), TypeInfoPropertyName = "T3756")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_own_record_class[]>), TypeInfoPropertyName = "T3757")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_own_record_class[]>), TypeInfoPropertyName = "T3758")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_own_record_class[], int>), TypeInfoPropertyName = "T3759")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3760")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3761")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3762")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3763")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_own_record_class>, int>), TypeInfoPropertyName = "T3764")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3765")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3766")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3767")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3768")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_own_record_class>, int>), TypeInfoPropertyName = "T3769")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_struct_own_record_class, int>>), TypeInfoPropertyName = "T3770")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_struct_own_record_class, int>>), TypeInfoPropertyName = "T3771")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_struct_own_record_class, int>>), TypeInfoPropertyName = "T3772")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_struct_own_record_class, int>>), TypeInfoPropertyName = "T3773")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_struct_own_record_class, int>, int>), TypeInfoPropertyName = "T3774")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3775")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3776")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3777")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3778")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_own_record_class>, int>), TypeInfoPropertyName = "T3779")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3780")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3781")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3782")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3783")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_own_record_class, int>), TypeInfoPropertyName = "T3784")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3785")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3786")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3787")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3788")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_own_record_class, int>), TypeInfoPropertyName = "T3789")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3790")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3791")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3792")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_struct_own_record_class>>), TypeInfoPropertyName = "T3793")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_struct_own_record_class>, int>), TypeInfoPropertyName = "T3794")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3795")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3796")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3797")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_struct_own_record_class>), TypeInfoPropertyName = "T3798")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_struct_own_record_class, int>), TypeInfoPropertyName = "T3799")] [JsonSerializable(typeof(V_validated_class_string))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_string>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_string>), TypeInfoPropertyName = "T3800")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_string>), TypeInfoPropertyName = "T3801")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_string>), TypeInfoPropertyName = "T3802")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_string>), TypeInfoPropertyName = "T3803")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_string, int>), TypeInfoPropertyName = "T3804")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_string>), TypeInfoPropertyName = "T3805")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_string>), TypeInfoPropertyName = "T3806")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_string>), TypeInfoPropertyName = "T3807")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_string>), TypeInfoPropertyName = "T3808")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_string, int>), TypeInfoPropertyName = "T3809")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_string>>), TypeInfoPropertyName = "T3810")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_string>>), TypeInfoPropertyName = "T3811")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_string>>), TypeInfoPropertyName = "T3812")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_string>>), TypeInfoPropertyName = "T3813")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_string>, int>), TypeInfoPropertyName = "T3814")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_string[]>), TypeInfoPropertyName = "T3815")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_string[]>), TypeInfoPropertyName = "T3816")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_string[]>), TypeInfoPropertyName = "T3817")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_string[]>), TypeInfoPropertyName = "T3818")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_string[], int>), TypeInfoPropertyName = "T3819")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_string>>), TypeInfoPropertyName = "T3820")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_string>>), TypeInfoPropertyName = "T3821")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_string>>), TypeInfoPropertyName = "T3822")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_string>>), TypeInfoPropertyName = "T3823")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_string>, int>), TypeInfoPropertyName = "T3824")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_string>>), TypeInfoPropertyName = "T3825")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_string>>), TypeInfoPropertyName = "T3826")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_string>>), TypeInfoPropertyName = "T3827")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_string>>), TypeInfoPropertyName = "T3828")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_string>, int>), TypeInfoPropertyName = "T3829")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_string, int>>), TypeInfoPropertyName = "T3830")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_string, int>>), TypeInfoPropertyName = "T3831")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_string, int>>), TypeInfoPropertyName = "T3832")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_string, int>>), TypeInfoPropertyName = "T3833")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_string, int>, int>), TypeInfoPropertyName = "T3834")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_string>>), TypeInfoPropertyName = "T3835")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_string>>), TypeInfoPropertyName = "T3836")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_string>>), TypeInfoPropertyName = "T3837")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_string>>), TypeInfoPropertyName = "T3838")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_string>, int>), TypeInfoPropertyName = "T3839")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_string>), TypeInfoPropertyName = "T3840")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_string>), TypeInfoPropertyName = "T3841")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_string>), TypeInfoPropertyName = "T3842")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_string>), TypeInfoPropertyName = "T3843")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_string, int>), TypeInfoPropertyName = "T3844")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_string>), TypeInfoPropertyName = "T3845")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_string>), TypeInfoPropertyName = "T3846")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_string>), TypeInfoPropertyName = "T3847")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_string>), TypeInfoPropertyName = "T3848")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_string, int>), TypeInfoPropertyName = "T3849")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_string>>), TypeInfoPropertyName = "T3850")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_string>>), TypeInfoPropertyName = "T3851")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_string>>), TypeInfoPropertyName = "T3852")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_string>>), TypeInfoPropertyName = "T3853")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_string>, int>), TypeInfoPropertyName = "T3854")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_string>), TypeInfoPropertyName = "T3855")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_string>), TypeInfoPropertyName = "T3856")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_string>), TypeInfoPropertyName = "T3857")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_string>), TypeInfoPropertyName = "T3858")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_string, int>), TypeInfoPropertyName = "T3859")] [JsonSerializable(typeof(V_validated_class_Guid))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_Guid>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_Guid>), TypeInfoPropertyName = "T3860")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_Guid>), TypeInfoPropertyName = "T3861")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_Guid>), TypeInfoPropertyName = "T3862")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_Guid>), TypeInfoPropertyName = "T3863")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_Guid, int>), TypeInfoPropertyName = "T3864")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_Guid>), TypeInfoPropertyName = "T3865")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_Guid>), TypeInfoPropertyName = "T3866")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_Guid>), TypeInfoPropertyName = "T3867")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_Guid>), TypeInfoPropertyName = "T3868")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_Guid, int>), TypeInfoPropertyName = "T3869")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_Guid>>), TypeInfoPropertyName = "T3870")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_Guid>>), TypeInfoPropertyName = "T3871")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_Guid>>), TypeInfoPropertyName = "T3872")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_Guid>>), TypeInfoPropertyName = "T3873")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_Guid>, int>), TypeInfoPropertyName = "T3874")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_Guid[]>), TypeInfoPropertyName = "T3875")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_Guid[]>), TypeInfoPropertyName = "T3876")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_Guid[]>), TypeInfoPropertyName = "T3877")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_Guid[]>), TypeInfoPropertyName = "T3878")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_Guid[], int>), TypeInfoPropertyName = "T3879")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_Guid>>), TypeInfoPropertyName = "T3880")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_Guid>>), TypeInfoPropertyName = "T3881")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_Guid>>), TypeInfoPropertyName = "T3882")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_Guid>>), TypeInfoPropertyName = "T3883")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_Guid>, int>), TypeInfoPropertyName = "T3884")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_Guid>>), TypeInfoPropertyName = "T3885")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_Guid>>), TypeInfoPropertyName = "T3886")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_Guid>>), TypeInfoPropertyName = "T3887")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_Guid>>), TypeInfoPropertyName = "T3888")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_Guid>, int>), TypeInfoPropertyName = "T3889")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_Guid, int>>), TypeInfoPropertyName = "T3890")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_Guid, int>>), TypeInfoPropertyName = "T3891")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_Guid, int>>), TypeInfoPropertyName = "T3892")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_Guid, int>>), TypeInfoPropertyName = "T3893")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_Guid, int>, int>), TypeInfoPropertyName = "T3894")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_Guid>>), TypeInfoPropertyName = "T3895")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_Guid>>), TypeInfoPropertyName = "T3896")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_Guid>>), TypeInfoPropertyName = "T3897")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_Guid>>), TypeInfoPropertyName = "T3898")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_Guid>, int>), TypeInfoPropertyName = "T3899")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_Guid>), TypeInfoPropertyName = "T3900")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_Guid>), TypeInfoPropertyName = "T3901")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_Guid>), TypeInfoPropertyName = "T3902")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_Guid>), TypeInfoPropertyName = "T3903")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_Guid, int>), TypeInfoPropertyName = "T3904")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_Guid>), TypeInfoPropertyName = "T3905")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_Guid>), TypeInfoPropertyName = "T3906")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_Guid>), TypeInfoPropertyName = "T3907")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_Guid>), TypeInfoPropertyName = "T3908")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_Guid, int>), TypeInfoPropertyName = "T3909")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_Guid>>), TypeInfoPropertyName = "T3910")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_Guid>>), TypeInfoPropertyName = "T3911")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_Guid>>), TypeInfoPropertyName = "T3912")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_Guid>>), TypeInfoPropertyName = "T3913")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_Guid>, int>), TypeInfoPropertyName = "T3914")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_Guid>), TypeInfoPropertyName = "T3915")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_Guid>), TypeInfoPropertyName = "T3916")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_Guid>), TypeInfoPropertyName = "T3917")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_Guid>), TypeInfoPropertyName = "T3918")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_Guid, int>), TypeInfoPropertyName = "T3919")] [JsonSerializable(typeof(V_validated_class_int))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_int>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_int>), TypeInfoPropertyName = "T3920")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_int>), TypeInfoPropertyName = "T3921")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_int>), TypeInfoPropertyName = "T3922")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_int>), TypeInfoPropertyName = "T3923")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_int, int>), TypeInfoPropertyName = "T3924")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_int>), TypeInfoPropertyName = "T3925")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_int>), TypeInfoPropertyName = "T3926")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_int>), TypeInfoPropertyName = "T3927")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_int>), TypeInfoPropertyName = "T3928")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_int, int>), TypeInfoPropertyName = "T3929")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_int>>), TypeInfoPropertyName = "T3930")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_int>>), TypeInfoPropertyName = "T3931")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_int>>), TypeInfoPropertyName = "T3932")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_int>>), TypeInfoPropertyName = "T3933")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_int>, int>), TypeInfoPropertyName = "T3934")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_int[]>), TypeInfoPropertyName = "T3935")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_int[]>), TypeInfoPropertyName = "T3936")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_int[]>), TypeInfoPropertyName = "T3937")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_int[]>), TypeInfoPropertyName = "T3938")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_int[], int>), TypeInfoPropertyName = "T3939")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_int>>), TypeInfoPropertyName = "T3940")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_int>>), TypeInfoPropertyName = "T3941")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_int>>), TypeInfoPropertyName = "T3942")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_int>>), TypeInfoPropertyName = "T3943")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_int>, int>), TypeInfoPropertyName = "T3944")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_int>>), TypeInfoPropertyName = "T3945")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_int>>), TypeInfoPropertyName = "T3946")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_int>>), TypeInfoPropertyName = "T3947")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_int>>), TypeInfoPropertyName = "T3948")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_int>, int>), TypeInfoPropertyName = "T3949")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_int, int>>), TypeInfoPropertyName = "T3950")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_int, int>>), TypeInfoPropertyName = "T3951")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_int, int>>), TypeInfoPropertyName = "T3952")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_int, int>>), TypeInfoPropertyName = "T3953")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_int, int>, int>), TypeInfoPropertyName = "T3954")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_int>>), TypeInfoPropertyName = "T3955")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_int>>), TypeInfoPropertyName = "T3956")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_int>>), TypeInfoPropertyName = "T3957")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_int>>), TypeInfoPropertyName = "T3958")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_int>, int>), TypeInfoPropertyName = "T3959")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_int>), TypeInfoPropertyName = "T3960")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_int>), TypeInfoPropertyName = "T3961")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_int>), TypeInfoPropertyName = "T3962")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_int>), TypeInfoPropertyName = "T3963")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_int, int>), TypeInfoPropertyName = "T3964")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_int>), TypeInfoPropertyName = "T3965")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_int>), TypeInfoPropertyName = "T3966")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_int>), TypeInfoPropertyName = "T3967")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_int>), TypeInfoPropertyName = "T3968")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_int, int>), TypeInfoPropertyName = "T3969")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_int>>), TypeInfoPropertyName = "T3970")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_int>>), TypeInfoPropertyName = "T3971")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_int>>), TypeInfoPropertyName = "T3972")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_int>>), TypeInfoPropertyName = "T3973")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_int>, int>), TypeInfoPropertyName = "T3974")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_int>), TypeInfoPropertyName = "T3975")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_int>), TypeInfoPropertyName = "T3976")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_int>), TypeInfoPropertyName = "T3977")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_int>), TypeInfoPropertyName = "T3978")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_int, int>), TypeInfoPropertyName = "T3979")] [JsonSerializable(typeof(V_validated_class_long))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_long>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_long>), TypeInfoPropertyName = "T3980")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_long>), TypeInfoPropertyName = "T3981")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_long>), TypeInfoPropertyName = "T3982")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_long>), TypeInfoPropertyName = "T3983")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_long, int>), TypeInfoPropertyName = "T3984")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_long>), TypeInfoPropertyName = "T3985")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_long>), TypeInfoPropertyName = "T3986")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_long>), TypeInfoPropertyName = "T3987")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_long>), TypeInfoPropertyName = "T3988")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_long, int>), TypeInfoPropertyName = "T3989")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_long>>), TypeInfoPropertyName = "T3990")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_long>>), TypeInfoPropertyName = "T3991")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_long>>), TypeInfoPropertyName = "T3992")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_long>>), TypeInfoPropertyName = "T3993")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_long>, int>), TypeInfoPropertyName = "T3994")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_long[]>), TypeInfoPropertyName = "T3995")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_long[]>), TypeInfoPropertyName = "T3996")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_long[]>), TypeInfoPropertyName = "T3997")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_long[]>), TypeInfoPropertyName = "T3998")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_long[], int>), TypeInfoPropertyName = "T3999")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_long>>), TypeInfoPropertyName = "T4000")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_long>>), TypeInfoPropertyName = "T4001")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_long>>), TypeInfoPropertyName = "T4002")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_long>>), TypeInfoPropertyName = "T4003")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_long>, int>), TypeInfoPropertyName = "T4004")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_long>>), TypeInfoPropertyName = "T4005")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_long>>), TypeInfoPropertyName = "T4006")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_long>>), TypeInfoPropertyName = "T4007")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_long>>), TypeInfoPropertyName = "T4008")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_long>, int>), TypeInfoPropertyName = "T4009")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_long, int>>), TypeInfoPropertyName = "T4010")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_long, int>>), TypeInfoPropertyName = "T4011")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_long, int>>), TypeInfoPropertyName = "T4012")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_long, int>>), TypeInfoPropertyName = "T4013")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_long, int>, int>), TypeInfoPropertyName = "T4014")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_long>>), TypeInfoPropertyName = "T4015")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_long>>), TypeInfoPropertyName = "T4016")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_long>>), TypeInfoPropertyName = "T4017")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_long>>), TypeInfoPropertyName = "T4018")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_long>, int>), TypeInfoPropertyName = "T4019")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_long>), TypeInfoPropertyName = "T4020")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_long>), TypeInfoPropertyName = "T4021")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_long>), TypeInfoPropertyName = "T4022")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_long>), TypeInfoPropertyName = "T4023")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_long, int>), TypeInfoPropertyName = "T4024")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_long>), TypeInfoPropertyName = "T4025")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_long>), TypeInfoPropertyName = "T4026")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_long>), TypeInfoPropertyName = "T4027")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_long>), TypeInfoPropertyName = "T4028")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_long, int>), TypeInfoPropertyName = "T4029")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_long>>), TypeInfoPropertyName = "T4030")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_long>>), TypeInfoPropertyName = "T4031")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_long>>), TypeInfoPropertyName = "T4032")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_long>>), TypeInfoPropertyName = "T4033")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_long>, int>), TypeInfoPropertyName = "T4034")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_long>), TypeInfoPropertyName = "T4035")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_long>), TypeInfoPropertyName = "T4036")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_long>), TypeInfoPropertyName = "T4037")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_long>), TypeInfoPropertyName = "T4038")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_long, int>), TypeInfoPropertyName = "T4039")] [JsonSerializable(typeof(V_validated_class_byte))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_byte>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_byte>), TypeInfoPropertyName = "T4040")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_byte>), TypeInfoPropertyName = "T4041")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_byte>), TypeInfoPropertyName = "T4042")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_byte>), TypeInfoPropertyName = "T4043")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_byte, int>), TypeInfoPropertyName = "T4044")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_byte>), TypeInfoPropertyName = "T4045")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_byte>), TypeInfoPropertyName = "T4046")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_byte>), TypeInfoPropertyName = "T4047")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_byte>), TypeInfoPropertyName = "T4048")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_byte, int>), TypeInfoPropertyName = "T4049")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_byte>>), TypeInfoPropertyName = "T4050")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_byte>>), TypeInfoPropertyName = "T4051")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_byte>>), TypeInfoPropertyName = "T4052")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_byte>>), TypeInfoPropertyName = "T4053")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_byte>, int>), TypeInfoPropertyName = "T4054")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_byte[]>), TypeInfoPropertyName = "T4055")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_byte[]>), TypeInfoPropertyName = "T4056")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_byte[]>), TypeInfoPropertyName = "T4057")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_byte[]>), TypeInfoPropertyName = "T4058")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_byte[], int>), TypeInfoPropertyName = "T4059")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_byte>>), TypeInfoPropertyName = "T4060")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_byte>>), TypeInfoPropertyName = "T4061")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_byte>>), TypeInfoPropertyName = "T4062")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_byte>>), TypeInfoPropertyName = "T4063")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_byte>, int>), TypeInfoPropertyName = "T4064")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_byte>>), TypeInfoPropertyName = "T4065")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_byte>>), TypeInfoPropertyName = "T4066")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_byte>>), TypeInfoPropertyName = "T4067")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_byte>>), TypeInfoPropertyName = "T4068")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_byte>, int>), TypeInfoPropertyName = "T4069")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_byte, int>>), TypeInfoPropertyName = "T4070")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_byte, int>>), TypeInfoPropertyName = "T4071")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_byte, int>>), TypeInfoPropertyName = "T4072")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_byte, int>>), TypeInfoPropertyName = "T4073")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_byte, int>, int>), TypeInfoPropertyName = "T4074")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_byte>>), TypeInfoPropertyName = "T4075")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_byte>>), TypeInfoPropertyName = "T4076")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_byte>>), TypeInfoPropertyName = "T4077")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_byte>>), TypeInfoPropertyName = "T4078")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_byte>, int>), TypeInfoPropertyName = "T4079")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_byte>), TypeInfoPropertyName = "T4080")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_byte>), TypeInfoPropertyName = "T4081")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_byte>), TypeInfoPropertyName = "T4082")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_byte>), TypeInfoPropertyName = "T4083")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_byte, int>), TypeInfoPropertyName = "T4084")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_byte>), TypeInfoPropertyName = "T4085")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_byte>), TypeInfoPropertyName = "T4086")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_byte>), TypeInfoPropertyName = "T4087")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_byte>), TypeInfoPropertyName = "T4088")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_byte, int>), TypeInfoPropertyName = "T4089")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_byte>>), TypeInfoPropertyName = "T4090")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_byte>>), TypeInfoPropertyName = "T4091")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_byte>>), TypeInfoPropertyName = "T4092")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_byte>>), TypeInfoPropertyName = "T4093")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_byte>, int>), TypeInfoPropertyName = "T4094")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_byte>), TypeInfoPropertyName = "T4095")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_byte>), TypeInfoPropertyName = "T4096")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_byte>), TypeInfoPropertyName = "T4097")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_byte>), TypeInfoPropertyName = "T4098")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_byte, int>), TypeInfoPropertyName = "T4099")] [JsonSerializable(typeof(V_validated_class_ulong))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_ulong>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_ulong>), TypeInfoPropertyName = "T4100")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_ulong>), TypeInfoPropertyName = "T4101")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_ulong>), TypeInfoPropertyName = "T4102")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_ulong>), TypeInfoPropertyName = "T4103")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_ulong, int>), TypeInfoPropertyName = "T4104")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_ulong>), TypeInfoPropertyName = "T4105")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_ulong>), TypeInfoPropertyName = "T4106")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_ulong>), TypeInfoPropertyName = "T4107")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_ulong>), TypeInfoPropertyName = "T4108")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_ulong, int>), TypeInfoPropertyName = "T4109")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_ulong>>), TypeInfoPropertyName = "T4110")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_ulong>>), TypeInfoPropertyName = "T4111")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_ulong>>), TypeInfoPropertyName = "T4112")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_ulong>>), TypeInfoPropertyName = "T4113")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_ulong>, int>), TypeInfoPropertyName = "T4114")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_ulong[]>), TypeInfoPropertyName = "T4115")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_ulong[]>), TypeInfoPropertyName = "T4116")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_ulong[]>), TypeInfoPropertyName = "T4117")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_ulong[]>), TypeInfoPropertyName = "T4118")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_ulong[], int>), TypeInfoPropertyName = "T4119")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_ulong>>), TypeInfoPropertyName = "T4120")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_ulong>>), TypeInfoPropertyName = "T4121")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_ulong>>), TypeInfoPropertyName = "T4122")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_ulong>>), TypeInfoPropertyName = "T4123")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_ulong>, int>), TypeInfoPropertyName = "T4124")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_ulong>>), TypeInfoPropertyName = "T4125")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_ulong>>), TypeInfoPropertyName = "T4126")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_ulong>>), TypeInfoPropertyName = "T4127")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_ulong>>), TypeInfoPropertyName = "T4128")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_ulong>, int>), TypeInfoPropertyName = "T4129")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_ulong, int>>), TypeInfoPropertyName = "T4130")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_ulong, int>>), TypeInfoPropertyName = "T4131")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_ulong, int>>), TypeInfoPropertyName = "T4132")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_ulong, int>>), TypeInfoPropertyName = "T4133")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_ulong, int>, int>), TypeInfoPropertyName = "T4134")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_ulong>>), TypeInfoPropertyName = "T4135")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_ulong>>), TypeInfoPropertyName = "T4136")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_ulong>>), TypeInfoPropertyName = "T4137")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_ulong>>), TypeInfoPropertyName = "T4138")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_ulong>, int>), TypeInfoPropertyName = "T4139")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_ulong>), TypeInfoPropertyName = "T4140")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_ulong>), TypeInfoPropertyName = "T4141")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_ulong>), TypeInfoPropertyName = "T4142")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_ulong>), TypeInfoPropertyName = "T4143")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_ulong, int>), TypeInfoPropertyName = "T4144")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_ulong>), TypeInfoPropertyName = "T4145")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_ulong>), TypeInfoPropertyName = "T4146")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_ulong>), TypeInfoPropertyName = "T4147")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_ulong>), TypeInfoPropertyName = "T4148")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_ulong, int>), TypeInfoPropertyName = "T4149")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_ulong>>), TypeInfoPropertyName = "T4150")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_ulong>>), TypeInfoPropertyName = "T4151")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_ulong>>), TypeInfoPropertyName = "T4152")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_ulong>>), TypeInfoPropertyName = "T4153")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_ulong>, int>), TypeInfoPropertyName = "T4154")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_ulong>), TypeInfoPropertyName = "T4155")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_ulong>), TypeInfoPropertyName = "T4156")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_ulong>), TypeInfoPropertyName = "T4157")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_ulong>), TypeInfoPropertyName = "T4158")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_ulong, int>), TypeInfoPropertyName = "T4159")] [JsonSerializable(typeof(V_validated_class_decimal))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_decimal>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_decimal>), TypeInfoPropertyName = "T4160")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_decimal>), TypeInfoPropertyName = "T4161")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_decimal>), TypeInfoPropertyName = "T4162")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_decimal>), TypeInfoPropertyName = "T4163")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_decimal, int>), TypeInfoPropertyName = "T4164")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_decimal>), TypeInfoPropertyName = "T4165")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_decimal>), TypeInfoPropertyName = "T4166")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_decimal>), TypeInfoPropertyName = "T4167")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_decimal>), TypeInfoPropertyName = "T4168")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_decimal, int>), TypeInfoPropertyName = "T4169")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_decimal>>), TypeInfoPropertyName = "T4170")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_decimal>>), TypeInfoPropertyName = "T4171")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_decimal>>), TypeInfoPropertyName = "T4172")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_decimal>>), TypeInfoPropertyName = "T4173")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_decimal>, int>), TypeInfoPropertyName = "T4174")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_decimal[]>), TypeInfoPropertyName = "T4175")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_decimal[]>), TypeInfoPropertyName = "T4176")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_decimal[]>), TypeInfoPropertyName = "T4177")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_decimal[]>), TypeInfoPropertyName = "T4178")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_decimal[], int>), TypeInfoPropertyName = "T4179")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_decimal>>), TypeInfoPropertyName = "T4180")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_decimal>>), TypeInfoPropertyName = "T4181")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_decimal>>), TypeInfoPropertyName = "T4182")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_decimal>>), TypeInfoPropertyName = "T4183")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_decimal>, int>), TypeInfoPropertyName = "T4184")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_decimal>>), TypeInfoPropertyName = "T4185")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_decimal>>), TypeInfoPropertyName = "T4186")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_decimal>>), TypeInfoPropertyName = "T4187")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_decimal>>), TypeInfoPropertyName = "T4188")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_decimal>, int>), TypeInfoPropertyName = "T4189")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_decimal, int>>), TypeInfoPropertyName = "T4190")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_decimal, int>>), TypeInfoPropertyName = "T4191")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_decimal, int>>), TypeInfoPropertyName = "T4192")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_decimal, int>>), TypeInfoPropertyName = "T4193")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_decimal, int>, int>), TypeInfoPropertyName = "T4194")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_decimal>>), TypeInfoPropertyName = "T4195")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_decimal>>), TypeInfoPropertyName = "T4196")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_decimal>>), TypeInfoPropertyName = "T4197")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_decimal>>), TypeInfoPropertyName = "T4198")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_decimal>, int>), TypeInfoPropertyName = "T4199")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_decimal>), TypeInfoPropertyName = "T4200")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_decimal>), TypeInfoPropertyName = "T4201")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_decimal>), TypeInfoPropertyName = "T4202")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_decimal>), TypeInfoPropertyName = "T4203")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_decimal, int>), TypeInfoPropertyName = "T4204")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_decimal>), TypeInfoPropertyName = "T4205")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_decimal>), TypeInfoPropertyName = "T4206")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_decimal>), TypeInfoPropertyName = "T4207")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_decimal>), TypeInfoPropertyName = "T4208")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_decimal, int>), TypeInfoPropertyName = "T4209")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_decimal>>), TypeInfoPropertyName = "T4210")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_decimal>>), TypeInfoPropertyName = "T4211")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_decimal>>), TypeInfoPropertyName = "T4212")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_decimal>>), TypeInfoPropertyName = "T4213")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_decimal>, int>), TypeInfoPropertyName = "T4214")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_decimal>), TypeInfoPropertyName = "T4215")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_decimal>), TypeInfoPropertyName = "T4216")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_decimal>), TypeInfoPropertyName = "T4217")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_decimal>), TypeInfoPropertyName = "T4218")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_decimal, int>), TypeInfoPropertyName = "T4219")] [JsonSerializable(typeof(V_validated_class_double))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_double>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_double>), TypeInfoPropertyName = "T4220")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_double>), TypeInfoPropertyName = "T4221")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_double>), TypeInfoPropertyName = "T4222")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_double>), TypeInfoPropertyName = "T4223")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_double, int>), TypeInfoPropertyName = "T4224")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_double>), TypeInfoPropertyName = "T4225")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_double>), TypeInfoPropertyName = "T4226")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_double>), TypeInfoPropertyName = "T4227")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_double>), TypeInfoPropertyName = "T4228")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_double, int>), TypeInfoPropertyName = "T4229")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_double>>), TypeInfoPropertyName = "T4230")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_double>>), TypeInfoPropertyName = "T4231")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_double>>), TypeInfoPropertyName = "T4232")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_double>>), TypeInfoPropertyName = "T4233")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_double>, int>), TypeInfoPropertyName = "T4234")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_double[]>), TypeInfoPropertyName = "T4235")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_double[]>), TypeInfoPropertyName = "T4236")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_double[]>), TypeInfoPropertyName = "T4237")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_double[]>), TypeInfoPropertyName = "T4238")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_double[], int>), TypeInfoPropertyName = "T4239")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_double>>), TypeInfoPropertyName = "T4240")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_double>>), TypeInfoPropertyName = "T4241")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_double>>), TypeInfoPropertyName = "T4242")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_double>>), TypeInfoPropertyName = "T4243")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_double>, int>), TypeInfoPropertyName = "T4244")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_double>>), TypeInfoPropertyName = "T4245")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_double>>), TypeInfoPropertyName = "T4246")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_double>>), TypeInfoPropertyName = "T4247")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_double>>), TypeInfoPropertyName = "T4248")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_double>, int>), TypeInfoPropertyName = "T4249")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_double, int>>), TypeInfoPropertyName = "T4250")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_double, int>>), TypeInfoPropertyName = "T4251")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_double, int>>), TypeInfoPropertyName = "T4252")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_double, int>>), TypeInfoPropertyName = "T4253")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_double, int>, int>), TypeInfoPropertyName = "T4254")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_double>>), TypeInfoPropertyName = "T4255")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_double>>), TypeInfoPropertyName = "T4256")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_double>>), TypeInfoPropertyName = "T4257")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_double>>), TypeInfoPropertyName = "T4258")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_double>, int>), TypeInfoPropertyName = "T4259")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_double>), TypeInfoPropertyName = "T4260")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_double>), TypeInfoPropertyName = "T4261")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_double>), TypeInfoPropertyName = "T4262")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_double>), TypeInfoPropertyName = "T4263")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_double, int>), TypeInfoPropertyName = "T4264")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_double>), TypeInfoPropertyName = "T4265")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_double>), TypeInfoPropertyName = "T4266")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_double>), TypeInfoPropertyName = "T4267")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_double>), TypeInfoPropertyName = "T4268")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_double, int>), TypeInfoPropertyName = "T4269")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_double>>), TypeInfoPropertyName = "T4270")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_double>>), TypeInfoPropertyName = "T4271")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_double>>), TypeInfoPropertyName = "T4272")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_double>>), TypeInfoPropertyName = "T4273")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_double>, int>), TypeInfoPropertyName = "T4274")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_double>), TypeInfoPropertyName = "T4275")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_double>), TypeInfoPropertyName = "T4276")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_double>), TypeInfoPropertyName = "T4277")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_double>), TypeInfoPropertyName = "T4278")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_double, int>), TypeInfoPropertyName = "T4279")] [JsonSerializable(typeof(V_validated_class_bool))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_bool>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_bool>), TypeInfoPropertyName = "T4280")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_bool>), TypeInfoPropertyName = "T4281")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_bool>), TypeInfoPropertyName = "T4282")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_bool>), TypeInfoPropertyName = "T4283")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_bool, int>), TypeInfoPropertyName = "T4284")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_bool>), TypeInfoPropertyName = "T4285")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_bool>), TypeInfoPropertyName = "T4286")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_bool>), TypeInfoPropertyName = "T4287")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_bool>), TypeInfoPropertyName = "T4288")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_bool, int>), TypeInfoPropertyName = "T4289")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_bool>>), TypeInfoPropertyName = "T4290")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_bool>>), TypeInfoPropertyName = "T4291")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_bool>>), TypeInfoPropertyName = "T4292")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_bool>>), TypeInfoPropertyName = "T4293")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_bool>, int>), TypeInfoPropertyName = "T4294")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_bool[]>), TypeInfoPropertyName = "T4295")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_bool[]>), TypeInfoPropertyName = "T4296")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_bool[]>), TypeInfoPropertyName = "T4297")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_bool[]>), TypeInfoPropertyName = "T4298")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_bool[], int>), TypeInfoPropertyName = "T4299")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_bool>>), TypeInfoPropertyName = "T4300")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_bool>>), TypeInfoPropertyName = "T4301")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_bool>>), TypeInfoPropertyName = "T4302")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_bool>>), TypeInfoPropertyName = "T4303")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_bool>, int>), TypeInfoPropertyName = "T4304")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_bool>>), TypeInfoPropertyName = "T4305")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_bool>>), TypeInfoPropertyName = "T4306")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_bool>>), TypeInfoPropertyName = "T4307")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_bool>>), TypeInfoPropertyName = "T4308")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_bool>, int>), TypeInfoPropertyName = "T4309")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_bool, int>>), TypeInfoPropertyName = "T4310")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_bool, int>>), TypeInfoPropertyName = "T4311")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_bool, int>>), TypeInfoPropertyName = "T4312")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_bool, int>>), TypeInfoPropertyName = "T4313")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_bool, int>, int>), TypeInfoPropertyName = "T4314")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_bool>>), TypeInfoPropertyName = "T4315")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_bool>>), TypeInfoPropertyName = "T4316")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_bool>>), TypeInfoPropertyName = "T4317")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_bool>>), TypeInfoPropertyName = "T4318")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_bool>, int>), TypeInfoPropertyName = "T4319")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_bool>), TypeInfoPropertyName = "T4320")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_bool>), TypeInfoPropertyName = "T4321")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_bool>), TypeInfoPropertyName = "T4322")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_bool>), TypeInfoPropertyName = "T4323")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_bool, int>), TypeInfoPropertyName = "T4324")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_bool>), TypeInfoPropertyName = "T4325")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_bool>), TypeInfoPropertyName = "T4326")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_bool>), TypeInfoPropertyName = "T4327")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_bool>), TypeInfoPropertyName = "T4328")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_bool, int>), TypeInfoPropertyName = "T4329")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_bool>>), TypeInfoPropertyName = "T4330")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_bool>>), TypeInfoPropertyName = "T4331")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_bool>>), TypeInfoPropertyName = "T4332")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_bool>>), TypeInfoPropertyName = "T4333")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_bool>, int>), TypeInfoPropertyName = "T4334")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_bool>), TypeInfoPropertyName = "T4335")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_bool>), TypeInfoPropertyName = "T4336")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_bool>), TypeInfoPropertyName = "T4337")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_bool>), TypeInfoPropertyName = "T4338")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_bool, int>), TypeInfoPropertyName = "T4339")] [JsonSerializable(typeof(V_validated_class_char))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_char>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_char>), TypeInfoPropertyName = "T4340")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_char>), TypeInfoPropertyName = "T4341")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_char>), TypeInfoPropertyName = "T4342")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_char>), TypeInfoPropertyName = "T4343")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_char, int>), TypeInfoPropertyName = "T4344")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_char>), TypeInfoPropertyName = "T4345")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_char>), TypeInfoPropertyName = "T4346")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_char>), TypeInfoPropertyName = "T4347")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_char>), TypeInfoPropertyName = "T4348")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_char, int>), TypeInfoPropertyName = "T4349")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_char>>), TypeInfoPropertyName = "T4350")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_char>>), TypeInfoPropertyName = "T4351")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_char>>), TypeInfoPropertyName = "T4352")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_char>>), TypeInfoPropertyName = "T4353")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_char>, int>), TypeInfoPropertyName = "T4354")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_char[]>), TypeInfoPropertyName = "T4355")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_char[]>), TypeInfoPropertyName = "T4356")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_char[]>), TypeInfoPropertyName = "T4357")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_char[]>), TypeInfoPropertyName = "T4358")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_char[], int>), TypeInfoPropertyName = "T4359")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_char>>), TypeInfoPropertyName = "T4360")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_char>>), TypeInfoPropertyName = "T4361")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_char>>), TypeInfoPropertyName = "T4362")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_char>>), TypeInfoPropertyName = "T4363")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_char>, int>), TypeInfoPropertyName = "T4364")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_char>>), TypeInfoPropertyName = "T4365")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_char>>), TypeInfoPropertyName = "T4366")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_char>>), TypeInfoPropertyName = "T4367")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_char>>), TypeInfoPropertyName = "T4368")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_char>, int>), TypeInfoPropertyName = "T4369")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_char, int>>), TypeInfoPropertyName = "T4370")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_char, int>>), TypeInfoPropertyName = "T4371")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_char, int>>), TypeInfoPropertyName = "T4372")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_char, int>>), TypeInfoPropertyName = "T4373")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_char, int>, int>), TypeInfoPropertyName = "T4374")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_char>>), TypeInfoPropertyName = "T4375")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_char>>), TypeInfoPropertyName = "T4376")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_char>>), TypeInfoPropertyName = "T4377")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_char>>), TypeInfoPropertyName = "T4378")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_char>, int>), TypeInfoPropertyName = "T4379")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_char>), TypeInfoPropertyName = "T4380")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_char>), TypeInfoPropertyName = "T4381")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_char>), TypeInfoPropertyName = "T4382")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_char>), TypeInfoPropertyName = "T4383")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_char, int>), TypeInfoPropertyName = "T4384")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_char>), TypeInfoPropertyName = "T4385")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_char>), TypeInfoPropertyName = "T4386")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_char>), TypeInfoPropertyName = "T4387")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_char>), TypeInfoPropertyName = "T4388")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_char, int>), TypeInfoPropertyName = "T4389")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_char>>), TypeInfoPropertyName = "T4390")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_char>>), TypeInfoPropertyName = "T4391")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_char>>), TypeInfoPropertyName = "T4392")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_char>>), TypeInfoPropertyName = "T4393")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_char>, int>), TypeInfoPropertyName = "T4394")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_char>), TypeInfoPropertyName = "T4395")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_char>), TypeInfoPropertyName = "T4396")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_char>), TypeInfoPropertyName = "T4397")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_char>), TypeInfoPropertyName = "T4398")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_char, int>), TypeInfoPropertyName = "T4399")] [JsonSerializable(typeof(V_validated_class_DateTime))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTime>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTime>), TypeInfoPropertyName = "T4400")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateTime>), TypeInfoPropertyName = "T4401")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateTime>), TypeInfoPropertyName = "T4402")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateTime>), TypeInfoPropertyName = "T4403")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateTime, int>), TypeInfoPropertyName = "T4404")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_DateTime>), TypeInfoPropertyName = "T4405")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_DateTime>), TypeInfoPropertyName = "T4406")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_DateTime>), TypeInfoPropertyName = "T4407")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_DateTime>), TypeInfoPropertyName = "T4408")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_DateTime, int>), TypeInfoPropertyName = "T4409")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4410")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4411")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4412")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4413")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_DateTime>, int>), TypeInfoPropertyName = "T4414")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTime[]>), TypeInfoPropertyName = "T4415")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateTime[]>), TypeInfoPropertyName = "T4416")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateTime[]>), TypeInfoPropertyName = "T4417")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateTime[]>), TypeInfoPropertyName = "T4418")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateTime[], int>), TypeInfoPropertyName = "T4419")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4420")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4421")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4422")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4423")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_DateTime>, int>), TypeInfoPropertyName = "T4424")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4425")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4426")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4427")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4428")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_DateTime>, int>), TypeInfoPropertyName = "T4429")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_DateTime, int>>), TypeInfoPropertyName = "T4430")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_DateTime, int>>), TypeInfoPropertyName = "T4431")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_DateTime, int>>), TypeInfoPropertyName = "T4432")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_DateTime, int>>), TypeInfoPropertyName = "T4433")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_DateTime, int>, int>), TypeInfoPropertyName = "T4434")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_DateTime>>), TypeInfoPropertyName = "T4435")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_DateTime>>), TypeInfoPropertyName = "T4436")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_DateTime>>), TypeInfoPropertyName = "T4437")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_DateTime>>), TypeInfoPropertyName = "T4438")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_DateTime>, int>), TypeInfoPropertyName = "T4439")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_DateTime>), TypeInfoPropertyName = "T4440")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_DateTime>), TypeInfoPropertyName = "T4441")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_DateTime>), TypeInfoPropertyName = "T4442")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_DateTime>), TypeInfoPropertyName = "T4443")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_DateTime, int>), TypeInfoPropertyName = "T4444")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_DateTime>), TypeInfoPropertyName = "T4445")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_DateTime>), TypeInfoPropertyName = "T4446")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_DateTime>), TypeInfoPropertyName = "T4447")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_DateTime>), TypeInfoPropertyName = "T4448")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_DateTime, int>), TypeInfoPropertyName = "T4449")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4450")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4451")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4452")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_DateTime>>), TypeInfoPropertyName = "T4453")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_DateTime>, int>), TypeInfoPropertyName = "T4454")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_DateTime>), TypeInfoPropertyName = "T4455")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_DateTime>), TypeInfoPropertyName = "T4456")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_DateTime>), TypeInfoPropertyName = "T4457")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_DateTime>), TypeInfoPropertyName = "T4458")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_DateTime, int>), TypeInfoPropertyName = "T4459")] [JsonSerializable(typeof(V_validated_class_DateTimeOffset))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTimeOffset>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4460")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4461")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4462")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4463")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateTimeOffset, int>), TypeInfoPropertyName = "T4464")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4465")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4466")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4467")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4468")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_DateTimeOffset, int>), TypeInfoPropertyName = "T4469")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4470")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4471")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4472")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4473")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T4474")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateTimeOffset[]>), TypeInfoPropertyName = "T4475")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateTimeOffset[]>), TypeInfoPropertyName = "T4476")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateTimeOffset[]>), TypeInfoPropertyName = "T4477")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateTimeOffset[]>), TypeInfoPropertyName = "T4478")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateTimeOffset[], int>), TypeInfoPropertyName = "T4479")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4480")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4481")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4482")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4483")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T4484")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4485")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4486")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4487")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4488")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T4489")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T4490")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T4491")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T4492")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_DateTimeOffset, int>>), TypeInfoPropertyName = "T4493")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_DateTimeOffset, int>, int>), TypeInfoPropertyName = "T4494")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4495")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4496")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4497")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4498")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T4499")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4500")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4501")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4502")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4503")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_DateTimeOffset, int>), TypeInfoPropertyName = "T4504")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4505")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4506")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4507")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4508")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_DateTimeOffset, int>), TypeInfoPropertyName = "T4509")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4510")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4511")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4512")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_DateTimeOffset>>), TypeInfoPropertyName = "T4513")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_DateTimeOffset>, int>), TypeInfoPropertyName = "T4514")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4515")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4516")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4517")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_DateTimeOffset>), TypeInfoPropertyName = "T4518")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_DateTimeOffset, int>), TypeInfoPropertyName = "T4519")] [JsonSerializable(typeof(V_validated_class_DateOnly))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4520")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4521")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4522")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4523")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateOnly, int>), TypeInfoPropertyName = "T4524")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4525")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4526")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4527")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4528")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_DateOnly, int>), TypeInfoPropertyName = "T4529")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4530")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4531")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4532")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4533")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_DateOnly>, int>), TypeInfoPropertyName = "T4534")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_DateOnly[]>), TypeInfoPropertyName = "T4535")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_DateOnly[]>), TypeInfoPropertyName = "T4536")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_DateOnly[]>), TypeInfoPropertyName = "T4537")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_DateOnly[]>), TypeInfoPropertyName = "T4538")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_DateOnly[], int>), TypeInfoPropertyName = "T4539")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4540")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4541")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4542")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4543")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_DateOnly>, int>), TypeInfoPropertyName = "T4544")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4545")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4546")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4547")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4548")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_DateOnly>, int>), TypeInfoPropertyName = "T4549")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_DateOnly, int>>), TypeInfoPropertyName = "T4550")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_DateOnly, int>>), TypeInfoPropertyName = "T4551")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_DateOnly, int>>), TypeInfoPropertyName = "T4552")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_DateOnly, int>>), TypeInfoPropertyName = "T4553")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_DateOnly, int>, int>), TypeInfoPropertyName = "T4554")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4555")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4556")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4557")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4558")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_DateOnly>, int>), TypeInfoPropertyName = "T4559")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4560")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4561")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4562")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4563")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_DateOnly, int>), TypeInfoPropertyName = "T4564")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4565")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4566")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4567")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_DateOnly>), TypeInfoPropertyName = "T4568")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_DateOnly, int>), TypeInfoPropertyName = "T4569")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4570")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4571")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4572")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_DateOnly>>), TypeInfoPropertyName = "T4573")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_DateOnly>, int>), TypeInfoPropertyName = "T4574")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_DateOnly>), TypeInfoPropertyName = "T4575")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_DateOnly>), TypeInfoPropertyName = "T4576")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_DateOnly>), TypeInfoPropertyName = "T4577")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_DateOnly>), TypeInfoPropertyName = "T4578")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_DateOnly, int>), TypeInfoPropertyName = "T4579")] [JsonSerializable(typeof(V_validated_class_TimeOnly))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeOnly>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4580")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4581")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4582")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4583")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_TimeOnly, int>), TypeInfoPropertyName = "T4584")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4585")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4586")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4587")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4588")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_TimeOnly, int>), TypeInfoPropertyName = "T4589")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4590")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4591")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4592")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4593")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_TimeOnly>, int>), TypeInfoPropertyName = "T4594")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeOnly[]>), TypeInfoPropertyName = "T4595")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_TimeOnly[]>), TypeInfoPropertyName = "T4596")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_TimeOnly[]>), TypeInfoPropertyName = "T4597")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_TimeOnly[]>), TypeInfoPropertyName = "T4598")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_TimeOnly[], int>), TypeInfoPropertyName = "T4599")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4600")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4601")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4602")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4603")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_TimeOnly>, int>), TypeInfoPropertyName = "T4604")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4605")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4606")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4607")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4608")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_TimeOnly>, int>), TypeInfoPropertyName = "T4609")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_TimeOnly, int>>), TypeInfoPropertyName = "T4610")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_TimeOnly, int>>), TypeInfoPropertyName = "T4611")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_TimeOnly, int>>), TypeInfoPropertyName = "T4612")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_TimeOnly, int>>), TypeInfoPropertyName = "T4613")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_TimeOnly, int>, int>), TypeInfoPropertyName = "T4614")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4615")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4616")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4617")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4618")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_TimeOnly>, int>), TypeInfoPropertyName = "T4619")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4620")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4621")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4622")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4623")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_TimeOnly, int>), TypeInfoPropertyName = "T4624")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4625")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4626")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4627")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4628")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_TimeOnly, int>), TypeInfoPropertyName = "T4629")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4630")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4631")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4632")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_TimeOnly>>), TypeInfoPropertyName = "T4633")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_TimeOnly>, int>), TypeInfoPropertyName = "T4634")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4635")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4636")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4637")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_TimeOnly>), TypeInfoPropertyName = "T4638")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_TimeOnly, int>), TypeInfoPropertyName = "T4639")] [JsonSerializable(typeof(V_validated_class_TimeSpan))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeSpan>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4640")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4641")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4642")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4643")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_TimeSpan, int>), TypeInfoPropertyName = "T4644")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4645")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4646")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4647")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4648")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_TimeSpan, int>), TypeInfoPropertyName = "T4649")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4650")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4651")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4652")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4653")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_TimeSpan>, int>), TypeInfoPropertyName = "T4654")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_TimeSpan[]>), TypeInfoPropertyName = "T4655")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_TimeSpan[]>), TypeInfoPropertyName = "T4656")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_TimeSpan[]>), TypeInfoPropertyName = "T4657")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_TimeSpan[]>), TypeInfoPropertyName = "T4658")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_TimeSpan[], int>), TypeInfoPropertyName = "T4659")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4660")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4661")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4662")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4663")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_TimeSpan>, int>), TypeInfoPropertyName = "T4664")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4665")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4666")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4667")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4668")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_TimeSpan>, int>), TypeInfoPropertyName = "T4669")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_TimeSpan, int>>), TypeInfoPropertyName = "T4670")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_TimeSpan, int>>), TypeInfoPropertyName = "T4671")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_TimeSpan, int>>), TypeInfoPropertyName = "T4672")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_TimeSpan, int>>), TypeInfoPropertyName = "T4673")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_TimeSpan, int>, int>), TypeInfoPropertyName = "T4674")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4675")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4676")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4677")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4678")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_TimeSpan>, int>), TypeInfoPropertyName = "T4679")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4680")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4681")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4682")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4683")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_TimeSpan, int>), TypeInfoPropertyName = "T4684")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4685")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4686")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4687")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4688")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_TimeSpan, int>), TypeInfoPropertyName = "T4689")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4690")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4691")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4692")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_TimeSpan>>), TypeInfoPropertyName = "T4693")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_TimeSpan>, int>), TypeInfoPropertyName = "T4694")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4695")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4696")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4697")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_TimeSpan>), TypeInfoPropertyName = "T4698")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_TimeSpan, int>), TypeInfoPropertyName = "T4699")] [JsonSerializable(typeof(V_validated_class_enum))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_enum>), TypeInfoPropertyName = "T4700")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_enum>), TypeInfoPropertyName = "T4701")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_enum>), TypeInfoPropertyName = "T4702")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_enum>), TypeInfoPropertyName = "T4703")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_enum, int>), TypeInfoPropertyName = "T4704")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_enum>), TypeInfoPropertyName = "T4705")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_enum>), TypeInfoPropertyName = "T4706")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_enum>), TypeInfoPropertyName = "T4707")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_enum>), TypeInfoPropertyName = "T4708")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_enum, int>), TypeInfoPropertyName = "T4709")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_enum>>), TypeInfoPropertyName = "T4710")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_enum>>), TypeInfoPropertyName = "T4711")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_enum>>), TypeInfoPropertyName = "T4712")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_enum>>), TypeInfoPropertyName = "T4713")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_enum>, int>), TypeInfoPropertyName = "T4714")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_enum[]>), TypeInfoPropertyName = "T4715")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_enum[]>), TypeInfoPropertyName = "T4716")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_enum[]>), TypeInfoPropertyName = "T4717")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_enum[]>), TypeInfoPropertyName = "T4718")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_enum[], int>), TypeInfoPropertyName = "T4719")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_enum>>), TypeInfoPropertyName = "T4720")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_enum>>), TypeInfoPropertyName = "T4721")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_enum>>), TypeInfoPropertyName = "T4722")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_enum>>), TypeInfoPropertyName = "T4723")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_enum>, int>), TypeInfoPropertyName = "T4724")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_enum>>), TypeInfoPropertyName = "T4725")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_enum>>), TypeInfoPropertyName = "T4726")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_enum>>), TypeInfoPropertyName = "T4727")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_enum>>), TypeInfoPropertyName = "T4728")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_enum>, int>), TypeInfoPropertyName = "T4729")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_enum, int>>), TypeInfoPropertyName = "T4730")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_enum, int>>), TypeInfoPropertyName = "T4731")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_enum, int>>), TypeInfoPropertyName = "T4732")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_enum, int>>), TypeInfoPropertyName = "T4733")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_enum, int>, int>), TypeInfoPropertyName = "T4734")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_enum>>), TypeInfoPropertyName = "T4735")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_enum>>), TypeInfoPropertyName = "T4736")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_enum>>), TypeInfoPropertyName = "T4737")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_enum>>), TypeInfoPropertyName = "T4738")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_enum>, int>), TypeInfoPropertyName = "T4739")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_enum>), TypeInfoPropertyName = "T4740")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_enum>), TypeInfoPropertyName = "T4741")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_enum>), TypeInfoPropertyName = "T4742")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_enum>), TypeInfoPropertyName = "T4743")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_enum, int>), TypeInfoPropertyName = "T4744")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_enum>), TypeInfoPropertyName = "T4745")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_enum>), TypeInfoPropertyName = "T4746")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_enum>), TypeInfoPropertyName = "T4747")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_enum>), TypeInfoPropertyName = "T4748")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_enum, int>), TypeInfoPropertyName = "T4749")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_enum>>), TypeInfoPropertyName = "T4750")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_enum>>), TypeInfoPropertyName = "T4751")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_enum>>), TypeInfoPropertyName = "T4752")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_enum>>), TypeInfoPropertyName = "T4753")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_enum>, int>), TypeInfoPropertyName = "T4754")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_enum>), TypeInfoPropertyName = "T4755")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_enum>), TypeInfoPropertyName = "T4756")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_enum>), TypeInfoPropertyName = "T4757")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_enum>), TypeInfoPropertyName = "T4758")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_enum, int>), TypeInfoPropertyName = "T4759")] [JsonSerializable(typeof(V_validated_class_flags_enum))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_flags_enum>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4760")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4761")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4762")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4763")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_flags_enum, int>), TypeInfoPropertyName = "T4764")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4765")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4766")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4767")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4768")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_flags_enum, int>), TypeInfoPropertyName = "T4769")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4770")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4771")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4772")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4773")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_flags_enum>, int>), TypeInfoPropertyName = "T4774")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_flags_enum[]>), TypeInfoPropertyName = "T4775")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_flags_enum[]>), TypeInfoPropertyName = "T4776")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_flags_enum[]>), TypeInfoPropertyName = "T4777")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_flags_enum[]>), TypeInfoPropertyName = "T4778")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_flags_enum[], int>), TypeInfoPropertyName = "T4779")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4780")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4781")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4782")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4783")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_flags_enum>, int>), TypeInfoPropertyName = "T4784")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4785")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4786")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4787")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4788")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_flags_enum>, int>), TypeInfoPropertyName = "T4789")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_flags_enum, int>>), TypeInfoPropertyName = "T4790")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_flags_enum, int>>), TypeInfoPropertyName = "T4791")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_flags_enum, int>>), TypeInfoPropertyName = "T4792")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_flags_enum, int>>), TypeInfoPropertyName = "T4793")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_flags_enum, int>, int>), TypeInfoPropertyName = "T4794")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4795")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4796")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4797")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4798")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_flags_enum>, int>), TypeInfoPropertyName = "T4799")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4800")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4801")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4802")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4803")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_flags_enum, int>), TypeInfoPropertyName = "T4804")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4805")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4806")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4807")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_flags_enum>), TypeInfoPropertyName = "T4808")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_flags_enum, int>), TypeInfoPropertyName = "T4809")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4810")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4811")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4812")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_flags_enum>>), TypeInfoPropertyName = "T4813")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_flags_enum>, int>), TypeInfoPropertyName = "T4814")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_flags_enum>), TypeInfoPropertyName = "T4815")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_flags_enum>), TypeInfoPropertyName = "T4816")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_flags_enum>), TypeInfoPropertyName = "T4817")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_flags_enum>), TypeInfoPropertyName = "T4818")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_flags_enum, int>), TypeInfoPropertyName = "T4819")] [JsonSerializable(typeof(V_validated_class_Uri))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_Uri>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_Uri>), TypeInfoPropertyName = "T4820")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_Uri>), TypeInfoPropertyName = "T4821")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_Uri>), TypeInfoPropertyName = "T4822")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_Uri>), TypeInfoPropertyName = "T4823")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_Uri, int>), TypeInfoPropertyName = "T4824")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_Uri>), TypeInfoPropertyName = "T4825")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_Uri>), TypeInfoPropertyName = "T4826")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_Uri>), TypeInfoPropertyName = "T4827")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_Uri>), TypeInfoPropertyName = "T4828")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_Uri, int>), TypeInfoPropertyName = "T4829")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_Uri>>), TypeInfoPropertyName = "T4830")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_Uri>>), TypeInfoPropertyName = "T4831")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_Uri>>), TypeInfoPropertyName = "T4832")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_Uri>>), TypeInfoPropertyName = "T4833")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_Uri>, int>), TypeInfoPropertyName = "T4834")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_Uri[]>), TypeInfoPropertyName = "T4835")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_Uri[]>), TypeInfoPropertyName = "T4836")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_Uri[]>), TypeInfoPropertyName = "T4837")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_Uri[]>), TypeInfoPropertyName = "T4838")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_Uri[], int>), TypeInfoPropertyName = "T4839")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_Uri>>), TypeInfoPropertyName = "T4840")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_Uri>>), TypeInfoPropertyName = "T4841")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_Uri>>), TypeInfoPropertyName = "T4842")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_Uri>>), TypeInfoPropertyName = "T4843")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_Uri>, int>), TypeInfoPropertyName = "T4844")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_Uri>>), TypeInfoPropertyName = "T4845")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_Uri>>), TypeInfoPropertyName = "T4846")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_Uri>>), TypeInfoPropertyName = "T4847")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_Uri>>), TypeInfoPropertyName = "T4848")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_Uri>, int>), TypeInfoPropertyName = "T4849")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_Uri, int>>), TypeInfoPropertyName = "T4850")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_Uri, int>>), TypeInfoPropertyName = "T4851")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_Uri, int>>), TypeInfoPropertyName = "T4852")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_Uri, int>>), TypeInfoPropertyName = "T4853")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_Uri, int>, int>), TypeInfoPropertyName = "T4854")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_Uri>>), TypeInfoPropertyName = "T4855")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_Uri>>), TypeInfoPropertyName = "T4856")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_Uri>>), TypeInfoPropertyName = "T4857")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_Uri>>), TypeInfoPropertyName = "T4858")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_Uri>, int>), TypeInfoPropertyName = "T4859")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_Uri>), TypeInfoPropertyName = "T4860")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_Uri>), TypeInfoPropertyName = "T4861")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_Uri>), TypeInfoPropertyName = "T4862")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_Uri>), TypeInfoPropertyName = "T4863")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_Uri, int>), TypeInfoPropertyName = "T4864")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_Uri>), TypeInfoPropertyName = "T4865")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_Uri>), TypeInfoPropertyName = "T4866")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_Uri>), TypeInfoPropertyName = "T4867")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_Uri>), TypeInfoPropertyName = "T4868")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_Uri, int>), TypeInfoPropertyName = "T4869")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_Uri>>), TypeInfoPropertyName = "T4870")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_Uri>>), TypeInfoPropertyName = "T4871")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_Uri>>), TypeInfoPropertyName = "T4872")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_Uri>>), TypeInfoPropertyName = "T4873")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_Uri>, int>), TypeInfoPropertyName = "T4874")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_Uri>), TypeInfoPropertyName = "T4875")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_Uri>), TypeInfoPropertyName = "T4876")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_Uri>), TypeInfoPropertyName = "T4877")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_Uri>), TypeInfoPropertyName = "T4878")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_Uri, int>), TypeInfoPropertyName = "T4879")] +[JsonSerializable(typeof(V_validated_class_NodaTime_Instant))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4880")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4881")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4882")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4883")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T4884")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4885")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4886")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4887")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4888")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T4889")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4890")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4891")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4892")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4893")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T4894")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T4895")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T4896")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T4897")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_NodaTime_Instant[]>), TypeInfoPropertyName = "T4898")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_NodaTime_Instant[], int>), TypeInfoPropertyName = "T4899")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4900")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4901")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4902")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4903")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T4904")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4905")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4906")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4907")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4908")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T4909")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T4910")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T4911")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T4912")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_NodaTime_Instant, int>>), TypeInfoPropertyName = "T4913")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_NodaTime_Instant, int>, int>), TypeInfoPropertyName = "T4914")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4915")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4916")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4917")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4918")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T4919")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4920")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4921")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4922")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4923")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T4924")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4925")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4926")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4927")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4928")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T4929")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4930")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4931")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4932")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_NodaTime_Instant>>), TypeInfoPropertyName = "T4933")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_NodaTime_Instant>, int>), TypeInfoPropertyName = "T4934")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4935")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4936")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4937")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_NodaTime_Instant>), TypeInfoPropertyName = "T4938")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_NodaTime_Instant, int>), TypeInfoPropertyName = "T4939")] [JsonSerializable(typeof(V_validated_class_own_record_class))] -[JsonSerializable(typeof(Cn_class_P<V_validated_class_own_record_class>))] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4940")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4941")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4942")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4943")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_own_record_class, int>), TypeInfoPropertyName = "T4944")] +[JsonSerializable(typeof(Cn_class_F<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4945")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4946")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4947")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4948")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_class_own_record_class, int>), TypeInfoPropertyName = "T4949")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4950")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4951")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4952")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4953")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_class_own_record_class>, int>), TypeInfoPropertyName = "T4954")] +[JsonSerializable(typeof(Cn_class_P<V_validated_class_own_record_class[]>), TypeInfoPropertyName = "T4955")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_class_own_record_class[]>), TypeInfoPropertyName = "T4956")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_class_own_record_class[]>), TypeInfoPropertyName = "T4957")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_class_own_record_class[]>), TypeInfoPropertyName = "T4958")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_class_own_record_class[], int>), TypeInfoPropertyName = "T4959")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4960")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4961")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4962")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4963")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_class_own_record_class>, int>), TypeInfoPropertyName = "T4964")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4965")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4966")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4967")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4968")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_class_own_record_class>, int>), TypeInfoPropertyName = "T4969")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<V_validated_class_own_record_class, int>>), TypeInfoPropertyName = "T4970")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<V_validated_class_own_record_class, int>>), TypeInfoPropertyName = "T4971")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<V_validated_class_own_record_class, int>>), TypeInfoPropertyName = "T4972")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<V_validated_class_own_record_class, int>>), TypeInfoPropertyName = "T4973")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<V_validated_class_own_record_class, int>, int>), TypeInfoPropertyName = "T4974")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4975")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4976")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4977")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4978")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_class_own_record_class>, int>), TypeInfoPropertyName = "T4979")] +[JsonSerializable(typeof(Cn_class_C<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4980")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4981")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4982")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4983")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_class_own_record_class, int>), TypeInfoPropertyName = "T4984")] +[JsonSerializable(typeof(Cn_class_R<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4985")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4986")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4987")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_class_own_record_class>), TypeInfoPropertyName = "T4988")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_class_own_record_class, int>), TypeInfoPropertyName = "T4989")] +[JsonSerializable(typeof(Cn_class_P<Patch<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4990")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4991")] +[JsonSerializable(typeof(Cn_struct_P<Patch<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4992")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<V_validated_class_own_record_class>>), TypeInfoPropertyName = "T4993")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<V_validated_class_own_record_class>, int>), TypeInfoPropertyName = "T4994")] +[JsonSerializable(typeof(Cn_class_P<PB_V_validated_class_own_record_class>), TypeInfoPropertyName = "T4995")] +[JsonSerializable(typeof(Cn_record_class_P<PB_V_validated_class_own_record_class>), TypeInfoPropertyName = "T4996")] +[JsonSerializable(typeof(Cn_struct_P<PB_V_validated_class_own_record_class>), TypeInfoPropertyName = "T4997")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_V_validated_class_own_record_class>), TypeInfoPropertyName = "T4998")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_V_validated_class_own_record_class, int>), TypeInfoPropertyName = "T4999")] +[JsonSerializable(typeof(V_record_struct_string?), TypeInfoPropertyName = "T5000")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_string?>), TypeInfoPropertyName = "T5001")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_string?>), TypeInfoPropertyName = "T5002")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_string?>), TypeInfoPropertyName = "T5003")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_string?>), TypeInfoPropertyName = "T5004")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_string?, int>), TypeInfoPropertyName = "T5005")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_string?>>), TypeInfoPropertyName = "T5006")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_string?>>), TypeInfoPropertyName = "T5007")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_string?>>), TypeInfoPropertyName = "T5008")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_string?>>), TypeInfoPropertyName = "T5009")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_string?>, int>), TypeInfoPropertyName = "T5010")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_string?[]>), TypeInfoPropertyName = "T5011")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_string?[]>), TypeInfoPropertyName = "T5012")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_string?[]>), TypeInfoPropertyName = "T5013")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_string?[]>), TypeInfoPropertyName = "T5014")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_string?[], int>), TypeInfoPropertyName = "T5015")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_string?>>), TypeInfoPropertyName = "T5016")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_string?>>), TypeInfoPropertyName = "T5017")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_string?>>), TypeInfoPropertyName = "T5018")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_string?>>), TypeInfoPropertyName = "T5019")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_string?>, int>), TypeInfoPropertyName = "T5020")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_string?>>), TypeInfoPropertyName = "T5021")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_string?>>), TypeInfoPropertyName = "T5022")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_string?>>), TypeInfoPropertyName = "T5023")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_string?>>), TypeInfoPropertyName = "T5024")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_string?>, int>), TypeInfoPropertyName = "T5025")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_string?>>), TypeInfoPropertyName = "T5026")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_string?>>), TypeInfoPropertyName = "T5027")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_string?>>), TypeInfoPropertyName = "T5028")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_string?>>), TypeInfoPropertyName = "T5029")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_string?>, int>), TypeInfoPropertyName = "T5030")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_string?>), TypeInfoPropertyName = "T5031")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_string?>), TypeInfoPropertyName = "T5032")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_string?>), TypeInfoPropertyName = "T5033")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_string?>), TypeInfoPropertyName = "T5034")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_string?, int>), TypeInfoPropertyName = "T5035")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_string?>), TypeInfoPropertyName = "T5036")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_string?>), TypeInfoPropertyName = "T5037")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_string?>), TypeInfoPropertyName = "T5038")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_string?>), TypeInfoPropertyName = "T5039")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_string?, int>), TypeInfoPropertyName = "T5040")] +[JsonSerializable(typeof(V_record_struct_Guid?), TypeInfoPropertyName = "T5041")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_Guid?>), TypeInfoPropertyName = "T5042")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_Guid?>), TypeInfoPropertyName = "T5043")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_Guid?>), TypeInfoPropertyName = "T5044")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_Guid?>), TypeInfoPropertyName = "T5045")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_Guid?, int>), TypeInfoPropertyName = "T5046")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5047")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5048")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5049")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5050")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_Guid?>, int>), TypeInfoPropertyName = "T5051")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Guid?[]>), TypeInfoPropertyName = "T5052")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Guid?[]>), TypeInfoPropertyName = "T5053")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Guid?[]>), TypeInfoPropertyName = "T5054")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Guid?[]>), TypeInfoPropertyName = "T5055")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Guid?[], int>), TypeInfoPropertyName = "T5056")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5057")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5058")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5059")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5060")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_Guid?>, int>), TypeInfoPropertyName = "T5061")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5062")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5063")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5064")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_Guid?>>), TypeInfoPropertyName = "T5065")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_Guid?>, int>), TypeInfoPropertyName = "T5066")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_Guid?>>), TypeInfoPropertyName = "T5067")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_Guid?>>), TypeInfoPropertyName = "T5068")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_Guid?>>), TypeInfoPropertyName = "T5069")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_Guid?>>), TypeInfoPropertyName = "T5070")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_Guid?>, int>), TypeInfoPropertyName = "T5071")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_Guid?>), TypeInfoPropertyName = "T5072")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_Guid?>), TypeInfoPropertyName = "T5073")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_Guid?>), TypeInfoPropertyName = "T5074")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_Guid?>), TypeInfoPropertyName = "T5075")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_Guid?, int>), TypeInfoPropertyName = "T5076")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_Guid?>), TypeInfoPropertyName = "T5077")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_Guid?>), TypeInfoPropertyName = "T5078")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_Guid?>), TypeInfoPropertyName = "T5079")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_Guid?>), TypeInfoPropertyName = "T5080")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_Guid?, int>), TypeInfoPropertyName = "T5081")] +[JsonSerializable(typeof(V_record_struct_int?), TypeInfoPropertyName = "T5082")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_int?>), TypeInfoPropertyName = "T5083")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_int?>), TypeInfoPropertyName = "T5084")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_int?>), TypeInfoPropertyName = "T5085")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_int?>), TypeInfoPropertyName = "T5086")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_int?, int>), TypeInfoPropertyName = "T5087")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_int?>>), TypeInfoPropertyName = "T5088")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_int?>>), TypeInfoPropertyName = "T5089")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_int?>>), TypeInfoPropertyName = "T5090")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_int?>>), TypeInfoPropertyName = "T5091")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_int?>, int>), TypeInfoPropertyName = "T5092")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_int?[]>), TypeInfoPropertyName = "T5093")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_int?[]>), TypeInfoPropertyName = "T5094")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_int?[]>), TypeInfoPropertyName = "T5095")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_int?[]>), TypeInfoPropertyName = "T5096")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_int?[], int>), TypeInfoPropertyName = "T5097")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_int?>>), TypeInfoPropertyName = "T5098")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_int?>>), TypeInfoPropertyName = "T5099")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_int?>>), TypeInfoPropertyName = "T5100")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_int?>>), TypeInfoPropertyName = "T5101")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_int?>, int>), TypeInfoPropertyName = "T5102")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_int?>>), TypeInfoPropertyName = "T5103")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_int?>>), TypeInfoPropertyName = "T5104")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_int?>>), TypeInfoPropertyName = "T5105")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_int?>>), TypeInfoPropertyName = "T5106")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_int?>, int>), TypeInfoPropertyName = "T5107")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_int?>>), TypeInfoPropertyName = "T5108")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_int?>>), TypeInfoPropertyName = "T5109")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_int?>>), TypeInfoPropertyName = "T5110")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_int?>>), TypeInfoPropertyName = "T5111")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_int?>, int>), TypeInfoPropertyName = "T5112")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_int?>), TypeInfoPropertyName = "T5113")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_int?>), TypeInfoPropertyName = "T5114")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_int?>), TypeInfoPropertyName = "T5115")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_int?>), TypeInfoPropertyName = "T5116")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_int?, int>), TypeInfoPropertyName = "T5117")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_int?>), TypeInfoPropertyName = "T5118")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_int?>), TypeInfoPropertyName = "T5119")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_int?>), TypeInfoPropertyName = "T5120")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_int?>), TypeInfoPropertyName = "T5121")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_int?, int>), TypeInfoPropertyName = "T5122")] +[JsonSerializable(typeof(V_record_struct_long?), TypeInfoPropertyName = "T5123")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_long?>), TypeInfoPropertyName = "T5124")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_long?>), TypeInfoPropertyName = "T5125")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_long?>), TypeInfoPropertyName = "T5126")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_long?>), TypeInfoPropertyName = "T5127")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_long?, int>), TypeInfoPropertyName = "T5128")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_long?>>), TypeInfoPropertyName = "T5129")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_long?>>), TypeInfoPropertyName = "T5130")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_long?>>), TypeInfoPropertyName = "T5131")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_long?>>), TypeInfoPropertyName = "T5132")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_long?>, int>), TypeInfoPropertyName = "T5133")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_long?[]>), TypeInfoPropertyName = "T5134")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_long?[]>), TypeInfoPropertyName = "T5135")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_long?[]>), TypeInfoPropertyName = "T5136")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_long?[]>), TypeInfoPropertyName = "T5137")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_long?[], int>), TypeInfoPropertyName = "T5138")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_long?>>), TypeInfoPropertyName = "T5139")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_long?>>), TypeInfoPropertyName = "T5140")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_long?>>), TypeInfoPropertyName = "T5141")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_long?>>), TypeInfoPropertyName = "T5142")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_long?>, int>), TypeInfoPropertyName = "T5143")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_long?>>), TypeInfoPropertyName = "T5144")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_long?>>), TypeInfoPropertyName = "T5145")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_long?>>), TypeInfoPropertyName = "T5146")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_long?>>), TypeInfoPropertyName = "T5147")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_long?>, int>), TypeInfoPropertyName = "T5148")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_long?>>), TypeInfoPropertyName = "T5149")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_long?>>), TypeInfoPropertyName = "T5150")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_long?>>), TypeInfoPropertyName = "T5151")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_long?>>), TypeInfoPropertyName = "T5152")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_long?>, int>), TypeInfoPropertyName = "T5153")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_long?>), TypeInfoPropertyName = "T5154")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_long?>), TypeInfoPropertyName = "T5155")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_long?>), TypeInfoPropertyName = "T5156")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_long?>), TypeInfoPropertyName = "T5157")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_long?, int>), TypeInfoPropertyName = "T5158")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_long?>), TypeInfoPropertyName = "T5159")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_long?>), TypeInfoPropertyName = "T5160")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_long?>), TypeInfoPropertyName = "T5161")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_long?>), TypeInfoPropertyName = "T5162")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_long?, int>), TypeInfoPropertyName = "T5163")] +[JsonSerializable(typeof(V_record_struct_byte?), TypeInfoPropertyName = "T5164")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_byte?>), TypeInfoPropertyName = "T5165")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_byte?>), TypeInfoPropertyName = "T5166")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_byte?>), TypeInfoPropertyName = "T5167")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_byte?>), TypeInfoPropertyName = "T5168")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_byte?, int>), TypeInfoPropertyName = "T5169")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_byte?>>), TypeInfoPropertyName = "T5170")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_byte?>>), TypeInfoPropertyName = "T5171")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_byte?>>), TypeInfoPropertyName = "T5172")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_byte?>>), TypeInfoPropertyName = "T5173")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_byte?>, int>), TypeInfoPropertyName = "T5174")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_byte?[]>), TypeInfoPropertyName = "T5175")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_byte?[]>), TypeInfoPropertyName = "T5176")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_byte?[]>), TypeInfoPropertyName = "T5177")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_byte?[]>), TypeInfoPropertyName = "T5178")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_byte?[], int>), TypeInfoPropertyName = "T5179")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_byte?>>), TypeInfoPropertyName = "T5180")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_byte?>>), TypeInfoPropertyName = "T5181")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_byte?>>), TypeInfoPropertyName = "T5182")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_byte?>>), TypeInfoPropertyName = "T5183")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_byte?>, int>), TypeInfoPropertyName = "T5184")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_byte?>>), TypeInfoPropertyName = "T5185")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_byte?>>), TypeInfoPropertyName = "T5186")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_byte?>>), TypeInfoPropertyName = "T5187")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_byte?>>), TypeInfoPropertyName = "T5188")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_byte?>, int>), TypeInfoPropertyName = "T5189")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_byte?>>), TypeInfoPropertyName = "T5190")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_byte?>>), TypeInfoPropertyName = "T5191")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_byte?>>), TypeInfoPropertyName = "T5192")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_byte?>>), TypeInfoPropertyName = "T5193")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_byte?>, int>), TypeInfoPropertyName = "T5194")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_byte?>), TypeInfoPropertyName = "T5195")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_byte?>), TypeInfoPropertyName = "T5196")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_byte?>), TypeInfoPropertyName = "T5197")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_byte?>), TypeInfoPropertyName = "T5198")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_byte?, int>), TypeInfoPropertyName = "T5199")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_byte?>), TypeInfoPropertyName = "T5200")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_byte?>), TypeInfoPropertyName = "T5201")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_byte?>), TypeInfoPropertyName = "T5202")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_byte?>), TypeInfoPropertyName = "T5203")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_byte?, int>), TypeInfoPropertyName = "T5204")] +[JsonSerializable(typeof(V_record_struct_ulong?), TypeInfoPropertyName = "T5205")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_ulong?>), TypeInfoPropertyName = "T5206")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_ulong?>), TypeInfoPropertyName = "T5207")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_ulong?>), TypeInfoPropertyName = "T5208")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_ulong?>), TypeInfoPropertyName = "T5209")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_ulong?, int>), TypeInfoPropertyName = "T5210")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5211")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5212")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5213")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5214")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_ulong?>, int>), TypeInfoPropertyName = "T5215")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_ulong?[]>), TypeInfoPropertyName = "T5216")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_ulong?[]>), TypeInfoPropertyName = "T5217")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_ulong?[]>), TypeInfoPropertyName = "T5218")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_ulong?[]>), TypeInfoPropertyName = "T5219")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_ulong?[], int>), TypeInfoPropertyName = "T5220")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5221")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5222")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5223")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5224")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_ulong?>, int>), TypeInfoPropertyName = "T5225")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5226")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5227")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5228")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_ulong?>>), TypeInfoPropertyName = "T5229")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_ulong?>, int>), TypeInfoPropertyName = "T5230")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_ulong?>>), TypeInfoPropertyName = "T5231")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_ulong?>>), TypeInfoPropertyName = "T5232")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_ulong?>>), TypeInfoPropertyName = "T5233")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_ulong?>>), TypeInfoPropertyName = "T5234")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_ulong?>, int>), TypeInfoPropertyName = "T5235")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_ulong?>), TypeInfoPropertyName = "T5236")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_ulong?>), TypeInfoPropertyName = "T5237")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_ulong?>), TypeInfoPropertyName = "T5238")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_ulong?>), TypeInfoPropertyName = "T5239")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_ulong?, int>), TypeInfoPropertyName = "T5240")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_ulong?>), TypeInfoPropertyName = "T5241")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_ulong?>), TypeInfoPropertyName = "T5242")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_ulong?>), TypeInfoPropertyName = "T5243")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_ulong?>), TypeInfoPropertyName = "T5244")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_ulong?, int>), TypeInfoPropertyName = "T5245")] +[JsonSerializable(typeof(V_record_struct_decimal?), TypeInfoPropertyName = "T5246")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_decimal?>), TypeInfoPropertyName = "T5247")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_decimal?>), TypeInfoPropertyName = "T5248")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_decimal?>), TypeInfoPropertyName = "T5249")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_decimal?>), TypeInfoPropertyName = "T5250")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_decimal?, int>), TypeInfoPropertyName = "T5251")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5252")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5253")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5254")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5255")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_decimal?>, int>), TypeInfoPropertyName = "T5256")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_decimal?[]>), TypeInfoPropertyName = "T5257")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_decimal?[]>), TypeInfoPropertyName = "T5258")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_decimal?[]>), TypeInfoPropertyName = "T5259")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_decimal?[]>), TypeInfoPropertyName = "T5260")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_decimal?[], int>), TypeInfoPropertyName = "T5261")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5262")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5263")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5264")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5265")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_decimal?>, int>), TypeInfoPropertyName = "T5266")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5267")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5268")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5269")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_decimal?>>), TypeInfoPropertyName = "T5270")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_decimal?>, int>), TypeInfoPropertyName = "T5271")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_decimal?>>), TypeInfoPropertyName = "T5272")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_decimal?>>), TypeInfoPropertyName = "T5273")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_decimal?>>), TypeInfoPropertyName = "T5274")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_decimal?>>), TypeInfoPropertyName = "T5275")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_decimal?>, int>), TypeInfoPropertyName = "T5276")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_decimal?>), TypeInfoPropertyName = "T5277")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_decimal?>), TypeInfoPropertyName = "T5278")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_decimal?>), TypeInfoPropertyName = "T5279")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_decimal?>), TypeInfoPropertyName = "T5280")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_decimal?, int>), TypeInfoPropertyName = "T5281")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_decimal?>), TypeInfoPropertyName = "T5282")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_decimal?>), TypeInfoPropertyName = "T5283")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_decimal?>), TypeInfoPropertyName = "T5284")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_decimal?>), TypeInfoPropertyName = "T5285")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_decimal?, int>), TypeInfoPropertyName = "T5286")] +[JsonSerializable(typeof(V_record_struct_double?), TypeInfoPropertyName = "T5287")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_double?>), TypeInfoPropertyName = "T5288")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_double?>), TypeInfoPropertyName = "T5289")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_double?>), TypeInfoPropertyName = "T5290")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_double?>), TypeInfoPropertyName = "T5291")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_double?, int>), TypeInfoPropertyName = "T5292")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_double?>>), TypeInfoPropertyName = "T5293")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_double?>>), TypeInfoPropertyName = "T5294")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_double?>>), TypeInfoPropertyName = "T5295")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_double?>>), TypeInfoPropertyName = "T5296")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_double?>, int>), TypeInfoPropertyName = "T5297")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_double?[]>), TypeInfoPropertyName = "T5298")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_double?[]>), TypeInfoPropertyName = "T5299")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_double?[]>), TypeInfoPropertyName = "T5300")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_double?[]>), TypeInfoPropertyName = "T5301")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_double?[], int>), TypeInfoPropertyName = "T5302")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_double?>>), TypeInfoPropertyName = "T5303")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_double?>>), TypeInfoPropertyName = "T5304")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_double?>>), TypeInfoPropertyName = "T5305")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_double?>>), TypeInfoPropertyName = "T5306")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_double?>, int>), TypeInfoPropertyName = "T5307")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_double?>>), TypeInfoPropertyName = "T5308")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_double?>>), TypeInfoPropertyName = "T5309")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_double?>>), TypeInfoPropertyName = "T5310")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_double?>>), TypeInfoPropertyName = "T5311")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_double?>, int>), TypeInfoPropertyName = "T5312")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_double?>>), TypeInfoPropertyName = "T5313")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_double?>>), TypeInfoPropertyName = "T5314")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_double?>>), TypeInfoPropertyName = "T5315")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_double?>>), TypeInfoPropertyName = "T5316")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_double?>, int>), TypeInfoPropertyName = "T5317")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_double?>), TypeInfoPropertyName = "T5318")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_double?>), TypeInfoPropertyName = "T5319")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_double?>), TypeInfoPropertyName = "T5320")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_double?>), TypeInfoPropertyName = "T5321")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_double?, int>), TypeInfoPropertyName = "T5322")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_double?>), TypeInfoPropertyName = "T5323")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_double?>), TypeInfoPropertyName = "T5324")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_double?>), TypeInfoPropertyName = "T5325")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_double?>), TypeInfoPropertyName = "T5326")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_double?, int>), TypeInfoPropertyName = "T5327")] +[JsonSerializable(typeof(V_record_struct_bool?), TypeInfoPropertyName = "T5328")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_bool?>), TypeInfoPropertyName = "T5329")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_bool?>), TypeInfoPropertyName = "T5330")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_bool?>), TypeInfoPropertyName = "T5331")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_bool?>), TypeInfoPropertyName = "T5332")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_bool?, int>), TypeInfoPropertyName = "T5333")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_bool?>>), TypeInfoPropertyName = "T5334")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_bool?>>), TypeInfoPropertyName = "T5335")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_bool?>>), TypeInfoPropertyName = "T5336")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_bool?>>), TypeInfoPropertyName = "T5337")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_bool?>, int>), TypeInfoPropertyName = "T5338")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_bool?[]>), TypeInfoPropertyName = "T5339")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_bool?[]>), TypeInfoPropertyName = "T5340")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_bool?[]>), TypeInfoPropertyName = "T5341")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_bool?[]>), TypeInfoPropertyName = "T5342")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_bool?[], int>), TypeInfoPropertyName = "T5343")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_bool?>>), TypeInfoPropertyName = "T5344")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_bool?>>), TypeInfoPropertyName = "T5345")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_bool?>>), TypeInfoPropertyName = "T5346")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_bool?>>), TypeInfoPropertyName = "T5347")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_bool?>, int>), TypeInfoPropertyName = "T5348")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_bool?>>), TypeInfoPropertyName = "T5349")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_bool?>>), TypeInfoPropertyName = "T5350")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_bool?>>), TypeInfoPropertyName = "T5351")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_bool?>>), TypeInfoPropertyName = "T5352")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_bool?>, int>), TypeInfoPropertyName = "T5353")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_bool?>>), TypeInfoPropertyName = "T5354")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_bool?>>), TypeInfoPropertyName = "T5355")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_bool?>>), TypeInfoPropertyName = "T5356")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_bool?>>), TypeInfoPropertyName = "T5357")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_bool?>, int>), TypeInfoPropertyName = "T5358")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_bool?>), TypeInfoPropertyName = "T5359")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_bool?>), TypeInfoPropertyName = "T5360")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_bool?>), TypeInfoPropertyName = "T5361")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_bool?>), TypeInfoPropertyName = "T5362")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_bool?, int>), TypeInfoPropertyName = "T5363")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_bool?>), TypeInfoPropertyName = "T5364")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_bool?>), TypeInfoPropertyName = "T5365")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_bool?>), TypeInfoPropertyName = "T5366")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_bool?>), TypeInfoPropertyName = "T5367")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_bool?, int>), TypeInfoPropertyName = "T5368")] +[JsonSerializable(typeof(V_record_struct_char?), TypeInfoPropertyName = "T5369")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_char?>), TypeInfoPropertyName = "T5370")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_char?>), TypeInfoPropertyName = "T5371")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_char?>), TypeInfoPropertyName = "T5372")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_char?>), TypeInfoPropertyName = "T5373")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_char?, int>), TypeInfoPropertyName = "T5374")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_char?>>), TypeInfoPropertyName = "T5375")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_char?>>), TypeInfoPropertyName = "T5376")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_char?>>), TypeInfoPropertyName = "T5377")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_char?>>), TypeInfoPropertyName = "T5378")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_char?>, int>), TypeInfoPropertyName = "T5379")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_char?[]>), TypeInfoPropertyName = "T5380")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_char?[]>), TypeInfoPropertyName = "T5381")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_char?[]>), TypeInfoPropertyName = "T5382")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_char?[]>), TypeInfoPropertyName = "T5383")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_char?[], int>), TypeInfoPropertyName = "T5384")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_char?>>), TypeInfoPropertyName = "T5385")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_char?>>), TypeInfoPropertyName = "T5386")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_char?>>), TypeInfoPropertyName = "T5387")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_char?>>), TypeInfoPropertyName = "T5388")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_char?>, int>), TypeInfoPropertyName = "T5389")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_char?>>), TypeInfoPropertyName = "T5390")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_char?>>), TypeInfoPropertyName = "T5391")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_char?>>), TypeInfoPropertyName = "T5392")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_char?>>), TypeInfoPropertyName = "T5393")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_char?>, int>), TypeInfoPropertyName = "T5394")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_char?>>), TypeInfoPropertyName = "T5395")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_char?>>), TypeInfoPropertyName = "T5396")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_char?>>), TypeInfoPropertyName = "T5397")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_char?>>), TypeInfoPropertyName = "T5398")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_char?>, int>), TypeInfoPropertyName = "T5399")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_char?>), TypeInfoPropertyName = "T5400")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_char?>), TypeInfoPropertyName = "T5401")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_char?>), TypeInfoPropertyName = "T5402")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_char?>), TypeInfoPropertyName = "T5403")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_char?, int>), TypeInfoPropertyName = "T5404")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_char?>), TypeInfoPropertyName = "T5405")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_char?>), TypeInfoPropertyName = "T5406")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_char?>), TypeInfoPropertyName = "T5407")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_char?>), TypeInfoPropertyName = "T5408")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_char?, int>), TypeInfoPropertyName = "T5409")] +[JsonSerializable(typeof(V_record_struct_DateTime?), TypeInfoPropertyName = "T5410")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5411")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5412")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5413")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5414")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateTime?, int>), TypeInfoPropertyName = "T5415")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5416")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5417")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5418")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5419")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateTime?>, int>), TypeInfoPropertyName = "T5420")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTime?[]>), TypeInfoPropertyName = "T5421")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTime?[]>), TypeInfoPropertyName = "T5422")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTime?[]>), TypeInfoPropertyName = "T5423")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTime?[]>), TypeInfoPropertyName = "T5424")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTime?[], int>), TypeInfoPropertyName = "T5425")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5426")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5427")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5428")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5429")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateTime?>, int>), TypeInfoPropertyName = "T5430")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5431")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5432")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5433")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5434")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateTime?>, int>), TypeInfoPropertyName = "T5435")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5436")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5437")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5438")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateTime?>>), TypeInfoPropertyName = "T5439")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateTime?>, int>), TypeInfoPropertyName = "T5440")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5441")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5442")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5443")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5444")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateTime?, int>), TypeInfoPropertyName = "T5445")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5446")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5447")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5448")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateTime?>), TypeInfoPropertyName = "T5449")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateTime?, int>), TypeInfoPropertyName = "T5450")] +[JsonSerializable(typeof(V_record_struct_DateTimeOffset?), TypeInfoPropertyName = "T5451")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5452")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5453")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5454")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5455")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T5456")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5457")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5458")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5459")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5460")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T5461")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T5462")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T5463")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T5464")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T5465")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateTimeOffset?[], int>), TypeInfoPropertyName = "T5466")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5467")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5468")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5469")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5470")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T5471")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5472")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5473")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5474")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5475")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T5476")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5477")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5478")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5479")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T5480")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T5481")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5482")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5483")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5484")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5485")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T5486")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5487")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5488")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5489")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateTimeOffset?>), TypeInfoPropertyName = "T5490")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T5491")] +[JsonSerializable(typeof(V_record_struct_DateOnly?), TypeInfoPropertyName = "T5492")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5493")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5494")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5495")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5496")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_DateOnly?, int>), TypeInfoPropertyName = "T5497")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5498")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5499")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5500")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5501")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_DateOnly?>, int>), TypeInfoPropertyName = "T5502")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_DateOnly?[]>), TypeInfoPropertyName = "T5503")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_DateOnly?[]>), TypeInfoPropertyName = "T5504")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_DateOnly?[]>), TypeInfoPropertyName = "T5505")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_DateOnly?[]>), TypeInfoPropertyName = "T5506")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_DateOnly?[], int>), TypeInfoPropertyName = "T5507")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5508")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5509")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5510")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5511")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_DateOnly?>, int>), TypeInfoPropertyName = "T5512")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5513")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5514")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5515")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5516")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_DateOnly?>, int>), TypeInfoPropertyName = "T5517")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5518")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5519")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5520")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_DateOnly?>>), TypeInfoPropertyName = "T5521")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_DateOnly?>, int>), TypeInfoPropertyName = "T5522")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5523")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5524")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5525")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5526")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_DateOnly?, int>), TypeInfoPropertyName = "T5527")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5528")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5529")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5530")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_DateOnly?>), TypeInfoPropertyName = "T5531")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_DateOnly?, int>), TypeInfoPropertyName = "T5532")] +[JsonSerializable(typeof(V_record_struct_TimeOnly?), TypeInfoPropertyName = "T5533")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5534")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5535")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5536")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5537")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_TimeOnly?, int>), TypeInfoPropertyName = "T5538")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5539")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5540")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5541")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5542")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T5543")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeOnly?[]>), TypeInfoPropertyName = "T5544")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeOnly?[]>), TypeInfoPropertyName = "T5545")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeOnly?[]>), TypeInfoPropertyName = "T5546")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeOnly?[]>), TypeInfoPropertyName = "T5547")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeOnly?[], int>), TypeInfoPropertyName = "T5548")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5549")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5550")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5551")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5552")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T5553")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5554")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5555")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5556")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5557")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T5558")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5559")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5560")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5561")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_TimeOnly?>>), TypeInfoPropertyName = "T5562")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T5563")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5564")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5565")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5566")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5567")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_TimeOnly?, int>), TypeInfoPropertyName = "T5568")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5569")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5570")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5571")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_TimeOnly?>), TypeInfoPropertyName = "T5572")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_TimeOnly?, int>), TypeInfoPropertyName = "T5573")] +[JsonSerializable(typeof(V_record_struct_TimeSpan?), TypeInfoPropertyName = "T5574")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5575")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5576")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5577")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5578")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_TimeSpan?, int>), TypeInfoPropertyName = "T5579")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5580")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5581")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5582")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5583")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T5584")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_TimeSpan?[]>), TypeInfoPropertyName = "T5585")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_TimeSpan?[]>), TypeInfoPropertyName = "T5586")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_TimeSpan?[]>), TypeInfoPropertyName = "T5587")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_TimeSpan?[]>), TypeInfoPropertyName = "T5588")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_TimeSpan?[], int>), TypeInfoPropertyName = "T5589")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5590")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5591")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5592")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5593")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T5594")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5595")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5596")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5597")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5598")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T5599")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5600")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5601")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5602")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_TimeSpan?>>), TypeInfoPropertyName = "T5603")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T5604")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5605")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5606")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5607")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5608")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_TimeSpan?, int>), TypeInfoPropertyName = "T5609")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5610")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5611")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5612")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_TimeSpan?>), TypeInfoPropertyName = "T5613")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_TimeSpan?, int>), TypeInfoPropertyName = "T5614")] +[JsonSerializable(typeof(V_record_struct_enum?), TypeInfoPropertyName = "T5615")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_enum?>), TypeInfoPropertyName = "T5616")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_enum?>), TypeInfoPropertyName = "T5617")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_enum?>), TypeInfoPropertyName = "T5618")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_enum?>), TypeInfoPropertyName = "T5619")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_enum?, int>), TypeInfoPropertyName = "T5620")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_enum?>>), TypeInfoPropertyName = "T5621")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_enum?>>), TypeInfoPropertyName = "T5622")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_enum?>>), TypeInfoPropertyName = "T5623")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_enum?>>), TypeInfoPropertyName = "T5624")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_enum?>, int>), TypeInfoPropertyName = "T5625")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_enum?[]>), TypeInfoPropertyName = "T5626")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_enum?[]>), TypeInfoPropertyName = "T5627")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_enum?[]>), TypeInfoPropertyName = "T5628")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_enum?[]>), TypeInfoPropertyName = "T5629")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_enum?[], int>), TypeInfoPropertyName = "T5630")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_enum?>>), TypeInfoPropertyName = "T5631")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_enum?>>), TypeInfoPropertyName = "T5632")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_enum?>>), TypeInfoPropertyName = "T5633")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_enum?>>), TypeInfoPropertyName = "T5634")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_enum?>, int>), TypeInfoPropertyName = "T5635")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_enum?>>), TypeInfoPropertyName = "T5636")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_enum?>>), TypeInfoPropertyName = "T5637")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_enum?>>), TypeInfoPropertyName = "T5638")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_enum?>>), TypeInfoPropertyName = "T5639")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_enum?>, int>), TypeInfoPropertyName = "T5640")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_enum?>>), TypeInfoPropertyName = "T5641")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_enum?>>), TypeInfoPropertyName = "T5642")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_enum?>>), TypeInfoPropertyName = "T5643")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_enum?>>), TypeInfoPropertyName = "T5644")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_enum?>, int>), TypeInfoPropertyName = "T5645")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_enum?>), TypeInfoPropertyName = "T5646")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_enum?>), TypeInfoPropertyName = "T5647")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_enum?>), TypeInfoPropertyName = "T5648")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_enum?>), TypeInfoPropertyName = "T5649")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_enum?, int>), TypeInfoPropertyName = "T5650")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_enum?>), TypeInfoPropertyName = "T5651")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_enum?>), TypeInfoPropertyName = "T5652")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_enum?>), TypeInfoPropertyName = "T5653")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_enum?>), TypeInfoPropertyName = "T5654")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_enum?, int>), TypeInfoPropertyName = "T5655")] +[JsonSerializable(typeof(V_record_struct_flags_enum?), TypeInfoPropertyName = "T5656")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5657")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5658")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5659")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5660")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_flags_enum?, int>), TypeInfoPropertyName = "T5661")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5662")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5663")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5664")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5665")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_flags_enum?>, int>), TypeInfoPropertyName = "T5666")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_flags_enum?[]>), TypeInfoPropertyName = "T5667")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_flags_enum?[]>), TypeInfoPropertyName = "T5668")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_flags_enum?[]>), TypeInfoPropertyName = "T5669")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_flags_enum?[]>), TypeInfoPropertyName = "T5670")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_flags_enum?[], int>), TypeInfoPropertyName = "T5671")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5672")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5673")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5674")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5675")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_flags_enum?>, int>), TypeInfoPropertyName = "T5676")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5677")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5678")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5679")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5680")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_flags_enum?>, int>), TypeInfoPropertyName = "T5681")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5682")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5683")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5684")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_flags_enum?>>), TypeInfoPropertyName = "T5685")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_flags_enum?>, int>), TypeInfoPropertyName = "T5686")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5687")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5688")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5689")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5690")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_flags_enum?, int>), TypeInfoPropertyName = "T5691")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5692")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5693")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5694")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_flags_enum?>), TypeInfoPropertyName = "T5695")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_flags_enum?, int>), TypeInfoPropertyName = "T5696")] +[JsonSerializable(typeof(V_record_struct_Uri?), TypeInfoPropertyName = "T5697")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_Uri?>), TypeInfoPropertyName = "T5698")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_Uri?>), TypeInfoPropertyName = "T5699")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_Uri?>), TypeInfoPropertyName = "T5700")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_Uri?>), TypeInfoPropertyName = "T5701")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_Uri?, int>), TypeInfoPropertyName = "T5702")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5703")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5704")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5705")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5706")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_Uri?>, int>), TypeInfoPropertyName = "T5707")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_Uri?[]>), TypeInfoPropertyName = "T5708")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_Uri?[]>), TypeInfoPropertyName = "T5709")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_Uri?[]>), TypeInfoPropertyName = "T5710")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_Uri?[]>), TypeInfoPropertyName = "T5711")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_Uri?[], int>), TypeInfoPropertyName = "T5712")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5713")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5714")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5715")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5716")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_Uri?>, int>), TypeInfoPropertyName = "T5717")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5718")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5719")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5720")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_Uri?>>), TypeInfoPropertyName = "T5721")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_Uri?>, int>), TypeInfoPropertyName = "T5722")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_Uri?>>), TypeInfoPropertyName = "T5723")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_Uri?>>), TypeInfoPropertyName = "T5724")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_Uri?>>), TypeInfoPropertyName = "T5725")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_Uri?>>), TypeInfoPropertyName = "T5726")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_Uri?>, int>), TypeInfoPropertyName = "T5727")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_Uri?>), TypeInfoPropertyName = "T5728")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_Uri?>), TypeInfoPropertyName = "T5729")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_Uri?>), TypeInfoPropertyName = "T5730")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_Uri?>), TypeInfoPropertyName = "T5731")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_Uri?, int>), TypeInfoPropertyName = "T5732")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_Uri?>), TypeInfoPropertyName = "T5733")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_Uri?>), TypeInfoPropertyName = "T5734")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_Uri?>), TypeInfoPropertyName = "T5735")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_Uri?>), TypeInfoPropertyName = "T5736")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_Uri?, int>), TypeInfoPropertyName = "T5737")] +[JsonSerializable(typeof(V_record_struct_NodaTime_Instant?), TypeInfoPropertyName = "T5738")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5739")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5740")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5741")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5742")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T5743")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5744")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5745")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5746")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5747")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T5748")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T5749")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T5750")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T5751")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T5752")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_NodaTime_Instant?[], int>), TypeInfoPropertyName = "T5753")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5754")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5755")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5756")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5757")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T5758")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5759")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5760")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5761")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5762")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T5763")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5764")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5765")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5766")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T5767")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T5768")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5769")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5770")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5771")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5772")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T5773")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5774")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5775")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5776")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T5777")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T5778")] +[JsonSerializable(typeof(V_record_struct_own_record_class?), TypeInfoPropertyName = "T5779")] +[JsonSerializable(typeof(Cn_class_F<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5780")] +[JsonSerializable(typeof(Cn_record_class_F<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5781")] +[JsonSerializable(typeof(Cn_struct_F<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5782")] +[JsonSerializable(typeof(Cn_record_struct_F<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5783")] +[JsonSerializable(typeof(Cn_generic_type_F<V_record_struct_own_record_class?, int>), TypeInfoPropertyName = "T5784")] +[JsonSerializable(typeof(Cn_class_P<List<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5785")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5786")] +[JsonSerializable(typeof(Cn_struct_P<List<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5787")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5788")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_record_struct_own_record_class?>, int>), TypeInfoPropertyName = "T5789")] +[JsonSerializable(typeof(Cn_class_P<V_record_struct_own_record_class?[]>), TypeInfoPropertyName = "T5790")] +[JsonSerializable(typeof(Cn_record_class_P<V_record_struct_own_record_class?[]>), TypeInfoPropertyName = "T5791")] +[JsonSerializable(typeof(Cn_struct_P<V_record_struct_own_record_class?[]>), TypeInfoPropertyName = "T5792")] +[JsonSerializable(typeof(Cn_record_struct_P<V_record_struct_own_record_class?[]>), TypeInfoPropertyName = "T5793")] +[JsonSerializable(typeof(Cn_generic_type_P<V_record_struct_own_record_class?[], int>), TypeInfoPropertyName = "T5794")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5795")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5796")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5797")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5798")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_record_struct_own_record_class?>, int>), TypeInfoPropertyName = "T5799")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5800")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5801")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5802")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5803")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_record_struct_own_record_class?>, int>), TypeInfoPropertyName = "T5804")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5805")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5806")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5807")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_record_struct_own_record_class?>>), TypeInfoPropertyName = "T5808")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_record_struct_own_record_class?>, int>), TypeInfoPropertyName = "T5809")] +[JsonSerializable(typeof(Cn_class_C<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5810")] +[JsonSerializable(typeof(Cn_record_class_C<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5811")] +[JsonSerializable(typeof(Cn_struct_C<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5812")] +[JsonSerializable(typeof(Cn_record_struct_C<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5813")] +[JsonSerializable(typeof(Cn_generic_type_C<V_record_struct_own_record_class?, int>), TypeInfoPropertyName = "T5814")] +[JsonSerializable(typeof(Cn_class_R<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5815")] +[JsonSerializable(typeof(Cn_record_class_R<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5816")] +[JsonSerializable(typeof(Cn_struct_R<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5817")] +[JsonSerializable(typeof(Cn_record_struct_R<V_record_struct_own_record_class?>), TypeInfoPropertyName = "T5818")] +[JsonSerializable(typeof(Cn_generic_type_R<V_record_struct_own_record_class?, int>), TypeInfoPropertyName = "T5819")] +[JsonSerializable(typeof(V_validated_struct_string?), TypeInfoPropertyName = "T5820")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_string?>), TypeInfoPropertyName = "T5821")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_string?>), TypeInfoPropertyName = "T5822")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_string?>), TypeInfoPropertyName = "T5823")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_string?>), TypeInfoPropertyName = "T5824")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_string?, int>), TypeInfoPropertyName = "T5825")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_string?>>), TypeInfoPropertyName = "T5826")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_string?>>), TypeInfoPropertyName = "T5827")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_string?>>), TypeInfoPropertyName = "T5828")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_string?>>), TypeInfoPropertyName = "T5829")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_string?>, int>), TypeInfoPropertyName = "T5830")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_string?[]>), TypeInfoPropertyName = "T5831")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_string?[]>), TypeInfoPropertyName = "T5832")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_string?[]>), TypeInfoPropertyName = "T5833")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_string?[]>), TypeInfoPropertyName = "T5834")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_string?[], int>), TypeInfoPropertyName = "T5835")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_string?>>), TypeInfoPropertyName = "T5836")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_string?>>), TypeInfoPropertyName = "T5837")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_string?>>), TypeInfoPropertyName = "T5838")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_string?>>), TypeInfoPropertyName = "T5839")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_string?>, int>), TypeInfoPropertyName = "T5840")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_string?>>), TypeInfoPropertyName = "T5841")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_string?>>), TypeInfoPropertyName = "T5842")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_string?>>), TypeInfoPropertyName = "T5843")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_string?>>), TypeInfoPropertyName = "T5844")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_string?>, int>), TypeInfoPropertyName = "T5845")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_string?>>), TypeInfoPropertyName = "T5846")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_string?>>), TypeInfoPropertyName = "T5847")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_string?>>), TypeInfoPropertyName = "T5848")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_string?>>), TypeInfoPropertyName = "T5849")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_string?>, int>), TypeInfoPropertyName = "T5850")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_string?>), TypeInfoPropertyName = "T5851")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_string?>), TypeInfoPropertyName = "T5852")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_string?>), TypeInfoPropertyName = "T5853")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_string?>), TypeInfoPropertyName = "T5854")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_string?, int>), TypeInfoPropertyName = "T5855")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_string?>), TypeInfoPropertyName = "T5856")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_string?>), TypeInfoPropertyName = "T5857")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_string?>), TypeInfoPropertyName = "T5858")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_string?>), TypeInfoPropertyName = "T5859")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_string?, int>), TypeInfoPropertyName = "T5860")] +[JsonSerializable(typeof(V_validated_struct_Guid?), TypeInfoPropertyName = "T5861")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5862")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5863")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5864")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5865")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_Guid?, int>), TypeInfoPropertyName = "T5866")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5867")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5868")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5869")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5870")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_Guid?>, int>), TypeInfoPropertyName = "T5871")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Guid?[]>), TypeInfoPropertyName = "T5872")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Guid?[]>), TypeInfoPropertyName = "T5873")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Guid?[]>), TypeInfoPropertyName = "T5874")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Guid?[]>), TypeInfoPropertyName = "T5875")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Guid?[], int>), TypeInfoPropertyName = "T5876")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5877")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5878")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5879")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5880")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_Guid?>, int>), TypeInfoPropertyName = "T5881")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5882")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5883")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5884")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5885")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_Guid?>, int>), TypeInfoPropertyName = "T5886")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5887")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5888")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5889")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_Guid?>>), TypeInfoPropertyName = "T5890")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_Guid?>, int>), TypeInfoPropertyName = "T5891")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5892")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5893")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5894")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5895")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_Guid?, int>), TypeInfoPropertyName = "T5896")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5897")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5898")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5899")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_Guid?>), TypeInfoPropertyName = "T5900")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_Guid?, int>), TypeInfoPropertyName = "T5901")] +[JsonSerializable(typeof(V_validated_struct_int?), TypeInfoPropertyName = "T5902")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_int?>), TypeInfoPropertyName = "T5903")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_int?>), TypeInfoPropertyName = "T5904")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_int?>), TypeInfoPropertyName = "T5905")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_int?>), TypeInfoPropertyName = "T5906")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_int?, int>), TypeInfoPropertyName = "T5907")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_int?>>), TypeInfoPropertyName = "T5908")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_int?>>), TypeInfoPropertyName = "T5909")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_int?>>), TypeInfoPropertyName = "T5910")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_int?>>), TypeInfoPropertyName = "T5911")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_int?>, int>), TypeInfoPropertyName = "T5912")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_int?[]>), TypeInfoPropertyName = "T5913")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_int?[]>), TypeInfoPropertyName = "T5914")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_int?[]>), TypeInfoPropertyName = "T5915")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_int?[]>), TypeInfoPropertyName = "T5916")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_int?[], int>), TypeInfoPropertyName = "T5917")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_int?>>), TypeInfoPropertyName = "T5918")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_int?>>), TypeInfoPropertyName = "T5919")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_int?>>), TypeInfoPropertyName = "T5920")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_int?>>), TypeInfoPropertyName = "T5921")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_int?>, int>), TypeInfoPropertyName = "T5922")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_int?>>), TypeInfoPropertyName = "T5923")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_int?>>), TypeInfoPropertyName = "T5924")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_int?>>), TypeInfoPropertyName = "T5925")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_int?>>), TypeInfoPropertyName = "T5926")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_int?>, int>), TypeInfoPropertyName = "T5927")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_int?>>), TypeInfoPropertyName = "T5928")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_int?>>), TypeInfoPropertyName = "T5929")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_int?>>), TypeInfoPropertyName = "T5930")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_int?>>), TypeInfoPropertyName = "T5931")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_int?>, int>), TypeInfoPropertyName = "T5932")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_int?>), TypeInfoPropertyName = "T5933")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_int?>), TypeInfoPropertyName = "T5934")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_int?>), TypeInfoPropertyName = "T5935")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_int?>), TypeInfoPropertyName = "T5936")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_int?, int>), TypeInfoPropertyName = "T5937")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_int?>), TypeInfoPropertyName = "T5938")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_int?>), TypeInfoPropertyName = "T5939")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_int?>), TypeInfoPropertyName = "T5940")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_int?>), TypeInfoPropertyName = "T5941")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_int?, int>), TypeInfoPropertyName = "T5942")] +[JsonSerializable(typeof(V_validated_struct_long?), TypeInfoPropertyName = "T5943")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_long?>), TypeInfoPropertyName = "T5944")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_long?>), TypeInfoPropertyName = "T5945")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_long?>), TypeInfoPropertyName = "T5946")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_long?>), TypeInfoPropertyName = "T5947")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_long?, int>), TypeInfoPropertyName = "T5948")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_long?>>), TypeInfoPropertyName = "T5949")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_long?>>), TypeInfoPropertyName = "T5950")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_long?>>), TypeInfoPropertyName = "T5951")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_long?>>), TypeInfoPropertyName = "T5952")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_long?>, int>), TypeInfoPropertyName = "T5953")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_long?[]>), TypeInfoPropertyName = "T5954")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_long?[]>), TypeInfoPropertyName = "T5955")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_long?[]>), TypeInfoPropertyName = "T5956")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_long?[]>), TypeInfoPropertyName = "T5957")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_long?[], int>), TypeInfoPropertyName = "T5958")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_long?>>), TypeInfoPropertyName = "T5959")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_long?>>), TypeInfoPropertyName = "T5960")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_long?>>), TypeInfoPropertyName = "T5961")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_long?>>), TypeInfoPropertyName = "T5962")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_long?>, int>), TypeInfoPropertyName = "T5963")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_long?>>), TypeInfoPropertyName = "T5964")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_long?>>), TypeInfoPropertyName = "T5965")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_long?>>), TypeInfoPropertyName = "T5966")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_long?>>), TypeInfoPropertyName = "T5967")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_long?>, int>), TypeInfoPropertyName = "T5968")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_long?>>), TypeInfoPropertyName = "T5969")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_long?>>), TypeInfoPropertyName = "T5970")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_long?>>), TypeInfoPropertyName = "T5971")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_long?>>), TypeInfoPropertyName = "T5972")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_long?>, int>), TypeInfoPropertyName = "T5973")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_long?>), TypeInfoPropertyName = "T5974")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_long?>), TypeInfoPropertyName = "T5975")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_long?>), TypeInfoPropertyName = "T5976")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_long?>), TypeInfoPropertyName = "T5977")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_long?, int>), TypeInfoPropertyName = "T5978")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_long?>), TypeInfoPropertyName = "T5979")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_long?>), TypeInfoPropertyName = "T5980")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_long?>), TypeInfoPropertyName = "T5981")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_long?>), TypeInfoPropertyName = "T5982")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_long?, int>), TypeInfoPropertyName = "T5983")] +[JsonSerializable(typeof(V_validated_struct_byte?), TypeInfoPropertyName = "T5984")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_byte?>), TypeInfoPropertyName = "T5985")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_byte?>), TypeInfoPropertyName = "T5986")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_byte?>), TypeInfoPropertyName = "T5987")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_byte?>), TypeInfoPropertyName = "T5988")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_byte?, int>), TypeInfoPropertyName = "T5989")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_byte?>>), TypeInfoPropertyName = "T5990")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_byte?>>), TypeInfoPropertyName = "T5991")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_byte?>>), TypeInfoPropertyName = "T5992")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_byte?>>), TypeInfoPropertyName = "T5993")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_byte?>, int>), TypeInfoPropertyName = "T5994")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_byte?[]>), TypeInfoPropertyName = "T5995")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_byte?[]>), TypeInfoPropertyName = "T5996")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_byte?[]>), TypeInfoPropertyName = "T5997")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_byte?[]>), TypeInfoPropertyName = "T5998")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_byte?[], int>), TypeInfoPropertyName = "T5999")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6000")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6001")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6002")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6003")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_byte?>, int>), TypeInfoPropertyName = "T6004")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6005")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6006")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6007")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_byte?>>), TypeInfoPropertyName = "T6008")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_byte?>, int>), TypeInfoPropertyName = "T6009")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_byte?>>), TypeInfoPropertyName = "T6010")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_byte?>>), TypeInfoPropertyName = "T6011")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_byte?>>), TypeInfoPropertyName = "T6012")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_byte?>>), TypeInfoPropertyName = "T6013")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_byte?>, int>), TypeInfoPropertyName = "T6014")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_byte?>), TypeInfoPropertyName = "T6015")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_byte?>), TypeInfoPropertyName = "T6016")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_byte?>), TypeInfoPropertyName = "T6017")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_byte?>), TypeInfoPropertyName = "T6018")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_byte?, int>), TypeInfoPropertyName = "T6019")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_byte?>), TypeInfoPropertyName = "T6020")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_byte?>), TypeInfoPropertyName = "T6021")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_byte?>), TypeInfoPropertyName = "T6022")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_byte?>), TypeInfoPropertyName = "T6023")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_byte?, int>), TypeInfoPropertyName = "T6024")] +[JsonSerializable(typeof(V_validated_struct_ulong?), TypeInfoPropertyName = "T6025")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6026")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6027")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6028")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6029")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_ulong?, int>), TypeInfoPropertyName = "T6030")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6031")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6032")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6033")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6034")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_ulong?>, int>), TypeInfoPropertyName = "T6035")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_ulong?[]>), TypeInfoPropertyName = "T6036")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_ulong?[]>), TypeInfoPropertyName = "T6037")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_ulong?[]>), TypeInfoPropertyName = "T6038")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_ulong?[]>), TypeInfoPropertyName = "T6039")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_ulong?[], int>), TypeInfoPropertyName = "T6040")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6041")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6042")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6043")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6044")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_ulong?>, int>), TypeInfoPropertyName = "T6045")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6046")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6047")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6048")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6049")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_ulong?>, int>), TypeInfoPropertyName = "T6050")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6051")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6052")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6053")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_ulong?>>), TypeInfoPropertyName = "T6054")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_ulong?>, int>), TypeInfoPropertyName = "T6055")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6056")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6057")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6058")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6059")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_ulong?, int>), TypeInfoPropertyName = "T6060")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6061")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6062")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6063")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_ulong?>), TypeInfoPropertyName = "T6064")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_ulong?, int>), TypeInfoPropertyName = "T6065")] +[JsonSerializable(typeof(V_validated_struct_decimal?), TypeInfoPropertyName = "T6066")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6067")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6068")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6069")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6070")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_decimal?, int>), TypeInfoPropertyName = "T6071")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6072")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6073")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6074")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6075")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_decimal?>, int>), TypeInfoPropertyName = "T6076")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_decimal?[]>), TypeInfoPropertyName = "T6077")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_decimal?[]>), TypeInfoPropertyName = "T6078")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_decimal?[]>), TypeInfoPropertyName = "T6079")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_decimal?[]>), TypeInfoPropertyName = "T6080")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_decimal?[], int>), TypeInfoPropertyName = "T6081")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6082")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6083")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6084")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6085")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_decimal?>, int>), TypeInfoPropertyName = "T6086")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6087")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6088")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6089")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6090")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_decimal?>, int>), TypeInfoPropertyName = "T6091")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6092")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6093")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6094")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_decimal?>>), TypeInfoPropertyName = "T6095")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_decimal?>, int>), TypeInfoPropertyName = "T6096")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6097")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6098")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6099")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6100")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_decimal?, int>), TypeInfoPropertyName = "T6101")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6102")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6103")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6104")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_decimal?>), TypeInfoPropertyName = "T6105")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_decimal?, int>), TypeInfoPropertyName = "T6106")] +[JsonSerializable(typeof(V_validated_struct_double?), TypeInfoPropertyName = "T6107")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_double?>), TypeInfoPropertyName = "T6108")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_double?>), TypeInfoPropertyName = "T6109")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_double?>), TypeInfoPropertyName = "T6110")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_double?>), TypeInfoPropertyName = "T6111")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_double?, int>), TypeInfoPropertyName = "T6112")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_double?>>), TypeInfoPropertyName = "T6113")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_double?>>), TypeInfoPropertyName = "T6114")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_double?>>), TypeInfoPropertyName = "T6115")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_double?>>), TypeInfoPropertyName = "T6116")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_double?>, int>), TypeInfoPropertyName = "T6117")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_double?[]>), TypeInfoPropertyName = "T6118")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_double?[]>), TypeInfoPropertyName = "T6119")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_double?[]>), TypeInfoPropertyName = "T6120")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_double?[]>), TypeInfoPropertyName = "T6121")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_double?[], int>), TypeInfoPropertyName = "T6122")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_double?>>), TypeInfoPropertyName = "T6123")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_double?>>), TypeInfoPropertyName = "T6124")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_double?>>), TypeInfoPropertyName = "T6125")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_double?>>), TypeInfoPropertyName = "T6126")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_double?>, int>), TypeInfoPropertyName = "T6127")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_double?>>), TypeInfoPropertyName = "T6128")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_double?>>), TypeInfoPropertyName = "T6129")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_double?>>), TypeInfoPropertyName = "T6130")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_double?>>), TypeInfoPropertyName = "T6131")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_double?>, int>), TypeInfoPropertyName = "T6132")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_double?>>), TypeInfoPropertyName = "T6133")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_double?>>), TypeInfoPropertyName = "T6134")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_double?>>), TypeInfoPropertyName = "T6135")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_double?>>), TypeInfoPropertyName = "T6136")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_double?>, int>), TypeInfoPropertyName = "T6137")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_double?>), TypeInfoPropertyName = "T6138")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_double?>), TypeInfoPropertyName = "T6139")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_double?>), TypeInfoPropertyName = "T6140")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_double?>), TypeInfoPropertyName = "T6141")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_double?, int>), TypeInfoPropertyName = "T6142")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_double?>), TypeInfoPropertyName = "T6143")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_double?>), TypeInfoPropertyName = "T6144")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_double?>), TypeInfoPropertyName = "T6145")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_double?>), TypeInfoPropertyName = "T6146")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_double?, int>), TypeInfoPropertyName = "T6147")] +[JsonSerializable(typeof(V_validated_struct_bool?), TypeInfoPropertyName = "T6148")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_bool?>), TypeInfoPropertyName = "T6149")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_bool?>), TypeInfoPropertyName = "T6150")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_bool?>), TypeInfoPropertyName = "T6151")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_bool?>), TypeInfoPropertyName = "T6152")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_bool?, int>), TypeInfoPropertyName = "T6153")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6154")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6155")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6156")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6157")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_bool?>, int>), TypeInfoPropertyName = "T6158")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_bool?[]>), TypeInfoPropertyName = "T6159")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_bool?[]>), TypeInfoPropertyName = "T6160")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_bool?[]>), TypeInfoPropertyName = "T6161")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_bool?[]>), TypeInfoPropertyName = "T6162")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_bool?[], int>), TypeInfoPropertyName = "T6163")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6164")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6165")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6166")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6167")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_bool?>, int>), TypeInfoPropertyName = "T6168")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6169")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6170")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6171")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_bool?>>), TypeInfoPropertyName = "T6172")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_bool?>, int>), TypeInfoPropertyName = "T6173")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_bool?>>), TypeInfoPropertyName = "T6174")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_bool?>>), TypeInfoPropertyName = "T6175")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_bool?>>), TypeInfoPropertyName = "T6176")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_bool?>>), TypeInfoPropertyName = "T6177")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_bool?>, int>), TypeInfoPropertyName = "T6178")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_bool?>), TypeInfoPropertyName = "T6179")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_bool?>), TypeInfoPropertyName = "T6180")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_bool?>), TypeInfoPropertyName = "T6181")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_bool?>), TypeInfoPropertyName = "T6182")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_bool?, int>), TypeInfoPropertyName = "T6183")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_bool?>), TypeInfoPropertyName = "T6184")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_bool?>), TypeInfoPropertyName = "T6185")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_bool?>), TypeInfoPropertyName = "T6186")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_bool?>), TypeInfoPropertyName = "T6187")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_bool?, int>), TypeInfoPropertyName = "T6188")] +[JsonSerializable(typeof(V_validated_struct_char?), TypeInfoPropertyName = "T6189")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_char?>), TypeInfoPropertyName = "T6190")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_char?>), TypeInfoPropertyName = "T6191")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_char?>), TypeInfoPropertyName = "T6192")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_char?>), TypeInfoPropertyName = "T6193")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_char?, int>), TypeInfoPropertyName = "T6194")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_char?>>), TypeInfoPropertyName = "T6195")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_char?>>), TypeInfoPropertyName = "T6196")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_char?>>), TypeInfoPropertyName = "T6197")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_char?>>), TypeInfoPropertyName = "T6198")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_char?>, int>), TypeInfoPropertyName = "T6199")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_char?[]>), TypeInfoPropertyName = "T6200")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_char?[]>), TypeInfoPropertyName = "T6201")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_char?[]>), TypeInfoPropertyName = "T6202")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_char?[]>), TypeInfoPropertyName = "T6203")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_char?[], int>), TypeInfoPropertyName = "T6204")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_char?>>), TypeInfoPropertyName = "T6205")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_char?>>), TypeInfoPropertyName = "T6206")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_char?>>), TypeInfoPropertyName = "T6207")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_char?>>), TypeInfoPropertyName = "T6208")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_char?>, int>), TypeInfoPropertyName = "T6209")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_char?>>), TypeInfoPropertyName = "T6210")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_char?>>), TypeInfoPropertyName = "T6211")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_char?>>), TypeInfoPropertyName = "T6212")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_char?>>), TypeInfoPropertyName = "T6213")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_char?>, int>), TypeInfoPropertyName = "T6214")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_char?>>), TypeInfoPropertyName = "T6215")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_char?>>), TypeInfoPropertyName = "T6216")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_char?>>), TypeInfoPropertyName = "T6217")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_char?>>), TypeInfoPropertyName = "T6218")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_char?>, int>), TypeInfoPropertyName = "T6219")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_char?>), TypeInfoPropertyName = "T6220")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_char?>), TypeInfoPropertyName = "T6221")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_char?>), TypeInfoPropertyName = "T6222")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_char?>), TypeInfoPropertyName = "T6223")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_char?, int>), TypeInfoPropertyName = "T6224")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_char?>), TypeInfoPropertyName = "T6225")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_char?>), TypeInfoPropertyName = "T6226")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_char?>), TypeInfoPropertyName = "T6227")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_char?>), TypeInfoPropertyName = "T6228")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_char?, int>), TypeInfoPropertyName = "T6229")] +[JsonSerializable(typeof(V_validated_struct_DateTime?), TypeInfoPropertyName = "T6230")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6231")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6232")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6233")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6234")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateTime?, int>), TypeInfoPropertyName = "T6235")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6236")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6237")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6238")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6239")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateTime?>, int>), TypeInfoPropertyName = "T6240")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTime?[]>), TypeInfoPropertyName = "T6241")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTime?[]>), TypeInfoPropertyName = "T6242")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTime?[]>), TypeInfoPropertyName = "T6243")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTime?[]>), TypeInfoPropertyName = "T6244")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTime?[], int>), TypeInfoPropertyName = "T6245")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6246")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6247")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6248")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6249")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateTime?>, int>), TypeInfoPropertyName = "T6250")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6251")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6252")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6253")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6254")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateTime?>, int>), TypeInfoPropertyName = "T6255")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6256")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6257")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6258")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateTime?>>), TypeInfoPropertyName = "T6259")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateTime?>, int>), TypeInfoPropertyName = "T6260")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6261")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6262")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6263")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6264")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateTime?, int>), TypeInfoPropertyName = "T6265")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6266")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6267")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6268")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateTime?>), TypeInfoPropertyName = "T6269")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateTime?, int>), TypeInfoPropertyName = "T6270")] +[JsonSerializable(typeof(V_validated_struct_DateTimeOffset?), TypeInfoPropertyName = "T6271")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6272")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6273")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6274")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6275")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T6276")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6277")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6278")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6279")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6280")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T6281")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T6282")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T6283")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T6284")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateTimeOffset?[]>), TypeInfoPropertyName = "T6285")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateTimeOffset?[], int>), TypeInfoPropertyName = "T6286")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6287")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6288")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6289")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6290")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T6291")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6292")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6293")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6294")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6295")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T6296")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6297")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6298")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6299")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateTimeOffset?>>), TypeInfoPropertyName = "T6300")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateTimeOffset?>, int>), TypeInfoPropertyName = "T6301")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6302")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6303")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6304")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6305")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T6306")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6307")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6308")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6309")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateTimeOffset?>), TypeInfoPropertyName = "T6310")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateTimeOffset?, int>), TypeInfoPropertyName = "T6311")] +[JsonSerializable(typeof(V_validated_struct_DateOnly?), TypeInfoPropertyName = "T6312")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6313")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6314")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6315")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6316")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_DateOnly?, int>), TypeInfoPropertyName = "T6317")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6318")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6319")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6320")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6321")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_DateOnly?>, int>), TypeInfoPropertyName = "T6322")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_DateOnly?[]>), TypeInfoPropertyName = "T6323")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_DateOnly?[]>), TypeInfoPropertyName = "T6324")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_DateOnly?[]>), TypeInfoPropertyName = "T6325")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_DateOnly?[]>), TypeInfoPropertyName = "T6326")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_DateOnly?[], int>), TypeInfoPropertyName = "T6327")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6328")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6329")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6330")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6331")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_DateOnly?>, int>), TypeInfoPropertyName = "T6332")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6333")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6334")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6335")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6336")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_DateOnly?>, int>), TypeInfoPropertyName = "T6337")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6338")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6339")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6340")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_DateOnly?>>), TypeInfoPropertyName = "T6341")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_DateOnly?>, int>), TypeInfoPropertyName = "T6342")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6343")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6344")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6345")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6346")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_DateOnly?, int>), TypeInfoPropertyName = "T6347")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6348")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6349")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6350")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_DateOnly?>), TypeInfoPropertyName = "T6351")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_DateOnly?, int>), TypeInfoPropertyName = "T6352")] +[JsonSerializable(typeof(V_validated_struct_TimeOnly?), TypeInfoPropertyName = "T6353")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6354")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6355")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6356")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6357")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_TimeOnly?, int>), TypeInfoPropertyName = "T6358")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6359")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6360")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6361")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6362")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T6363")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeOnly?[]>), TypeInfoPropertyName = "T6364")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeOnly?[]>), TypeInfoPropertyName = "T6365")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeOnly?[]>), TypeInfoPropertyName = "T6366")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeOnly?[]>), TypeInfoPropertyName = "T6367")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeOnly?[], int>), TypeInfoPropertyName = "T6368")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6369")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6370")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6371")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6372")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T6373")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6374")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6375")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6376")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6377")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T6378")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6379")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6380")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6381")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_TimeOnly?>>), TypeInfoPropertyName = "T6382")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_TimeOnly?>, int>), TypeInfoPropertyName = "T6383")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6384")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6385")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6386")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6387")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_TimeOnly?, int>), TypeInfoPropertyName = "T6388")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6389")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6390")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6391")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_TimeOnly?>), TypeInfoPropertyName = "T6392")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_TimeOnly?, int>), TypeInfoPropertyName = "T6393")] +[JsonSerializable(typeof(V_validated_struct_TimeSpan?), TypeInfoPropertyName = "T6394")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6395")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6396")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6397")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6398")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_TimeSpan?, int>), TypeInfoPropertyName = "T6399")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6400")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6401")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6402")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6403")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T6404")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_TimeSpan?[]>), TypeInfoPropertyName = "T6405")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_TimeSpan?[]>), TypeInfoPropertyName = "T6406")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_TimeSpan?[]>), TypeInfoPropertyName = "T6407")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_TimeSpan?[]>), TypeInfoPropertyName = "T6408")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_TimeSpan?[], int>), TypeInfoPropertyName = "T6409")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6410")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6411")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6412")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6413")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T6414")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6415")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6416")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6417")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6418")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T6419")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6420")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6421")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6422")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_TimeSpan?>>), TypeInfoPropertyName = "T6423")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_TimeSpan?>, int>), TypeInfoPropertyName = "T6424")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6425")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6426")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6427")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6428")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_TimeSpan?, int>), TypeInfoPropertyName = "T6429")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6430")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6431")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6432")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_TimeSpan?>), TypeInfoPropertyName = "T6433")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_TimeSpan?, int>), TypeInfoPropertyName = "T6434")] +[JsonSerializable(typeof(V_validated_struct_enum?), TypeInfoPropertyName = "T6435")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_enum?>), TypeInfoPropertyName = "T6436")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_enum?>), TypeInfoPropertyName = "T6437")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_enum?>), TypeInfoPropertyName = "T6438")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_enum?>), TypeInfoPropertyName = "T6439")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_enum?, int>), TypeInfoPropertyName = "T6440")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6441")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6442")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6443")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6444")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_enum?>, int>), TypeInfoPropertyName = "T6445")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_enum?[]>), TypeInfoPropertyName = "T6446")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_enum?[]>), TypeInfoPropertyName = "T6447")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_enum?[]>), TypeInfoPropertyName = "T6448")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_enum?[]>), TypeInfoPropertyName = "T6449")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_enum?[], int>), TypeInfoPropertyName = "T6450")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6451")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6452")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6453")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6454")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_enum?>, int>), TypeInfoPropertyName = "T6455")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6456")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6457")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6458")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_enum?>>), TypeInfoPropertyName = "T6459")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_enum?>, int>), TypeInfoPropertyName = "T6460")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_enum?>>), TypeInfoPropertyName = "T6461")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_enum?>>), TypeInfoPropertyName = "T6462")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_enum?>>), TypeInfoPropertyName = "T6463")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_enum?>>), TypeInfoPropertyName = "T6464")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_enum?>, int>), TypeInfoPropertyName = "T6465")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_enum?>), TypeInfoPropertyName = "T6466")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_enum?>), TypeInfoPropertyName = "T6467")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_enum?>), TypeInfoPropertyName = "T6468")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_enum?>), TypeInfoPropertyName = "T6469")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_enum?, int>), TypeInfoPropertyName = "T6470")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_enum?>), TypeInfoPropertyName = "T6471")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_enum?>), TypeInfoPropertyName = "T6472")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_enum?>), TypeInfoPropertyName = "T6473")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_enum?>), TypeInfoPropertyName = "T6474")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_enum?, int>), TypeInfoPropertyName = "T6475")] +[JsonSerializable(typeof(V_validated_struct_flags_enum?), TypeInfoPropertyName = "T6476")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6477")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6478")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6479")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6480")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_flags_enum?, int>), TypeInfoPropertyName = "T6481")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6482")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6483")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6484")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6485")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_flags_enum?>, int>), TypeInfoPropertyName = "T6486")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_flags_enum?[]>), TypeInfoPropertyName = "T6487")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_flags_enum?[]>), TypeInfoPropertyName = "T6488")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_flags_enum?[]>), TypeInfoPropertyName = "T6489")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_flags_enum?[]>), TypeInfoPropertyName = "T6490")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_flags_enum?[], int>), TypeInfoPropertyName = "T6491")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6492")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6493")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6494")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6495")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_flags_enum?>, int>), TypeInfoPropertyName = "T6496")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6497")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6498")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6499")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6500")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_flags_enum?>, int>), TypeInfoPropertyName = "T6501")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6502")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6503")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6504")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_flags_enum?>>), TypeInfoPropertyName = "T6505")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_flags_enum?>, int>), TypeInfoPropertyName = "T6506")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6507")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6508")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6509")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6510")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_flags_enum?, int>), TypeInfoPropertyName = "T6511")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6512")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6513")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6514")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_flags_enum?>), TypeInfoPropertyName = "T6515")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_flags_enum?, int>), TypeInfoPropertyName = "T6516")] +[JsonSerializable(typeof(V_validated_struct_Uri?), TypeInfoPropertyName = "T6517")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6518")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6519")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6520")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6521")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_Uri?, int>), TypeInfoPropertyName = "T6522")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6523")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6524")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6525")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6526")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_Uri?>, int>), TypeInfoPropertyName = "T6527")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_Uri?[]>), TypeInfoPropertyName = "T6528")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_Uri?[]>), TypeInfoPropertyName = "T6529")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_Uri?[]>), TypeInfoPropertyName = "T6530")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_Uri?[]>), TypeInfoPropertyName = "T6531")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_Uri?[], int>), TypeInfoPropertyName = "T6532")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6533")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6534")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6535")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6536")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_Uri?>, int>), TypeInfoPropertyName = "T6537")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6538")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6539")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6540")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6541")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_Uri?>, int>), TypeInfoPropertyName = "T6542")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6543")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6544")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6545")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_Uri?>>), TypeInfoPropertyName = "T6546")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_Uri?>, int>), TypeInfoPropertyName = "T6547")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6548")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6549")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6550")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6551")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_Uri?, int>), TypeInfoPropertyName = "T6552")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6553")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6554")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6555")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_Uri?>), TypeInfoPropertyName = "T6556")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_Uri?, int>), TypeInfoPropertyName = "T6557")] +[JsonSerializable(typeof(V_validated_struct_NodaTime_Instant?), TypeInfoPropertyName = "T6558")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6559")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6560")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6561")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6562")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T6563")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6564")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6565")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6566")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6567")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T6568")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T6569")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T6570")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T6571")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_NodaTime_Instant?[]>), TypeInfoPropertyName = "T6572")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_NodaTime_Instant?[], int>), TypeInfoPropertyName = "T6573")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6574")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6575")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6576")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6577")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T6578")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6579")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6580")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6581")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6582")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T6583")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6584")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6585")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6586")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_NodaTime_Instant?>>), TypeInfoPropertyName = "T6587")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_NodaTime_Instant?>, int>), TypeInfoPropertyName = "T6588")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6589")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6590")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6591")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6592")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T6593")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6594")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6595")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6596")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_NodaTime_Instant?>), TypeInfoPropertyName = "T6597")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_NodaTime_Instant?, int>), TypeInfoPropertyName = "T6598")] +[JsonSerializable(typeof(V_validated_struct_own_record_class?), TypeInfoPropertyName = "T6599")] +[JsonSerializable(typeof(Cn_class_F<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6600")] +[JsonSerializable(typeof(Cn_record_class_F<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6601")] +[JsonSerializable(typeof(Cn_struct_F<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6602")] +[JsonSerializable(typeof(Cn_record_struct_F<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6603")] +[JsonSerializable(typeof(Cn_generic_type_F<V_validated_struct_own_record_class?, int>), TypeInfoPropertyName = "T6604")] +[JsonSerializable(typeof(Cn_class_P<List<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6605")] +[JsonSerializable(typeof(Cn_record_class_P<List<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6606")] +[JsonSerializable(typeof(Cn_struct_P<List<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6607")] +[JsonSerializable(typeof(Cn_record_struct_P<List<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6608")] +[JsonSerializable(typeof(Cn_generic_type_P<List<V_validated_struct_own_record_class?>, int>), TypeInfoPropertyName = "T6609")] +[JsonSerializable(typeof(Cn_class_P<V_validated_struct_own_record_class?[]>), TypeInfoPropertyName = "T6610")] +[JsonSerializable(typeof(Cn_record_class_P<V_validated_struct_own_record_class?[]>), TypeInfoPropertyName = "T6611")] +[JsonSerializable(typeof(Cn_struct_P<V_validated_struct_own_record_class?[]>), TypeInfoPropertyName = "T6612")] +[JsonSerializable(typeof(Cn_record_struct_P<V_validated_struct_own_record_class?[]>), TypeInfoPropertyName = "T6613")] +[JsonSerializable(typeof(Cn_generic_type_P<V_validated_struct_own_record_class?[], int>), TypeInfoPropertyName = "T6614")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6615")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6616")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6617")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6618")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<V_validated_struct_own_record_class?>, int>), TypeInfoPropertyName = "T6619")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6620")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6621")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6622")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6623")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<V_validated_struct_own_record_class?>, int>), TypeInfoPropertyName = "T6624")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6625")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6626")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6627")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, V_validated_struct_own_record_class?>>), TypeInfoPropertyName = "T6628")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, V_validated_struct_own_record_class?>, int>), TypeInfoPropertyName = "T6629")] +[JsonSerializable(typeof(Cn_class_C<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6630")] +[JsonSerializable(typeof(Cn_record_class_C<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6631")] +[JsonSerializable(typeof(Cn_struct_C<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6632")] +[JsonSerializable(typeof(Cn_record_struct_C<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6633")] +[JsonSerializable(typeof(Cn_generic_type_C<V_validated_struct_own_record_class?, int>), TypeInfoPropertyName = "T6634")] +[JsonSerializable(typeof(Cn_class_R<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6635")] +[JsonSerializable(typeof(Cn_record_class_R<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6636")] +[JsonSerializable(typeof(Cn_struct_R<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6637")] +[JsonSerializable(typeof(Cn_record_struct_R<V_validated_struct_own_record_class?>), TypeInfoPropertyName = "T6638")] +[JsonSerializable(typeof(Cn_generic_type_R<V_validated_struct_own_record_class?, int>), TypeInfoPropertyName = "T6639")] [JsonSerializable(typeof(PSet))] -[JsonSerializable(typeof(Cn_class_P<PSet>))] +[JsonSerializable(typeof(Cn_class_P<PSet>), TypeInfoPropertyName = "T6640")] +[JsonSerializable(typeof(Cn_record_class_P<PSet>), TypeInfoPropertyName = "T6641")] +[JsonSerializable(typeof(Cn_struct_P<PSet>), TypeInfoPropertyName = "T6642")] +[JsonSerializable(typeof(Cn_record_struct_P<PSet>), TypeInfoPropertyName = "T6643")] +[JsonSerializable(typeof(Cn_generic_type_P<PSet, int>), TypeInfoPropertyName = "T6644")] +[JsonSerializable(typeof(Cn_class_F<PSet>), TypeInfoPropertyName = "T6645")] +[JsonSerializable(typeof(Cn_record_class_F<PSet>), TypeInfoPropertyName = "T6646")] +[JsonSerializable(typeof(Cn_struct_F<PSet>), TypeInfoPropertyName = "T6647")] +[JsonSerializable(typeof(Cn_record_struct_F<PSet>), TypeInfoPropertyName = "T6648")] +[JsonSerializable(typeof(Cn_generic_type_F<PSet, int>), TypeInfoPropertyName = "T6649")] +[JsonSerializable(typeof(Cn_class_P<List<PSet>>), TypeInfoPropertyName = "T6650")] +[JsonSerializable(typeof(Cn_record_class_P<List<PSet>>), TypeInfoPropertyName = "T6651")] +[JsonSerializable(typeof(Cn_struct_P<List<PSet>>), TypeInfoPropertyName = "T6652")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PSet>>), TypeInfoPropertyName = "T6653")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PSet>, int>), TypeInfoPropertyName = "T6654")] +[JsonSerializable(typeof(Cn_class_P<PSet[]>), TypeInfoPropertyName = "T6655")] +[JsonSerializable(typeof(Cn_record_class_P<PSet[]>), TypeInfoPropertyName = "T6656")] +[JsonSerializable(typeof(Cn_struct_P<PSet[]>), TypeInfoPropertyName = "T6657")] +[JsonSerializable(typeof(Cn_record_struct_P<PSet[]>), TypeInfoPropertyName = "T6658")] +[JsonSerializable(typeof(Cn_generic_type_P<PSet[], int>), TypeInfoPropertyName = "T6659")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PSet>>), TypeInfoPropertyName = "T6660")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PSet>>), TypeInfoPropertyName = "T6661")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PSet>>), TypeInfoPropertyName = "T6662")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PSet>>), TypeInfoPropertyName = "T6663")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PSet>, int>), TypeInfoPropertyName = "T6664")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PSet>>), TypeInfoPropertyName = "T6665")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PSet>>), TypeInfoPropertyName = "T6666")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PSet>>), TypeInfoPropertyName = "T6667")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PSet>>), TypeInfoPropertyName = "T6668")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PSet>, int>), TypeInfoPropertyName = "T6669")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<PSet, int>>), TypeInfoPropertyName = "T6670")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<PSet, int>>), TypeInfoPropertyName = "T6671")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<PSet, int>>), TypeInfoPropertyName = "T6672")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<PSet, int>>), TypeInfoPropertyName = "T6673")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<PSet, int>, int>), TypeInfoPropertyName = "T6674")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PSet>>), TypeInfoPropertyName = "T6675")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PSet>>), TypeInfoPropertyName = "T6676")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PSet>>), TypeInfoPropertyName = "T6677")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PSet>>), TypeInfoPropertyName = "T6678")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PSet>, int>), TypeInfoPropertyName = "T6679")] +[JsonSerializable(typeof(Cn_class_C<PSet>), TypeInfoPropertyName = "T6680")] +[JsonSerializable(typeof(Cn_record_class_C<PSet>), TypeInfoPropertyName = "T6681")] +[JsonSerializable(typeof(Cn_struct_C<PSet>), TypeInfoPropertyName = "T6682")] +[JsonSerializable(typeof(Cn_record_struct_C<PSet>), TypeInfoPropertyName = "T6683")] +[JsonSerializable(typeof(Cn_generic_type_C<PSet, int>), TypeInfoPropertyName = "T6684")] +[JsonSerializable(typeof(Cn_class_R<PSet>), TypeInfoPropertyName = "T6685")] +[JsonSerializable(typeof(Cn_record_class_R<PSet>), TypeInfoPropertyName = "T6686")] +[JsonSerializable(typeof(Cn_struct_R<PSet>), TypeInfoPropertyName = "T6687")] +[JsonSerializable(typeof(Cn_record_struct_R<PSet>), TypeInfoPropertyName = "T6688")] +[JsonSerializable(typeof(Cn_generic_type_R<PSet, int>), TypeInfoPropertyName = "T6689")] +[JsonSerializable(typeof(Cn_class_P<Patch<PSet>>), TypeInfoPropertyName = "T6690")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<PSet>>), TypeInfoPropertyName = "T6691")] +[JsonSerializable(typeof(Cn_struct_P<Patch<PSet>>), TypeInfoPropertyName = "T6692")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<PSet>>), TypeInfoPropertyName = "T6693")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<PSet>, int>), TypeInfoPropertyName = "T6694")] +[JsonSerializable(typeof(Cn_class_P<PB_PSet>), TypeInfoPropertyName = "T6695")] +[JsonSerializable(typeof(Cn_record_class_P<PB_PSet>), TypeInfoPropertyName = "T6696")] +[JsonSerializable(typeof(Cn_struct_P<PB_PSet>), TypeInfoPropertyName = "T6697")] +[JsonSerializable(typeof(Cn_record_struct_P<PB_PSet>), TypeInfoPropertyName = "T6698")] +[JsonSerializable(typeof(Cn_generic_type_P<PB_PSet, int>), TypeInfoPropertyName = "T6699")] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(Cn_class_P<string>), TypeInfoPropertyName = "T6700")] +[JsonSerializable(typeof(Cn_record_class_P<string>), TypeInfoPropertyName = "T6701")] +[JsonSerializable(typeof(Cn_struct_P<string>), TypeInfoPropertyName = "T6702")] +[JsonSerializable(typeof(Cn_record_struct_P<string>), TypeInfoPropertyName = "T6703")] +[JsonSerializable(typeof(Cn_generic_type_P<string, int>), TypeInfoPropertyName = "T6704")] +[JsonSerializable(typeof(Cn_class_F<string>), TypeInfoPropertyName = "T6705")] +[JsonSerializable(typeof(Cn_record_class_F<string>), TypeInfoPropertyName = "T6706")] +[JsonSerializable(typeof(Cn_struct_F<string>), TypeInfoPropertyName = "T6707")] +[JsonSerializable(typeof(Cn_record_struct_F<string>), TypeInfoPropertyName = "T6708")] +[JsonSerializable(typeof(Cn_generic_type_F<string, int>), TypeInfoPropertyName = "T6709")] +[JsonSerializable(typeof(Cn_class_P<List<string>>), TypeInfoPropertyName = "T6710")] +[JsonSerializable(typeof(Cn_record_class_P<List<string>>), TypeInfoPropertyName = "T6711")] +[JsonSerializable(typeof(Cn_struct_P<List<string>>), TypeInfoPropertyName = "T6712")] +[JsonSerializable(typeof(Cn_record_struct_P<List<string>>), TypeInfoPropertyName = "T6713")] +[JsonSerializable(typeof(Cn_generic_type_P<List<string>, int>), TypeInfoPropertyName = "T6714")] +[JsonSerializable(typeof(Cn_class_P<string[]>), TypeInfoPropertyName = "T6715")] +[JsonSerializable(typeof(Cn_record_class_P<string[]>), TypeInfoPropertyName = "T6716")] +[JsonSerializable(typeof(Cn_struct_P<string[]>), TypeInfoPropertyName = "T6717")] +[JsonSerializable(typeof(Cn_record_struct_P<string[]>), TypeInfoPropertyName = "T6718")] +[JsonSerializable(typeof(Cn_generic_type_P<string[], int>), TypeInfoPropertyName = "T6719")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<string>>), TypeInfoPropertyName = "T6720")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<string>>), TypeInfoPropertyName = "T6721")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<string>>), TypeInfoPropertyName = "T6722")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<string>>), TypeInfoPropertyName = "T6723")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<string>, int>), TypeInfoPropertyName = "T6724")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<string>>), TypeInfoPropertyName = "T6725")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<string>>), TypeInfoPropertyName = "T6726")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<string>>), TypeInfoPropertyName = "T6727")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<string>>), TypeInfoPropertyName = "T6728")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<string>, int>), TypeInfoPropertyName = "T6729")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, int>>), TypeInfoPropertyName = "T6730")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, int>>), TypeInfoPropertyName = "T6731")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, int>>), TypeInfoPropertyName = "T6732")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, int>>), TypeInfoPropertyName = "T6733")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, int>, int>), TypeInfoPropertyName = "T6734")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, string>>), TypeInfoPropertyName = "T6735")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, string>>), TypeInfoPropertyName = "T6736")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, string>>), TypeInfoPropertyName = "T6737")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, string>>), TypeInfoPropertyName = "T6738")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, string>, int>), TypeInfoPropertyName = "T6739")] +[JsonSerializable(typeof(Cn_class_C<string>), TypeInfoPropertyName = "T6740")] +[JsonSerializable(typeof(Cn_record_class_C<string>), TypeInfoPropertyName = "T6741")] +[JsonSerializable(typeof(Cn_struct_C<string>), TypeInfoPropertyName = "T6742")] +[JsonSerializable(typeof(Cn_record_struct_C<string>), TypeInfoPropertyName = "T6743")] +[JsonSerializable(typeof(Cn_generic_type_C<string, int>), TypeInfoPropertyName = "T6744")] +[JsonSerializable(typeof(Cn_class_R<string>), TypeInfoPropertyName = "T6745")] +[JsonSerializable(typeof(Cn_record_class_R<string>), TypeInfoPropertyName = "T6746")] +[JsonSerializable(typeof(Cn_struct_R<string>), TypeInfoPropertyName = "T6747")] +[JsonSerializable(typeof(Cn_record_struct_R<string>), TypeInfoPropertyName = "T6748")] +[JsonSerializable(typeof(Cn_generic_type_R<string, int>), TypeInfoPropertyName = "T6749")] +[JsonSerializable(typeof(Cn_class_P<Patch<string>>), TypeInfoPropertyName = "T6750")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<string>>), TypeInfoPropertyName = "T6751")] +[JsonSerializable(typeof(Cn_struct_P<Patch<string>>), TypeInfoPropertyName = "T6752")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<string>>), TypeInfoPropertyName = "T6753")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<string>, int>), TypeInfoPropertyName = "T6754")] +[JsonSerializable(typeof(Cn_class_P<PBP_string>), TypeInfoPropertyName = "T6755")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_string>), TypeInfoPropertyName = "T6756")] +[JsonSerializable(typeof(Cn_struct_P<PBP_string>), TypeInfoPropertyName = "T6757")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_string>), TypeInfoPropertyName = "T6758")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_string, int>), TypeInfoPropertyName = "T6759")] +[JsonSerializable(typeof(Guid))] +[JsonSerializable(typeof(Cn_class_P<Guid>), TypeInfoPropertyName = "T6760")] +[JsonSerializable(typeof(Cn_record_class_P<Guid>), TypeInfoPropertyName = "T6761")] +[JsonSerializable(typeof(Cn_struct_P<Guid>), TypeInfoPropertyName = "T6762")] +[JsonSerializable(typeof(Cn_record_struct_P<Guid>), TypeInfoPropertyName = "T6763")] +[JsonSerializable(typeof(Cn_generic_type_P<Guid, int>), TypeInfoPropertyName = "T6764")] +[JsonSerializable(typeof(Cn_class_P<Guid?>), TypeInfoPropertyName = "T6765")] +[JsonSerializable(typeof(Cn_record_class_P<Guid?>), TypeInfoPropertyName = "T6766")] +[JsonSerializable(typeof(Cn_struct_P<Guid?>), TypeInfoPropertyName = "T6767")] +[JsonSerializable(typeof(Cn_record_struct_P<Guid?>), TypeInfoPropertyName = "T6768")] +[JsonSerializable(typeof(Cn_generic_type_P<Guid?, int>), TypeInfoPropertyName = "T6769")] +[JsonSerializable(typeof(Cn_class_F<Guid>), TypeInfoPropertyName = "T6770")] +[JsonSerializable(typeof(Cn_record_class_F<Guid>), TypeInfoPropertyName = "T6771")] +[JsonSerializable(typeof(Cn_struct_F<Guid>), TypeInfoPropertyName = "T6772")] +[JsonSerializable(typeof(Cn_record_struct_F<Guid>), TypeInfoPropertyName = "T6773")] +[JsonSerializable(typeof(Cn_generic_type_F<Guid, int>), TypeInfoPropertyName = "T6774")] +[JsonSerializable(typeof(Cn_class_P<List<Guid>>), TypeInfoPropertyName = "T6775")] +[JsonSerializable(typeof(Cn_record_class_P<List<Guid>>), TypeInfoPropertyName = "T6776")] +[JsonSerializable(typeof(Cn_struct_P<List<Guid>>), TypeInfoPropertyName = "T6777")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Guid>>), TypeInfoPropertyName = "T6778")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Guid>, int>), TypeInfoPropertyName = "T6779")] +[JsonSerializable(typeof(Cn_class_P<Guid[]>), TypeInfoPropertyName = "T6780")] +[JsonSerializable(typeof(Cn_record_class_P<Guid[]>), TypeInfoPropertyName = "T6781")] +[JsonSerializable(typeof(Cn_struct_P<Guid[]>), TypeInfoPropertyName = "T6782")] +[JsonSerializable(typeof(Cn_record_struct_P<Guid[]>), TypeInfoPropertyName = "T6783")] +[JsonSerializable(typeof(Cn_generic_type_P<Guid[], int>), TypeInfoPropertyName = "T6784")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<Guid>>), TypeInfoPropertyName = "T6785")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Guid>>), TypeInfoPropertyName = "T6786")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Guid>>), TypeInfoPropertyName = "T6787")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<Guid>>), TypeInfoPropertyName = "T6788")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<Guid>, int>), TypeInfoPropertyName = "T6789")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<Guid>>), TypeInfoPropertyName = "T6790")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<Guid>>), TypeInfoPropertyName = "T6791")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<Guid>>), TypeInfoPropertyName = "T6792")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<Guid>>), TypeInfoPropertyName = "T6793")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<Guid>, int>), TypeInfoPropertyName = "T6794")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<Guid, int>>), TypeInfoPropertyName = "T6795")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<Guid, int>>), TypeInfoPropertyName = "T6796")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<Guid, int>>), TypeInfoPropertyName = "T6797")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<Guid, int>>), TypeInfoPropertyName = "T6798")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<Guid, int>, int>), TypeInfoPropertyName = "T6799")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, Guid>>), TypeInfoPropertyName = "T6800")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, Guid>>), TypeInfoPropertyName = "T6801")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, Guid>>), TypeInfoPropertyName = "T6802")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, Guid>>), TypeInfoPropertyName = "T6803")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, Guid>, int>), TypeInfoPropertyName = "T6804")] +[JsonSerializable(typeof(Cn_class_C<Guid>), TypeInfoPropertyName = "T6805")] +[JsonSerializable(typeof(Cn_record_class_C<Guid>), TypeInfoPropertyName = "T6806")] +[JsonSerializable(typeof(Cn_struct_C<Guid>), TypeInfoPropertyName = "T6807")] +[JsonSerializable(typeof(Cn_record_struct_C<Guid>), TypeInfoPropertyName = "T6808")] +[JsonSerializable(typeof(Cn_generic_type_C<Guid, int>), TypeInfoPropertyName = "T6809")] +[JsonSerializable(typeof(Cn_class_R<Guid>), TypeInfoPropertyName = "T6810")] +[JsonSerializable(typeof(Cn_record_class_R<Guid>), TypeInfoPropertyName = "T6811")] +[JsonSerializable(typeof(Cn_struct_R<Guid>), TypeInfoPropertyName = "T6812")] +[JsonSerializable(typeof(Cn_record_struct_R<Guid>), TypeInfoPropertyName = "T6813")] +[JsonSerializable(typeof(Cn_generic_type_R<Guid, int>), TypeInfoPropertyName = "T6814")] +[JsonSerializable(typeof(Cn_class_P<Patch<Guid>>), TypeInfoPropertyName = "T6815")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<Guid>>), TypeInfoPropertyName = "T6816")] +[JsonSerializable(typeof(Cn_struct_P<Patch<Guid>>), TypeInfoPropertyName = "T6817")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<Guid>>), TypeInfoPropertyName = "T6818")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<Guid>, int>), TypeInfoPropertyName = "T6819")] +[JsonSerializable(typeof(Cn_class_P<PBP_Guid>), TypeInfoPropertyName = "T6820")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_Guid>), TypeInfoPropertyName = "T6821")] +[JsonSerializable(typeof(Cn_struct_P<PBP_Guid>), TypeInfoPropertyName = "T6822")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_Guid>), TypeInfoPropertyName = "T6823")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_Guid, int>), TypeInfoPropertyName = "T6824")] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(Cn_class_P<int>), TypeInfoPropertyName = "T6825")] +[JsonSerializable(typeof(Cn_record_class_P<int>), TypeInfoPropertyName = "T6826")] +[JsonSerializable(typeof(Cn_struct_P<int>), TypeInfoPropertyName = "T6827")] +[JsonSerializable(typeof(Cn_record_struct_P<int>), TypeInfoPropertyName = "T6828")] +[JsonSerializable(typeof(Cn_generic_type_P<int, int>), TypeInfoPropertyName = "T6829")] +[JsonSerializable(typeof(Cn_class_P<int?>), TypeInfoPropertyName = "T6830")] +[JsonSerializable(typeof(Cn_record_class_P<int?>), TypeInfoPropertyName = "T6831")] +[JsonSerializable(typeof(Cn_struct_P<int?>), TypeInfoPropertyName = "T6832")] +[JsonSerializable(typeof(Cn_record_struct_P<int?>), TypeInfoPropertyName = "T6833")] +[JsonSerializable(typeof(Cn_generic_type_P<int?, int>), TypeInfoPropertyName = "T6834")] +[JsonSerializable(typeof(Cn_class_F<int>), TypeInfoPropertyName = "T6835")] +[JsonSerializable(typeof(Cn_record_class_F<int>), TypeInfoPropertyName = "T6836")] +[JsonSerializable(typeof(Cn_struct_F<int>), TypeInfoPropertyName = "T6837")] +[JsonSerializable(typeof(Cn_record_struct_F<int>), TypeInfoPropertyName = "T6838")] +[JsonSerializable(typeof(Cn_generic_type_F<int, int>), TypeInfoPropertyName = "T6839")] +[JsonSerializable(typeof(Cn_class_P<List<int>>), TypeInfoPropertyName = "T6840")] +[JsonSerializable(typeof(Cn_record_class_P<List<int>>), TypeInfoPropertyName = "T6841")] +[JsonSerializable(typeof(Cn_struct_P<List<int>>), TypeInfoPropertyName = "T6842")] +[JsonSerializable(typeof(Cn_record_struct_P<List<int>>), TypeInfoPropertyName = "T6843")] +[JsonSerializable(typeof(Cn_generic_type_P<List<int>, int>), TypeInfoPropertyName = "T6844")] +[JsonSerializable(typeof(Cn_class_P<int[]>), TypeInfoPropertyName = "T6845")] +[JsonSerializable(typeof(Cn_record_class_P<int[]>), TypeInfoPropertyName = "T6846")] +[JsonSerializable(typeof(Cn_struct_P<int[]>), TypeInfoPropertyName = "T6847")] +[JsonSerializable(typeof(Cn_record_struct_P<int[]>), TypeInfoPropertyName = "T6848")] +[JsonSerializable(typeof(Cn_generic_type_P<int[], int>), TypeInfoPropertyName = "T6849")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<int>>), TypeInfoPropertyName = "T6850")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<int>>), TypeInfoPropertyName = "T6851")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<int>>), TypeInfoPropertyName = "T6852")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<int>>), TypeInfoPropertyName = "T6853")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<int>, int>), TypeInfoPropertyName = "T6854")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<int>>), TypeInfoPropertyName = "T6855")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<int>>), TypeInfoPropertyName = "T6856")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<int>>), TypeInfoPropertyName = "T6857")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<int>>), TypeInfoPropertyName = "T6858")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<int>, int>), TypeInfoPropertyName = "T6859")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<int, int>>), TypeInfoPropertyName = "T6860")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<int, int>>), TypeInfoPropertyName = "T6861")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<int, int>>), TypeInfoPropertyName = "T6862")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<int, int>>), TypeInfoPropertyName = "T6863")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<int, int>, int>), TypeInfoPropertyName = "T6864")] +[JsonSerializable(typeof(Cn_class_C<int>), TypeInfoPropertyName = "T6865")] +[JsonSerializable(typeof(Cn_record_class_C<int>), TypeInfoPropertyName = "T6866")] +[JsonSerializable(typeof(Cn_struct_C<int>), TypeInfoPropertyName = "T6867")] +[JsonSerializable(typeof(Cn_record_struct_C<int>), TypeInfoPropertyName = "T6868")] +[JsonSerializable(typeof(Cn_generic_type_C<int, int>), TypeInfoPropertyName = "T6869")] +[JsonSerializable(typeof(Cn_class_R<int>), TypeInfoPropertyName = "T6870")] +[JsonSerializable(typeof(Cn_record_class_R<int>), TypeInfoPropertyName = "T6871")] +[JsonSerializable(typeof(Cn_struct_R<int>), TypeInfoPropertyName = "T6872")] +[JsonSerializable(typeof(Cn_record_struct_R<int>), TypeInfoPropertyName = "T6873")] +[JsonSerializable(typeof(Cn_generic_type_R<int, int>), TypeInfoPropertyName = "T6874")] +[JsonSerializable(typeof(Cn_class_P<Patch<int>>), TypeInfoPropertyName = "T6875")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<int>>), TypeInfoPropertyName = "T6876")] +[JsonSerializable(typeof(Cn_struct_P<Patch<int>>), TypeInfoPropertyName = "T6877")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<int>>), TypeInfoPropertyName = "T6878")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<int>, int>), TypeInfoPropertyName = "T6879")] +[JsonSerializable(typeof(Cn_class_P<PBP_int>), TypeInfoPropertyName = "T6880")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_int>), TypeInfoPropertyName = "T6881")] +[JsonSerializable(typeof(Cn_struct_P<PBP_int>), TypeInfoPropertyName = "T6882")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_int>), TypeInfoPropertyName = "T6883")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_int, int>), TypeInfoPropertyName = "T6884")] +[JsonSerializable(typeof(long))] +[JsonSerializable(typeof(Cn_class_P<long>), TypeInfoPropertyName = "T6885")] +[JsonSerializable(typeof(Cn_record_class_P<long>), TypeInfoPropertyName = "T6886")] +[JsonSerializable(typeof(Cn_struct_P<long>), TypeInfoPropertyName = "T6887")] +[JsonSerializable(typeof(Cn_record_struct_P<long>), TypeInfoPropertyName = "T6888")] +[JsonSerializable(typeof(Cn_generic_type_P<long, int>), TypeInfoPropertyName = "T6889")] +[JsonSerializable(typeof(Cn_class_P<long?>), TypeInfoPropertyName = "T6890")] +[JsonSerializable(typeof(Cn_record_class_P<long?>), TypeInfoPropertyName = "T6891")] +[JsonSerializable(typeof(Cn_struct_P<long?>), TypeInfoPropertyName = "T6892")] +[JsonSerializable(typeof(Cn_record_struct_P<long?>), TypeInfoPropertyName = "T6893")] +[JsonSerializable(typeof(Cn_generic_type_P<long?, int>), TypeInfoPropertyName = "T6894")] +[JsonSerializable(typeof(Cn_class_F<long>), TypeInfoPropertyName = "T6895")] +[JsonSerializable(typeof(Cn_record_class_F<long>), TypeInfoPropertyName = "T6896")] +[JsonSerializable(typeof(Cn_struct_F<long>), TypeInfoPropertyName = "T6897")] +[JsonSerializable(typeof(Cn_record_struct_F<long>), TypeInfoPropertyName = "T6898")] +[JsonSerializable(typeof(Cn_generic_type_F<long, int>), TypeInfoPropertyName = "T6899")] +[JsonSerializable(typeof(Cn_class_P<List<long>>), TypeInfoPropertyName = "T6900")] +[JsonSerializable(typeof(Cn_record_class_P<List<long>>), TypeInfoPropertyName = "T6901")] +[JsonSerializable(typeof(Cn_struct_P<List<long>>), TypeInfoPropertyName = "T6902")] +[JsonSerializable(typeof(Cn_record_struct_P<List<long>>), TypeInfoPropertyName = "T6903")] +[JsonSerializable(typeof(Cn_generic_type_P<List<long>, int>), TypeInfoPropertyName = "T6904")] +[JsonSerializable(typeof(Cn_class_P<long[]>), TypeInfoPropertyName = "T6905")] +[JsonSerializable(typeof(Cn_record_class_P<long[]>), TypeInfoPropertyName = "T6906")] +[JsonSerializable(typeof(Cn_struct_P<long[]>), TypeInfoPropertyName = "T6907")] +[JsonSerializable(typeof(Cn_record_struct_P<long[]>), TypeInfoPropertyName = "T6908")] +[JsonSerializable(typeof(Cn_generic_type_P<long[], int>), TypeInfoPropertyName = "T6909")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<long>>), TypeInfoPropertyName = "T6910")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<long>>), TypeInfoPropertyName = "T6911")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<long>>), TypeInfoPropertyName = "T6912")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<long>>), TypeInfoPropertyName = "T6913")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<long>, int>), TypeInfoPropertyName = "T6914")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<long>>), TypeInfoPropertyName = "T6915")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<long>>), TypeInfoPropertyName = "T6916")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<long>>), TypeInfoPropertyName = "T6917")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<long>>), TypeInfoPropertyName = "T6918")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<long>, int>), TypeInfoPropertyName = "T6919")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<long, int>>), TypeInfoPropertyName = "T6920")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<long, int>>), TypeInfoPropertyName = "T6921")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<long, int>>), TypeInfoPropertyName = "T6922")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<long, int>>), TypeInfoPropertyName = "T6923")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<long, int>, int>), TypeInfoPropertyName = "T6924")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, long>>), TypeInfoPropertyName = "T6925")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, long>>), TypeInfoPropertyName = "T6926")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, long>>), TypeInfoPropertyName = "T6927")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, long>>), TypeInfoPropertyName = "T6928")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, long>, int>), TypeInfoPropertyName = "T6929")] +[JsonSerializable(typeof(Cn_class_C<long>), TypeInfoPropertyName = "T6930")] +[JsonSerializable(typeof(Cn_record_class_C<long>), TypeInfoPropertyName = "T6931")] +[JsonSerializable(typeof(Cn_struct_C<long>), TypeInfoPropertyName = "T6932")] +[JsonSerializable(typeof(Cn_record_struct_C<long>), TypeInfoPropertyName = "T6933")] +[JsonSerializable(typeof(Cn_generic_type_C<long, int>), TypeInfoPropertyName = "T6934")] +[JsonSerializable(typeof(Cn_class_R<long>), TypeInfoPropertyName = "T6935")] +[JsonSerializable(typeof(Cn_record_class_R<long>), TypeInfoPropertyName = "T6936")] +[JsonSerializable(typeof(Cn_struct_R<long>), TypeInfoPropertyName = "T6937")] +[JsonSerializable(typeof(Cn_record_struct_R<long>), TypeInfoPropertyName = "T6938")] +[JsonSerializable(typeof(Cn_generic_type_R<long, int>), TypeInfoPropertyName = "T6939")] +[JsonSerializable(typeof(Cn_class_P<Patch<long>>), TypeInfoPropertyName = "T6940")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<long>>), TypeInfoPropertyName = "T6941")] +[JsonSerializable(typeof(Cn_struct_P<Patch<long>>), TypeInfoPropertyName = "T6942")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<long>>), TypeInfoPropertyName = "T6943")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<long>, int>), TypeInfoPropertyName = "T6944")] +[JsonSerializable(typeof(Cn_class_P<PBP_long>), TypeInfoPropertyName = "T6945")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_long>), TypeInfoPropertyName = "T6946")] +[JsonSerializable(typeof(Cn_struct_P<PBP_long>), TypeInfoPropertyName = "T6947")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_long>), TypeInfoPropertyName = "T6948")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_long, int>), TypeInfoPropertyName = "T6949")] +[JsonSerializable(typeof(byte))] +[JsonSerializable(typeof(Cn_class_P<byte>), TypeInfoPropertyName = "T6950")] +[JsonSerializable(typeof(Cn_record_class_P<byte>), TypeInfoPropertyName = "T6951")] +[JsonSerializable(typeof(Cn_struct_P<byte>), TypeInfoPropertyName = "T6952")] +[JsonSerializable(typeof(Cn_record_struct_P<byte>), TypeInfoPropertyName = "T6953")] +[JsonSerializable(typeof(Cn_generic_type_P<byte, int>), TypeInfoPropertyName = "T6954")] +[JsonSerializable(typeof(Cn_class_P<byte?>), TypeInfoPropertyName = "T6955")] +[JsonSerializable(typeof(Cn_record_class_P<byte?>), TypeInfoPropertyName = "T6956")] +[JsonSerializable(typeof(Cn_struct_P<byte?>), TypeInfoPropertyName = "T6957")] +[JsonSerializable(typeof(Cn_record_struct_P<byte?>), TypeInfoPropertyName = "T6958")] +[JsonSerializable(typeof(Cn_generic_type_P<byte?, int>), TypeInfoPropertyName = "T6959")] +[JsonSerializable(typeof(Cn_class_F<byte>), TypeInfoPropertyName = "T6960")] +[JsonSerializable(typeof(Cn_record_class_F<byte>), TypeInfoPropertyName = "T6961")] +[JsonSerializable(typeof(Cn_struct_F<byte>), TypeInfoPropertyName = "T6962")] +[JsonSerializable(typeof(Cn_record_struct_F<byte>), TypeInfoPropertyName = "T6963")] +[JsonSerializable(typeof(Cn_generic_type_F<byte, int>), TypeInfoPropertyName = "T6964")] +[JsonSerializable(typeof(Cn_class_P<List<byte>>), TypeInfoPropertyName = "T6965")] +[JsonSerializable(typeof(Cn_record_class_P<List<byte>>), TypeInfoPropertyName = "T6966")] +[JsonSerializable(typeof(Cn_struct_P<List<byte>>), TypeInfoPropertyName = "T6967")] +[JsonSerializable(typeof(Cn_record_struct_P<List<byte>>), TypeInfoPropertyName = "T6968")] +[JsonSerializable(typeof(Cn_generic_type_P<List<byte>, int>), TypeInfoPropertyName = "T6969")] +[JsonSerializable(typeof(Cn_class_P<byte[]>), TypeInfoPropertyName = "T6970")] +[JsonSerializable(typeof(Cn_record_class_P<byte[]>), TypeInfoPropertyName = "T6971")] +[JsonSerializable(typeof(Cn_struct_P<byte[]>), TypeInfoPropertyName = "T6972")] +[JsonSerializable(typeof(Cn_record_struct_P<byte[]>), TypeInfoPropertyName = "T6973")] +[JsonSerializable(typeof(Cn_generic_type_P<byte[], int>), TypeInfoPropertyName = "T6974")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<byte>>), TypeInfoPropertyName = "T6975")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<byte>>), TypeInfoPropertyName = "T6976")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<byte>>), TypeInfoPropertyName = "T6977")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<byte>>), TypeInfoPropertyName = "T6978")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<byte>, int>), TypeInfoPropertyName = "T6979")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<byte>>), TypeInfoPropertyName = "T6980")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<byte>>), TypeInfoPropertyName = "T6981")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<byte>>), TypeInfoPropertyName = "T6982")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<byte>>), TypeInfoPropertyName = "T6983")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<byte>, int>), TypeInfoPropertyName = "T6984")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<byte, int>>), TypeInfoPropertyName = "T6985")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<byte, int>>), TypeInfoPropertyName = "T6986")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<byte, int>>), TypeInfoPropertyName = "T6987")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<byte, int>>), TypeInfoPropertyName = "T6988")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<byte, int>, int>), TypeInfoPropertyName = "T6989")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, byte>>), TypeInfoPropertyName = "T6990")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, byte>>), TypeInfoPropertyName = "T6991")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, byte>>), TypeInfoPropertyName = "T6992")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, byte>>), TypeInfoPropertyName = "T6993")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, byte>, int>), TypeInfoPropertyName = "T6994")] +[JsonSerializable(typeof(Cn_class_C<byte>), TypeInfoPropertyName = "T6995")] +[JsonSerializable(typeof(Cn_record_class_C<byte>), TypeInfoPropertyName = "T6996")] +[JsonSerializable(typeof(Cn_struct_C<byte>), TypeInfoPropertyName = "T6997")] +[JsonSerializable(typeof(Cn_record_struct_C<byte>), TypeInfoPropertyName = "T6998")] +[JsonSerializable(typeof(Cn_generic_type_C<byte, int>), TypeInfoPropertyName = "T6999")] +[JsonSerializable(typeof(Cn_class_R<byte>), TypeInfoPropertyName = "T7000")] +[JsonSerializable(typeof(Cn_record_class_R<byte>), TypeInfoPropertyName = "T7001")] +[JsonSerializable(typeof(Cn_struct_R<byte>), TypeInfoPropertyName = "T7002")] +[JsonSerializable(typeof(Cn_record_struct_R<byte>), TypeInfoPropertyName = "T7003")] +[JsonSerializable(typeof(Cn_generic_type_R<byte, int>), TypeInfoPropertyName = "T7004")] +[JsonSerializable(typeof(Cn_class_P<Patch<byte>>), TypeInfoPropertyName = "T7005")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<byte>>), TypeInfoPropertyName = "T7006")] +[JsonSerializable(typeof(Cn_struct_P<Patch<byte>>), TypeInfoPropertyName = "T7007")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<byte>>), TypeInfoPropertyName = "T7008")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<byte>, int>), TypeInfoPropertyName = "T7009")] +[JsonSerializable(typeof(Cn_class_P<PBP_byte>), TypeInfoPropertyName = "T7010")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_byte>), TypeInfoPropertyName = "T7011")] +[JsonSerializable(typeof(Cn_struct_P<PBP_byte>), TypeInfoPropertyName = "T7012")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_byte>), TypeInfoPropertyName = "T7013")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_byte, int>), TypeInfoPropertyName = "T7014")] +[JsonSerializable(typeof(ulong))] +[JsonSerializable(typeof(Cn_class_P<ulong>), TypeInfoPropertyName = "T7015")] +[JsonSerializable(typeof(Cn_record_class_P<ulong>), TypeInfoPropertyName = "T7016")] +[JsonSerializable(typeof(Cn_struct_P<ulong>), TypeInfoPropertyName = "T7017")] +[JsonSerializable(typeof(Cn_record_struct_P<ulong>), TypeInfoPropertyName = "T7018")] +[JsonSerializable(typeof(Cn_generic_type_P<ulong, int>), TypeInfoPropertyName = "T7019")] +[JsonSerializable(typeof(Cn_class_P<ulong?>), TypeInfoPropertyName = "T7020")] +[JsonSerializable(typeof(Cn_record_class_P<ulong?>), TypeInfoPropertyName = "T7021")] +[JsonSerializable(typeof(Cn_struct_P<ulong?>), TypeInfoPropertyName = "T7022")] +[JsonSerializable(typeof(Cn_record_struct_P<ulong?>), TypeInfoPropertyName = "T7023")] +[JsonSerializable(typeof(Cn_generic_type_P<ulong?, int>), TypeInfoPropertyName = "T7024")] +[JsonSerializable(typeof(Cn_class_F<ulong>), TypeInfoPropertyName = "T7025")] +[JsonSerializable(typeof(Cn_record_class_F<ulong>), TypeInfoPropertyName = "T7026")] +[JsonSerializable(typeof(Cn_struct_F<ulong>), TypeInfoPropertyName = "T7027")] +[JsonSerializable(typeof(Cn_record_struct_F<ulong>), TypeInfoPropertyName = "T7028")] +[JsonSerializable(typeof(Cn_generic_type_F<ulong, int>), TypeInfoPropertyName = "T7029")] +[JsonSerializable(typeof(Cn_class_P<List<ulong>>), TypeInfoPropertyName = "T7030")] +[JsonSerializable(typeof(Cn_record_class_P<List<ulong>>), TypeInfoPropertyName = "T7031")] +[JsonSerializable(typeof(Cn_struct_P<List<ulong>>), TypeInfoPropertyName = "T7032")] +[JsonSerializable(typeof(Cn_record_struct_P<List<ulong>>), TypeInfoPropertyName = "T7033")] +[JsonSerializable(typeof(Cn_generic_type_P<List<ulong>, int>), TypeInfoPropertyName = "T7034")] +[JsonSerializable(typeof(Cn_class_P<ulong[]>), TypeInfoPropertyName = "T7035")] +[JsonSerializable(typeof(Cn_record_class_P<ulong[]>), TypeInfoPropertyName = "T7036")] +[JsonSerializable(typeof(Cn_struct_P<ulong[]>), TypeInfoPropertyName = "T7037")] +[JsonSerializable(typeof(Cn_record_struct_P<ulong[]>), TypeInfoPropertyName = "T7038")] +[JsonSerializable(typeof(Cn_generic_type_P<ulong[], int>), TypeInfoPropertyName = "T7039")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<ulong>>), TypeInfoPropertyName = "T7040")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<ulong>>), TypeInfoPropertyName = "T7041")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<ulong>>), TypeInfoPropertyName = "T7042")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<ulong>>), TypeInfoPropertyName = "T7043")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<ulong>, int>), TypeInfoPropertyName = "T7044")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<ulong>>), TypeInfoPropertyName = "T7045")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<ulong>>), TypeInfoPropertyName = "T7046")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<ulong>>), TypeInfoPropertyName = "T7047")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<ulong>>), TypeInfoPropertyName = "T7048")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<ulong>, int>), TypeInfoPropertyName = "T7049")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<ulong, int>>), TypeInfoPropertyName = "T7050")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<ulong, int>>), TypeInfoPropertyName = "T7051")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<ulong, int>>), TypeInfoPropertyName = "T7052")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<ulong, int>>), TypeInfoPropertyName = "T7053")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<ulong, int>, int>), TypeInfoPropertyName = "T7054")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, ulong>>), TypeInfoPropertyName = "T7055")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, ulong>>), TypeInfoPropertyName = "T7056")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, ulong>>), TypeInfoPropertyName = "T7057")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, ulong>>), TypeInfoPropertyName = "T7058")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, ulong>, int>), TypeInfoPropertyName = "T7059")] +[JsonSerializable(typeof(Cn_class_C<ulong>), TypeInfoPropertyName = "T7060")] +[JsonSerializable(typeof(Cn_record_class_C<ulong>), TypeInfoPropertyName = "T7061")] +[JsonSerializable(typeof(Cn_struct_C<ulong>), TypeInfoPropertyName = "T7062")] +[JsonSerializable(typeof(Cn_record_struct_C<ulong>), TypeInfoPropertyName = "T7063")] +[JsonSerializable(typeof(Cn_generic_type_C<ulong, int>), TypeInfoPropertyName = "T7064")] +[JsonSerializable(typeof(Cn_class_R<ulong>), TypeInfoPropertyName = "T7065")] +[JsonSerializable(typeof(Cn_record_class_R<ulong>), TypeInfoPropertyName = "T7066")] +[JsonSerializable(typeof(Cn_struct_R<ulong>), TypeInfoPropertyName = "T7067")] +[JsonSerializable(typeof(Cn_record_struct_R<ulong>), TypeInfoPropertyName = "T7068")] +[JsonSerializable(typeof(Cn_generic_type_R<ulong, int>), TypeInfoPropertyName = "T7069")] +[JsonSerializable(typeof(Cn_class_P<Patch<ulong>>), TypeInfoPropertyName = "T7070")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<ulong>>), TypeInfoPropertyName = "T7071")] +[JsonSerializable(typeof(Cn_struct_P<Patch<ulong>>), TypeInfoPropertyName = "T7072")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<ulong>>), TypeInfoPropertyName = "T7073")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<ulong>, int>), TypeInfoPropertyName = "T7074")] +[JsonSerializable(typeof(Cn_class_P<PBP_ulong>), TypeInfoPropertyName = "T7075")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_ulong>), TypeInfoPropertyName = "T7076")] +[JsonSerializable(typeof(Cn_struct_P<PBP_ulong>), TypeInfoPropertyName = "T7077")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_ulong>), TypeInfoPropertyName = "T7078")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_ulong, int>), TypeInfoPropertyName = "T7079")] +[JsonSerializable(typeof(decimal))] +[JsonSerializable(typeof(Cn_class_P<decimal>), TypeInfoPropertyName = "T7080")] +[JsonSerializable(typeof(Cn_record_class_P<decimal>), TypeInfoPropertyName = "T7081")] +[JsonSerializable(typeof(Cn_struct_P<decimal>), TypeInfoPropertyName = "T7082")] +[JsonSerializable(typeof(Cn_record_struct_P<decimal>), TypeInfoPropertyName = "T7083")] +[JsonSerializable(typeof(Cn_generic_type_P<decimal, int>), TypeInfoPropertyName = "T7084")] +[JsonSerializable(typeof(Cn_class_P<decimal?>), TypeInfoPropertyName = "T7085")] +[JsonSerializable(typeof(Cn_record_class_P<decimal?>), TypeInfoPropertyName = "T7086")] +[JsonSerializable(typeof(Cn_struct_P<decimal?>), TypeInfoPropertyName = "T7087")] +[JsonSerializable(typeof(Cn_record_struct_P<decimal?>), TypeInfoPropertyName = "T7088")] +[JsonSerializable(typeof(Cn_generic_type_P<decimal?, int>), TypeInfoPropertyName = "T7089")] +[JsonSerializable(typeof(Cn_class_F<decimal>), TypeInfoPropertyName = "T7090")] +[JsonSerializable(typeof(Cn_record_class_F<decimal>), TypeInfoPropertyName = "T7091")] +[JsonSerializable(typeof(Cn_struct_F<decimal>), TypeInfoPropertyName = "T7092")] +[JsonSerializable(typeof(Cn_record_struct_F<decimal>), TypeInfoPropertyName = "T7093")] +[JsonSerializable(typeof(Cn_generic_type_F<decimal, int>), TypeInfoPropertyName = "T7094")] +[JsonSerializable(typeof(Cn_class_P<List<decimal>>), TypeInfoPropertyName = "T7095")] +[JsonSerializable(typeof(Cn_record_class_P<List<decimal>>), TypeInfoPropertyName = "T7096")] +[JsonSerializable(typeof(Cn_struct_P<List<decimal>>), TypeInfoPropertyName = "T7097")] +[JsonSerializable(typeof(Cn_record_struct_P<List<decimal>>), TypeInfoPropertyName = "T7098")] +[JsonSerializable(typeof(Cn_generic_type_P<List<decimal>, int>), TypeInfoPropertyName = "T7099")] +[JsonSerializable(typeof(Cn_class_P<decimal[]>), TypeInfoPropertyName = "T7100")] +[JsonSerializable(typeof(Cn_record_class_P<decimal[]>), TypeInfoPropertyName = "T7101")] +[JsonSerializable(typeof(Cn_struct_P<decimal[]>), TypeInfoPropertyName = "T7102")] +[JsonSerializable(typeof(Cn_record_struct_P<decimal[]>), TypeInfoPropertyName = "T7103")] +[JsonSerializable(typeof(Cn_generic_type_P<decimal[], int>), TypeInfoPropertyName = "T7104")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<decimal>>), TypeInfoPropertyName = "T7105")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<decimal>>), TypeInfoPropertyName = "T7106")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<decimal>>), TypeInfoPropertyName = "T7107")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<decimal>>), TypeInfoPropertyName = "T7108")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<decimal>, int>), TypeInfoPropertyName = "T7109")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<decimal>>), TypeInfoPropertyName = "T7110")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<decimal>>), TypeInfoPropertyName = "T7111")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<decimal>>), TypeInfoPropertyName = "T7112")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<decimal>>), TypeInfoPropertyName = "T7113")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<decimal>, int>), TypeInfoPropertyName = "T7114")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<decimal, int>>), TypeInfoPropertyName = "T7115")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<decimal, int>>), TypeInfoPropertyName = "T7116")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<decimal, int>>), TypeInfoPropertyName = "T7117")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<decimal, int>>), TypeInfoPropertyName = "T7118")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<decimal, int>, int>), TypeInfoPropertyName = "T7119")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, decimal>>), TypeInfoPropertyName = "T7120")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, decimal>>), TypeInfoPropertyName = "T7121")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, decimal>>), TypeInfoPropertyName = "T7122")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, decimal>>), TypeInfoPropertyName = "T7123")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, decimal>, int>), TypeInfoPropertyName = "T7124")] +[JsonSerializable(typeof(Cn_class_C<decimal>), TypeInfoPropertyName = "T7125")] +[JsonSerializable(typeof(Cn_record_class_C<decimal>), TypeInfoPropertyName = "T7126")] +[JsonSerializable(typeof(Cn_struct_C<decimal>), TypeInfoPropertyName = "T7127")] +[JsonSerializable(typeof(Cn_record_struct_C<decimal>), TypeInfoPropertyName = "T7128")] +[JsonSerializable(typeof(Cn_generic_type_C<decimal, int>), TypeInfoPropertyName = "T7129")] +[JsonSerializable(typeof(Cn_class_R<decimal>), TypeInfoPropertyName = "T7130")] +[JsonSerializable(typeof(Cn_record_class_R<decimal>), TypeInfoPropertyName = "T7131")] +[JsonSerializable(typeof(Cn_struct_R<decimal>), TypeInfoPropertyName = "T7132")] +[JsonSerializable(typeof(Cn_record_struct_R<decimal>), TypeInfoPropertyName = "T7133")] +[JsonSerializable(typeof(Cn_generic_type_R<decimal, int>), TypeInfoPropertyName = "T7134")] +[JsonSerializable(typeof(Cn_class_P<Patch<decimal>>), TypeInfoPropertyName = "T7135")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<decimal>>), TypeInfoPropertyName = "T7136")] +[JsonSerializable(typeof(Cn_struct_P<Patch<decimal>>), TypeInfoPropertyName = "T7137")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<decimal>>), TypeInfoPropertyName = "T7138")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<decimal>, int>), TypeInfoPropertyName = "T7139")] +[JsonSerializable(typeof(Cn_class_P<PBP_decimal>), TypeInfoPropertyName = "T7140")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_decimal>), TypeInfoPropertyName = "T7141")] +[JsonSerializable(typeof(Cn_struct_P<PBP_decimal>), TypeInfoPropertyName = "T7142")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_decimal>), TypeInfoPropertyName = "T7143")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_decimal, int>), TypeInfoPropertyName = "T7144")] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(Cn_class_P<double>), TypeInfoPropertyName = "T7145")] +[JsonSerializable(typeof(Cn_record_class_P<double>), TypeInfoPropertyName = "T7146")] +[JsonSerializable(typeof(Cn_struct_P<double>), TypeInfoPropertyName = "T7147")] +[JsonSerializable(typeof(Cn_record_struct_P<double>), TypeInfoPropertyName = "T7148")] +[JsonSerializable(typeof(Cn_generic_type_P<double, int>), TypeInfoPropertyName = "T7149")] +[JsonSerializable(typeof(Cn_class_P<double?>), TypeInfoPropertyName = "T7150")] +[JsonSerializable(typeof(Cn_record_class_P<double?>), TypeInfoPropertyName = "T7151")] +[JsonSerializable(typeof(Cn_struct_P<double?>), TypeInfoPropertyName = "T7152")] +[JsonSerializable(typeof(Cn_record_struct_P<double?>), TypeInfoPropertyName = "T7153")] +[JsonSerializable(typeof(Cn_generic_type_P<double?, int>), TypeInfoPropertyName = "T7154")] +[JsonSerializable(typeof(Cn_class_F<double>), TypeInfoPropertyName = "T7155")] +[JsonSerializable(typeof(Cn_record_class_F<double>), TypeInfoPropertyName = "T7156")] +[JsonSerializable(typeof(Cn_struct_F<double>), TypeInfoPropertyName = "T7157")] +[JsonSerializable(typeof(Cn_record_struct_F<double>), TypeInfoPropertyName = "T7158")] +[JsonSerializable(typeof(Cn_generic_type_F<double, int>), TypeInfoPropertyName = "T7159")] +[JsonSerializable(typeof(Cn_class_P<List<double>>), TypeInfoPropertyName = "T7160")] +[JsonSerializable(typeof(Cn_record_class_P<List<double>>), TypeInfoPropertyName = "T7161")] +[JsonSerializable(typeof(Cn_struct_P<List<double>>), TypeInfoPropertyName = "T7162")] +[JsonSerializable(typeof(Cn_record_struct_P<List<double>>), TypeInfoPropertyName = "T7163")] +[JsonSerializable(typeof(Cn_generic_type_P<List<double>, int>), TypeInfoPropertyName = "T7164")] +[JsonSerializable(typeof(Cn_class_P<double[]>), TypeInfoPropertyName = "T7165")] +[JsonSerializable(typeof(Cn_record_class_P<double[]>), TypeInfoPropertyName = "T7166")] +[JsonSerializable(typeof(Cn_struct_P<double[]>), TypeInfoPropertyName = "T7167")] +[JsonSerializable(typeof(Cn_record_struct_P<double[]>), TypeInfoPropertyName = "T7168")] +[JsonSerializable(typeof(Cn_generic_type_P<double[], int>), TypeInfoPropertyName = "T7169")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<double>>), TypeInfoPropertyName = "T7170")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<double>>), TypeInfoPropertyName = "T7171")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<double>>), TypeInfoPropertyName = "T7172")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<double>>), TypeInfoPropertyName = "T7173")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<double>, int>), TypeInfoPropertyName = "T7174")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<double>>), TypeInfoPropertyName = "T7175")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<double>>), TypeInfoPropertyName = "T7176")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<double>>), TypeInfoPropertyName = "T7177")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<double>>), TypeInfoPropertyName = "T7178")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<double>, int>), TypeInfoPropertyName = "T7179")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<double, int>>), TypeInfoPropertyName = "T7180")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<double, int>>), TypeInfoPropertyName = "T7181")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<double, int>>), TypeInfoPropertyName = "T7182")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<double, int>>), TypeInfoPropertyName = "T7183")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<double, int>, int>), TypeInfoPropertyName = "T7184")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, double>>), TypeInfoPropertyName = "T7185")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, double>>), TypeInfoPropertyName = "T7186")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, double>>), TypeInfoPropertyName = "T7187")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, double>>), TypeInfoPropertyName = "T7188")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, double>, int>), TypeInfoPropertyName = "T7189")] +[JsonSerializable(typeof(Cn_class_C<double>), TypeInfoPropertyName = "T7190")] +[JsonSerializable(typeof(Cn_record_class_C<double>), TypeInfoPropertyName = "T7191")] +[JsonSerializable(typeof(Cn_struct_C<double>), TypeInfoPropertyName = "T7192")] +[JsonSerializable(typeof(Cn_record_struct_C<double>), TypeInfoPropertyName = "T7193")] +[JsonSerializable(typeof(Cn_generic_type_C<double, int>), TypeInfoPropertyName = "T7194")] +[JsonSerializable(typeof(Cn_class_R<double>), TypeInfoPropertyName = "T7195")] +[JsonSerializable(typeof(Cn_record_class_R<double>), TypeInfoPropertyName = "T7196")] +[JsonSerializable(typeof(Cn_struct_R<double>), TypeInfoPropertyName = "T7197")] +[JsonSerializable(typeof(Cn_record_struct_R<double>), TypeInfoPropertyName = "T7198")] +[JsonSerializable(typeof(Cn_generic_type_R<double, int>), TypeInfoPropertyName = "T7199")] +[JsonSerializable(typeof(Cn_class_P<Patch<double>>), TypeInfoPropertyName = "T7200")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<double>>), TypeInfoPropertyName = "T7201")] +[JsonSerializable(typeof(Cn_struct_P<Patch<double>>), TypeInfoPropertyName = "T7202")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<double>>), TypeInfoPropertyName = "T7203")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<double>, int>), TypeInfoPropertyName = "T7204")] +[JsonSerializable(typeof(Cn_class_P<PBP_double>), TypeInfoPropertyName = "T7205")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_double>), TypeInfoPropertyName = "T7206")] +[JsonSerializable(typeof(Cn_struct_P<PBP_double>), TypeInfoPropertyName = "T7207")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_double>), TypeInfoPropertyName = "T7208")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_double, int>), TypeInfoPropertyName = "T7209")] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(Cn_class_P<bool>), TypeInfoPropertyName = "T7210")] +[JsonSerializable(typeof(Cn_record_class_P<bool>), TypeInfoPropertyName = "T7211")] +[JsonSerializable(typeof(Cn_struct_P<bool>), TypeInfoPropertyName = "T7212")] +[JsonSerializable(typeof(Cn_record_struct_P<bool>), TypeInfoPropertyName = "T7213")] +[JsonSerializable(typeof(Cn_generic_type_P<bool, int>), TypeInfoPropertyName = "T7214")] +[JsonSerializable(typeof(Cn_class_P<bool?>), TypeInfoPropertyName = "T7215")] +[JsonSerializable(typeof(Cn_record_class_P<bool?>), TypeInfoPropertyName = "T7216")] +[JsonSerializable(typeof(Cn_struct_P<bool?>), TypeInfoPropertyName = "T7217")] +[JsonSerializable(typeof(Cn_record_struct_P<bool?>), TypeInfoPropertyName = "T7218")] +[JsonSerializable(typeof(Cn_generic_type_P<bool?, int>), TypeInfoPropertyName = "T7219")] +[JsonSerializable(typeof(Cn_class_F<bool>), TypeInfoPropertyName = "T7220")] +[JsonSerializable(typeof(Cn_record_class_F<bool>), TypeInfoPropertyName = "T7221")] +[JsonSerializable(typeof(Cn_struct_F<bool>), TypeInfoPropertyName = "T7222")] +[JsonSerializable(typeof(Cn_record_struct_F<bool>), TypeInfoPropertyName = "T7223")] +[JsonSerializable(typeof(Cn_generic_type_F<bool, int>), TypeInfoPropertyName = "T7224")] +[JsonSerializable(typeof(Cn_class_P<List<bool>>), TypeInfoPropertyName = "T7225")] +[JsonSerializable(typeof(Cn_record_class_P<List<bool>>), TypeInfoPropertyName = "T7226")] +[JsonSerializable(typeof(Cn_struct_P<List<bool>>), TypeInfoPropertyName = "T7227")] +[JsonSerializable(typeof(Cn_record_struct_P<List<bool>>), TypeInfoPropertyName = "T7228")] +[JsonSerializable(typeof(Cn_generic_type_P<List<bool>, int>), TypeInfoPropertyName = "T7229")] +[JsonSerializable(typeof(Cn_class_P<bool[]>), TypeInfoPropertyName = "T7230")] +[JsonSerializable(typeof(Cn_record_class_P<bool[]>), TypeInfoPropertyName = "T7231")] +[JsonSerializable(typeof(Cn_struct_P<bool[]>), TypeInfoPropertyName = "T7232")] +[JsonSerializable(typeof(Cn_record_struct_P<bool[]>), TypeInfoPropertyName = "T7233")] +[JsonSerializable(typeof(Cn_generic_type_P<bool[], int>), TypeInfoPropertyName = "T7234")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<bool>>), TypeInfoPropertyName = "T7235")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<bool>>), TypeInfoPropertyName = "T7236")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<bool>>), TypeInfoPropertyName = "T7237")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<bool>>), TypeInfoPropertyName = "T7238")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<bool>, int>), TypeInfoPropertyName = "T7239")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<bool>>), TypeInfoPropertyName = "T7240")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<bool>>), TypeInfoPropertyName = "T7241")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<bool>>), TypeInfoPropertyName = "T7242")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<bool>>), TypeInfoPropertyName = "T7243")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<bool>, int>), TypeInfoPropertyName = "T7244")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<bool, int>>), TypeInfoPropertyName = "T7245")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<bool, int>>), TypeInfoPropertyName = "T7246")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<bool, int>>), TypeInfoPropertyName = "T7247")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<bool, int>>), TypeInfoPropertyName = "T7248")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<bool, int>, int>), TypeInfoPropertyName = "T7249")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, bool>>), TypeInfoPropertyName = "T7250")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, bool>>), TypeInfoPropertyName = "T7251")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, bool>>), TypeInfoPropertyName = "T7252")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, bool>>), TypeInfoPropertyName = "T7253")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, bool>, int>), TypeInfoPropertyName = "T7254")] +[JsonSerializable(typeof(Cn_class_C<bool>), TypeInfoPropertyName = "T7255")] +[JsonSerializable(typeof(Cn_record_class_C<bool>), TypeInfoPropertyName = "T7256")] +[JsonSerializable(typeof(Cn_struct_C<bool>), TypeInfoPropertyName = "T7257")] +[JsonSerializable(typeof(Cn_record_struct_C<bool>), TypeInfoPropertyName = "T7258")] +[JsonSerializable(typeof(Cn_generic_type_C<bool, int>), TypeInfoPropertyName = "T7259")] +[JsonSerializable(typeof(Cn_class_R<bool>), TypeInfoPropertyName = "T7260")] +[JsonSerializable(typeof(Cn_record_class_R<bool>), TypeInfoPropertyName = "T7261")] +[JsonSerializable(typeof(Cn_struct_R<bool>), TypeInfoPropertyName = "T7262")] +[JsonSerializable(typeof(Cn_record_struct_R<bool>), TypeInfoPropertyName = "T7263")] +[JsonSerializable(typeof(Cn_generic_type_R<bool, int>), TypeInfoPropertyName = "T7264")] +[JsonSerializable(typeof(Cn_class_P<Patch<bool>>), TypeInfoPropertyName = "T7265")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<bool>>), TypeInfoPropertyName = "T7266")] +[JsonSerializable(typeof(Cn_struct_P<Patch<bool>>), TypeInfoPropertyName = "T7267")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<bool>>), TypeInfoPropertyName = "T7268")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<bool>, int>), TypeInfoPropertyName = "T7269")] +[JsonSerializable(typeof(Cn_class_P<PBP_bool>), TypeInfoPropertyName = "T7270")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_bool>), TypeInfoPropertyName = "T7271")] +[JsonSerializable(typeof(Cn_struct_P<PBP_bool>), TypeInfoPropertyName = "T7272")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_bool>), TypeInfoPropertyName = "T7273")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_bool, int>), TypeInfoPropertyName = "T7274")] +[JsonSerializable(typeof(char))] +[JsonSerializable(typeof(Cn_class_P<char>), TypeInfoPropertyName = "T7275")] +[JsonSerializable(typeof(Cn_record_class_P<char>), TypeInfoPropertyName = "T7276")] +[JsonSerializable(typeof(Cn_struct_P<char>), TypeInfoPropertyName = "T7277")] +[JsonSerializable(typeof(Cn_record_struct_P<char>), TypeInfoPropertyName = "T7278")] +[JsonSerializable(typeof(Cn_generic_type_P<char, int>), TypeInfoPropertyName = "T7279")] +[JsonSerializable(typeof(Cn_class_P<char?>), TypeInfoPropertyName = "T7280")] +[JsonSerializable(typeof(Cn_record_class_P<char?>), TypeInfoPropertyName = "T7281")] +[JsonSerializable(typeof(Cn_struct_P<char?>), TypeInfoPropertyName = "T7282")] +[JsonSerializable(typeof(Cn_record_struct_P<char?>), TypeInfoPropertyName = "T7283")] +[JsonSerializable(typeof(Cn_generic_type_P<char?, int>), TypeInfoPropertyName = "T7284")] +[JsonSerializable(typeof(Cn_class_F<char>), TypeInfoPropertyName = "T7285")] +[JsonSerializable(typeof(Cn_record_class_F<char>), TypeInfoPropertyName = "T7286")] +[JsonSerializable(typeof(Cn_struct_F<char>), TypeInfoPropertyName = "T7287")] +[JsonSerializable(typeof(Cn_record_struct_F<char>), TypeInfoPropertyName = "T7288")] +[JsonSerializable(typeof(Cn_generic_type_F<char, int>), TypeInfoPropertyName = "T7289")] +[JsonSerializable(typeof(Cn_class_P<List<char>>), TypeInfoPropertyName = "T7290")] +[JsonSerializable(typeof(Cn_record_class_P<List<char>>), TypeInfoPropertyName = "T7291")] +[JsonSerializable(typeof(Cn_struct_P<List<char>>), TypeInfoPropertyName = "T7292")] +[JsonSerializable(typeof(Cn_record_struct_P<List<char>>), TypeInfoPropertyName = "T7293")] +[JsonSerializable(typeof(Cn_generic_type_P<List<char>, int>), TypeInfoPropertyName = "T7294")] +[JsonSerializable(typeof(Cn_class_P<char[]>), TypeInfoPropertyName = "T7295")] +[JsonSerializable(typeof(Cn_record_class_P<char[]>), TypeInfoPropertyName = "T7296")] +[JsonSerializable(typeof(Cn_struct_P<char[]>), TypeInfoPropertyName = "T7297")] +[JsonSerializable(typeof(Cn_record_struct_P<char[]>), TypeInfoPropertyName = "T7298")] +[JsonSerializable(typeof(Cn_generic_type_P<char[], int>), TypeInfoPropertyName = "T7299")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<char>>), TypeInfoPropertyName = "T7300")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<char>>), TypeInfoPropertyName = "T7301")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<char>>), TypeInfoPropertyName = "T7302")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<char>>), TypeInfoPropertyName = "T7303")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<char>, int>), TypeInfoPropertyName = "T7304")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<char>>), TypeInfoPropertyName = "T7305")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<char>>), TypeInfoPropertyName = "T7306")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<char>>), TypeInfoPropertyName = "T7307")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<char>>), TypeInfoPropertyName = "T7308")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<char>, int>), TypeInfoPropertyName = "T7309")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<char, int>>), TypeInfoPropertyName = "T7310")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<char, int>>), TypeInfoPropertyName = "T7311")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<char, int>>), TypeInfoPropertyName = "T7312")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<char, int>>), TypeInfoPropertyName = "T7313")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<char, int>, int>), TypeInfoPropertyName = "T7314")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, char>>), TypeInfoPropertyName = "T7315")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, char>>), TypeInfoPropertyName = "T7316")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, char>>), TypeInfoPropertyName = "T7317")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, char>>), TypeInfoPropertyName = "T7318")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, char>, int>), TypeInfoPropertyName = "T7319")] +[JsonSerializable(typeof(Cn_class_C<char>), TypeInfoPropertyName = "T7320")] +[JsonSerializable(typeof(Cn_record_class_C<char>), TypeInfoPropertyName = "T7321")] +[JsonSerializable(typeof(Cn_struct_C<char>), TypeInfoPropertyName = "T7322")] +[JsonSerializable(typeof(Cn_record_struct_C<char>), TypeInfoPropertyName = "T7323")] +[JsonSerializable(typeof(Cn_generic_type_C<char, int>), TypeInfoPropertyName = "T7324")] +[JsonSerializable(typeof(Cn_class_R<char>), TypeInfoPropertyName = "T7325")] +[JsonSerializable(typeof(Cn_record_class_R<char>), TypeInfoPropertyName = "T7326")] +[JsonSerializable(typeof(Cn_struct_R<char>), TypeInfoPropertyName = "T7327")] +[JsonSerializable(typeof(Cn_record_struct_R<char>), TypeInfoPropertyName = "T7328")] +[JsonSerializable(typeof(Cn_generic_type_R<char, int>), TypeInfoPropertyName = "T7329")] +[JsonSerializable(typeof(Cn_class_P<Patch<char>>), TypeInfoPropertyName = "T7330")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<char>>), TypeInfoPropertyName = "T7331")] +[JsonSerializable(typeof(Cn_struct_P<Patch<char>>), TypeInfoPropertyName = "T7332")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<char>>), TypeInfoPropertyName = "T7333")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<char>, int>), TypeInfoPropertyName = "T7334")] +[JsonSerializable(typeof(Cn_class_P<PBP_char>), TypeInfoPropertyName = "T7335")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_char>), TypeInfoPropertyName = "T7336")] +[JsonSerializable(typeof(Cn_struct_P<PBP_char>), TypeInfoPropertyName = "T7337")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_char>), TypeInfoPropertyName = "T7338")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_char, int>), TypeInfoPropertyName = "T7339")] +[JsonSerializable(typeof(DateTime))] +[JsonSerializable(typeof(Cn_class_P<DateTime>), TypeInfoPropertyName = "T7340")] +[JsonSerializable(typeof(Cn_record_class_P<DateTime>), TypeInfoPropertyName = "T7341")] +[JsonSerializable(typeof(Cn_struct_P<DateTime>), TypeInfoPropertyName = "T7342")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTime>), TypeInfoPropertyName = "T7343")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTime, int>), TypeInfoPropertyName = "T7344")] +[JsonSerializable(typeof(Cn_class_P<DateTime?>), TypeInfoPropertyName = "T7345")] +[JsonSerializable(typeof(Cn_record_class_P<DateTime?>), TypeInfoPropertyName = "T7346")] +[JsonSerializable(typeof(Cn_struct_P<DateTime?>), TypeInfoPropertyName = "T7347")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTime?>), TypeInfoPropertyName = "T7348")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTime?, int>), TypeInfoPropertyName = "T7349")] +[JsonSerializable(typeof(Cn_class_F<DateTime>), TypeInfoPropertyName = "T7350")] +[JsonSerializable(typeof(Cn_record_class_F<DateTime>), TypeInfoPropertyName = "T7351")] +[JsonSerializable(typeof(Cn_struct_F<DateTime>), TypeInfoPropertyName = "T7352")] +[JsonSerializable(typeof(Cn_record_struct_F<DateTime>), TypeInfoPropertyName = "T7353")] +[JsonSerializable(typeof(Cn_generic_type_F<DateTime, int>), TypeInfoPropertyName = "T7354")] +[JsonSerializable(typeof(Cn_class_P<List<DateTime>>), TypeInfoPropertyName = "T7355")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateTime>>), TypeInfoPropertyName = "T7356")] +[JsonSerializable(typeof(Cn_struct_P<List<DateTime>>), TypeInfoPropertyName = "T7357")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateTime>>), TypeInfoPropertyName = "T7358")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateTime>, int>), TypeInfoPropertyName = "T7359")] +[JsonSerializable(typeof(Cn_class_P<DateTime[]>), TypeInfoPropertyName = "T7360")] +[JsonSerializable(typeof(Cn_record_class_P<DateTime[]>), TypeInfoPropertyName = "T7361")] +[JsonSerializable(typeof(Cn_struct_P<DateTime[]>), TypeInfoPropertyName = "T7362")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTime[]>), TypeInfoPropertyName = "T7363")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTime[], int>), TypeInfoPropertyName = "T7364")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateTime>>), TypeInfoPropertyName = "T7365")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateTime>>), TypeInfoPropertyName = "T7366")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateTime>>), TypeInfoPropertyName = "T7367")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateTime>>), TypeInfoPropertyName = "T7368")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateTime>, int>), TypeInfoPropertyName = "T7369")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateTime>>), TypeInfoPropertyName = "T7370")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateTime>>), TypeInfoPropertyName = "T7371")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateTime>>), TypeInfoPropertyName = "T7372")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateTime>>), TypeInfoPropertyName = "T7373")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateTime>, int>), TypeInfoPropertyName = "T7374")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<DateTime, int>>), TypeInfoPropertyName = "T7375")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<DateTime, int>>), TypeInfoPropertyName = "T7376")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<DateTime, int>>), TypeInfoPropertyName = "T7377")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<DateTime, int>>), TypeInfoPropertyName = "T7378")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<DateTime, int>, int>), TypeInfoPropertyName = "T7379")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateTime>>), TypeInfoPropertyName = "T7380")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateTime>>), TypeInfoPropertyName = "T7381")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateTime>>), TypeInfoPropertyName = "T7382")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateTime>>), TypeInfoPropertyName = "T7383")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateTime>, int>), TypeInfoPropertyName = "T7384")] +[JsonSerializable(typeof(Cn_class_C<DateTime>), TypeInfoPropertyName = "T7385")] +[JsonSerializable(typeof(Cn_record_class_C<DateTime>), TypeInfoPropertyName = "T7386")] +[JsonSerializable(typeof(Cn_struct_C<DateTime>), TypeInfoPropertyName = "T7387")] +[JsonSerializable(typeof(Cn_record_struct_C<DateTime>), TypeInfoPropertyName = "T7388")] +[JsonSerializable(typeof(Cn_generic_type_C<DateTime, int>), TypeInfoPropertyName = "T7389")] +[JsonSerializable(typeof(Cn_class_R<DateTime>), TypeInfoPropertyName = "T7390")] +[JsonSerializable(typeof(Cn_record_class_R<DateTime>), TypeInfoPropertyName = "T7391")] +[JsonSerializable(typeof(Cn_struct_R<DateTime>), TypeInfoPropertyName = "T7392")] +[JsonSerializable(typeof(Cn_record_struct_R<DateTime>), TypeInfoPropertyName = "T7393")] +[JsonSerializable(typeof(Cn_generic_type_R<DateTime, int>), TypeInfoPropertyName = "T7394")] +[JsonSerializable(typeof(Cn_class_P<Patch<DateTime>>), TypeInfoPropertyName = "T7395")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<DateTime>>), TypeInfoPropertyName = "T7396")] +[JsonSerializable(typeof(Cn_struct_P<Patch<DateTime>>), TypeInfoPropertyName = "T7397")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<DateTime>>), TypeInfoPropertyName = "T7398")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<DateTime>, int>), TypeInfoPropertyName = "T7399")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateTime>), TypeInfoPropertyName = "T7400")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateTime>), TypeInfoPropertyName = "T7401")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateTime>), TypeInfoPropertyName = "T7402")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateTime>), TypeInfoPropertyName = "T7403")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateTime, int>), TypeInfoPropertyName = "T7404")] +[JsonSerializable(typeof(DateTimeOffset))] +[JsonSerializable(typeof(Cn_class_P<DateTimeOffset>), TypeInfoPropertyName = "T7405")] +[JsonSerializable(typeof(Cn_record_class_P<DateTimeOffset>), TypeInfoPropertyName = "T7406")] +[JsonSerializable(typeof(Cn_struct_P<DateTimeOffset>), TypeInfoPropertyName = "T7407")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTimeOffset>), TypeInfoPropertyName = "T7408")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTimeOffset, int>), TypeInfoPropertyName = "T7409")] +[JsonSerializable(typeof(Cn_class_P<DateTimeOffset?>), TypeInfoPropertyName = "T7410")] +[JsonSerializable(typeof(Cn_record_class_P<DateTimeOffset?>), TypeInfoPropertyName = "T7411")] +[JsonSerializable(typeof(Cn_struct_P<DateTimeOffset?>), TypeInfoPropertyName = "T7412")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTimeOffset?>), TypeInfoPropertyName = "T7413")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTimeOffset?, int>), TypeInfoPropertyName = "T7414")] +[JsonSerializable(typeof(Cn_class_F<DateTimeOffset>), TypeInfoPropertyName = "T7415")] +[JsonSerializable(typeof(Cn_record_class_F<DateTimeOffset>), TypeInfoPropertyName = "T7416")] +[JsonSerializable(typeof(Cn_struct_F<DateTimeOffset>), TypeInfoPropertyName = "T7417")] +[JsonSerializable(typeof(Cn_record_struct_F<DateTimeOffset>), TypeInfoPropertyName = "T7418")] +[JsonSerializable(typeof(Cn_generic_type_F<DateTimeOffset, int>), TypeInfoPropertyName = "T7419")] +[JsonSerializable(typeof(Cn_class_P<List<DateTimeOffset>>), TypeInfoPropertyName = "T7420")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateTimeOffset>>), TypeInfoPropertyName = "T7421")] +[JsonSerializable(typeof(Cn_struct_P<List<DateTimeOffset>>), TypeInfoPropertyName = "T7422")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateTimeOffset>>), TypeInfoPropertyName = "T7423")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateTimeOffset>, int>), TypeInfoPropertyName = "T7424")] +[JsonSerializable(typeof(Cn_class_P<DateTimeOffset[]>), TypeInfoPropertyName = "T7425")] +[JsonSerializable(typeof(Cn_record_class_P<DateTimeOffset[]>), TypeInfoPropertyName = "T7426")] +[JsonSerializable(typeof(Cn_struct_P<DateTimeOffset[]>), TypeInfoPropertyName = "T7427")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTimeOffset[]>), TypeInfoPropertyName = "T7428")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTimeOffset[], int>), TypeInfoPropertyName = "T7429")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateTimeOffset>>), TypeInfoPropertyName = "T7430")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateTimeOffset>>), TypeInfoPropertyName = "T7431")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateTimeOffset>>), TypeInfoPropertyName = "T7432")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateTimeOffset>>), TypeInfoPropertyName = "T7433")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateTimeOffset>, int>), TypeInfoPropertyName = "T7434")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateTimeOffset>>), TypeInfoPropertyName = "T7435")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateTimeOffset>>), TypeInfoPropertyName = "T7436")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateTimeOffset>>), TypeInfoPropertyName = "T7437")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateTimeOffset>>), TypeInfoPropertyName = "T7438")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateTimeOffset>, int>), TypeInfoPropertyName = "T7439")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<DateTimeOffset, int>>), TypeInfoPropertyName = "T7440")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<DateTimeOffset, int>>), TypeInfoPropertyName = "T7441")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<DateTimeOffset, int>>), TypeInfoPropertyName = "T7442")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<DateTimeOffset, int>>), TypeInfoPropertyName = "T7443")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<DateTimeOffset, int>, int>), TypeInfoPropertyName = "T7444")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateTimeOffset>>), TypeInfoPropertyName = "T7445")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateTimeOffset>>), TypeInfoPropertyName = "T7446")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateTimeOffset>>), TypeInfoPropertyName = "T7447")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateTimeOffset>>), TypeInfoPropertyName = "T7448")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateTimeOffset>, int>), TypeInfoPropertyName = "T7449")] +[JsonSerializable(typeof(Cn_class_C<DateTimeOffset>), TypeInfoPropertyName = "T7450")] +[JsonSerializable(typeof(Cn_record_class_C<DateTimeOffset>), TypeInfoPropertyName = "T7451")] +[JsonSerializable(typeof(Cn_struct_C<DateTimeOffset>), TypeInfoPropertyName = "T7452")] +[JsonSerializable(typeof(Cn_record_struct_C<DateTimeOffset>), TypeInfoPropertyName = "T7453")] +[JsonSerializable(typeof(Cn_generic_type_C<DateTimeOffset, int>), TypeInfoPropertyName = "T7454")] +[JsonSerializable(typeof(Cn_class_R<DateTimeOffset>), TypeInfoPropertyName = "T7455")] +[JsonSerializable(typeof(Cn_record_class_R<DateTimeOffset>), TypeInfoPropertyName = "T7456")] +[JsonSerializable(typeof(Cn_struct_R<DateTimeOffset>), TypeInfoPropertyName = "T7457")] +[JsonSerializable(typeof(Cn_record_struct_R<DateTimeOffset>), TypeInfoPropertyName = "T7458")] +[JsonSerializable(typeof(Cn_generic_type_R<DateTimeOffset, int>), TypeInfoPropertyName = "T7459")] +[JsonSerializable(typeof(Cn_class_P<Patch<DateTimeOffset>>), TypeInfoPropertyName = "T7460")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<DateTimeOffset>>), TypeInfoPropertyName = "T7461")] +[JsonSerializable(typeof(Cn_struct_P<Patch<DateTimeOffset>>), TypeInfoPropertyName = "T7462")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<DateTimeOffset>>), TypeInfoPropertyName = "T7463")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<DateTimeOffset>, int>), TypeInfoPropertyName = "T7464")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateTimeOffset>), TypeInfoPropertyName = "T7465")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateTimeOffset>), TypeInfoPropertyName = "T7466")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateTimeOffset>), TypeInfoPropertyName = "T7467")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateTimeOffset>), TypeInfoPropertyName = "T7468")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateTimeOffset, int>), TypeInfoPropertyName = "T7469")] +[JsonSerializable(typeof(DateOnly))] +[JsonSerializable(typeof(Cn_class_P<DateOnly>), TypeInfoPropertyName = "T7470")] +[JsonSerializable(typeof(Cn_record_class_P<DateOnly>), TypeInfoPropertyName = "T7471")] +[JsonSerializable(typeof(Cn_struct_P<DateOnly>), TypeInfoPropertyName = "T7472")] +[JsonSerializable(typeof(Cn_record_struct_P<DateOnly>), TypeInfoPropertyName = "T7473")] +[JsonSerializable(typeof(Cn_generic_type_P<DateOnly, int>), TypeInfoPropertyName = "T7474")] +[JsonSerializable(typeof(Cn_class_P<DateOnly?>), TypeInfoPropertyName = "T7475")] +[JsonSerializable(typeof(Cn_record_class_P<DateOnly?>), TypeInfoPropertyName = "T7476")] +[JsonSerializable(typeof(Cn_struct_P<DateOnly?>), TypeInfoPropertyName = "T7477")] +[JsonSerializable(typeof(Cn_record_struct_P<DateOnly?>), TypeInfoPropertyName = "T7478")] +[JsonSerializable(typeof(Cn_generic_type_P<DateOnly?, int>), TypeInfoPropertyName = "T7479")] +[JsonSerializable(typeof(Cn_class_F<DateOnly>), TypeInfoPropertyName = "T7480")] +[JsonSerializable(typeof(Cn_record_class_F<DateOnly>), TypeInfoPropertyName = "T7481")] +[JsonSerializable(typeof(Cn_struct_F<DateOnly>), TypeInfoPropertyName = "T7482")] +[JsonSerializable(typeof(Cn_record_struct_F<DateOnly>), TypeInfoPropertyName = "T7483")] +[JsonSerializable(typeof(Cn_generic_type_F<DateOnly, int>), TypeInfoPropertyName = "T7484")] +[JsonSerializable(typeof(Cn_class_P<List<DateOnly>>), TypeInfoPropertyName = "T7485")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateOnly>>), TypeInfoPropertyName = "T7486")] +[JsonSerializable(typeof(Cn_struct_P<List<DateOnly>>), TypeInfoPropertyName = "T7487")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateOnly>>), TypeInfoPropertyName = "T7488")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateOnly>, int>), TypeInfoPropertyName = "T7489")] +[JsonSerializable(typeof(Cn_class_P<DateOnly[]>), TypeInfoPropertyName = "T7490")] +[JsonSerializable(typeof(Cn_record_class_P<DateOnly[]>), TypeInfoPropertyName = "T7491")] +[JsonSerializable(typeof(Cn_struct_P<DateOnly[]>), TypeInfoPropertyName = "T7492")] +[JsonSerializable(typeof(Cn_record_struct_P<DateOnly[]>), TypeInfoPropertyName = "T7493")] +[JsonSerializable(typeof(Cn_generic_type_P<DateOnly[], int>), TypeInfoPropertyName = "T7494")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateOnly>>), TypeInfoPropertyName = "T7495")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateOnly>>), TypeInfoPropertyName = "T7496")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateOnly>>), TypeInfoPropertyName = "T7497")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateOnly>>), TypeInfoPropertyName = "T7498")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateOnly>, int>), TypeInfoPropertyName = "T7499")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateOnly>>), TypeInfoPropertyName = "T7500")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateOnly>>), TypeInfoPropertyName = "T7501")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateOnly>>), TypeInfoPropertyName = "T7502")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateOnly>>), TypeInfoPropertyName = "T7503")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateOnly>, int>), TypeInfoPropertyName = "T7504")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<DateOnly, int>>), TypeInfoPropertyName = "T7505")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<DateOnly, int>>), TypeInfoPropertyName = "T7506")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<DateOnly, int>>), TypeInfoPropertyName = "T7507")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<DateOnly, int>>), TypeInfoPropertyName = "T7508")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<DateOnly, int>, int>), TypeInfoPropertyName = "T7509")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateOnly>>), TypeInfoPropertyName = "T7510")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateOnly>>), TypeInfoPropertyName = "T7511")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateOnly>>), TypeInfoPropertyName = "T7512")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateOnly>>), TypeInfoPropertyName = "T7513")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateOnly>, int>), TypeInfoPropertyName = "T7514")] +[JsonSerializable(typeof(Cn_class_C<DateOnly>), TypeInfoPropertyName = "T7515")] +[JsonSerializable(typeof(Cn_record_class_C<DateOnly>), TypeInfoPropertyName = "T7516")] +[JsonSerializable(typeof(Cn_struct_C<DateOnly>), TypeInfoPropertyName = "T7517")] +[JsonSerializable(typeof(Cn_record_struct_C<DateOnly>), TypeInfoPropertyName = "T7518")] +[JsonSerializable(typeof(Cn_generic_type_C<DateOnly, int>), TypeInfoPropertyName = "T7519")] +[JsonSerializable(typeof(Cn_class_R<DateOnly>), TypeInfoPropertyName = "T7520")] +[JsonSerializable(typeof(Cn_record_class_R<DateOnly>), TypeInfoPropertyName = "T7521")] +[JsonSerializable(typeof(Cn_struct_R<DateOnly>), TypeInfoPropertyName = "T7522")] +[JsonSerializable(typeof(Cn_record_struct_R<DateOnly>), TypeInfoPropertyName = "T7523")] +[JsonSerializable(typeof(Cn_generic_type_R<DateOnly, int>), TypeInfoPropertyName = "T7524")] +[JsonSerializable(typeof(Cn_class_P<Patch<DateOnly>>), TypeInfoPropertyName = "T7525")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<DateOnly>>), TypeInfoPropertyName = "T7526")] +[JsonSerializable(typeof(Cn_struct_P<Patch<DateOnly>>), TypeInfoPropertyName = "T7527")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<DateOnly>>), TypeInfoPropertyName = "T7528")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<DateOnly>, int>), TypeInfoPropertyName = "T7529")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateOnly>), TypeInfoPropertyName = "T7530")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateOnly>), TypeInfoPropertyName = "T7531")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateOnly>), TypeInfoPropertyName = "T7532")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateOnly>), TypeInfoPropertyName = "T7533")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateOnly, int>), TypeInfoPropertyName = "T7534")] +[JsonSerializable(typeof(TimeOnly))] +[JsonSerializable(typeof(Cn_class_P<TimeOnly>), TypeInfoPropertyName = "T7535")] +[JsonSerializable(typeof(Cn_record_class_P<TimeOnly>), TypeInfoPropertyName = "T7536")] +[JsonSerializable(typeof(Cn_struct_P<TimeOnly>), TypeInfoPropertyName = "T7537")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeOnly>), TypeInfoPropertyName = "T7538")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeOnly, int>), TypeInfoPropertyName = "T7539")] +[JsonSerializable(typeof(Cn_class_P<TimeOnly?>), TypeInfoPropertyName = "T7540")] +[JsonSerializable(typeof(Cn_record_class_P<TimeOnly?>), TypeInfoPropertyName = "T7541")] +[JsonSerializable(typeof(Cn_struct_P<TimeOnly?>), TypeInfoPropertyName = "T7542")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeOnly?>), TypeInfoPropertyName = "T7543")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeOnly?, int>), TypeInfoPropertyName = "T7544")] +[JsonSerializable(typeof(Cn_class_F<TimeOnly>), TypeInfoPropertyName = "T7545")] +[JsonSerializable(typeof(Cn_record_class_F<TimeOnly>), TypeInfoPropertyName = "T7546")] +[JsonSerializable(typeof(Cn_struct_F<TimeOnly>), TypeInfoPropertyName = "T7547")] +[JsonSerializable(typeof(Cn_record_struct_F<TimeOnly>), TypeInfoPropertyName = "T7548")] +[JsonSerializable(typeof(Cn_generic_type_F<TimeOnly, int>), TypeInfoPropertyName = "T7549")] +[JsonSerializable(typeof(Cn_class_P<List<TimeOnly>>), TypeInfoPropertyName = "T7550")] +[JsonSerializable(typeof(Cn_record_class_P<List<TimeOnly>>), TypeInfoPropertyName = "T7551")] +[JsonSerializable(typeof(Cn_struct_P<List<TimeOnly>>), TypeInfoPropertyName = "T7552")] +[JsonSerializable(typeof(Cn_record_struct_P<List<TimeOnly>>), TypeInfoPropertyName = "T7553")] +[JsonSerializable(typeof(Cn_generic_type_P<List<TimeOnly>, int>), TypeInfoPropertyName = "T7554")] +[JsonSerializable(typeof(Cn_class_P<TimeOnly[]>), TypeInfoPropertyName = "T7555")] +[JsonSerializable(typeof(Cn_record_class_P<TimeOnly[]>), TypeInfoPropertyName = "T7556")] +[JsonSerializable(typeof(Cn_struct_P<TimeOnly[]>), TypeInfoPropertyName = "T7557")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeOnly[]>), TypeInfoPropertyName = "T7558")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeOnly[], int>), TypeInfoPropertyName = "T7559")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<TimeOnly>>), TypeInfoPropertyName = "T7560")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<TimeOnly>>), TypeInfoPropertyName = "T7561")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<TimeOnly>>), TypeInfoPropertyName = "T7562")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<TimeOnly>>), TypeInfoPropertyName = "T7563")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<TimeOnly>, int>), TypeInfoPropertyName = "T7564")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<TimeOnly>>), TypeInfoPropertyName = "T7565")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<TimeOnly>>), TypeInfoPropertyName = "T7566")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<TimeOnly>>), TypeInfoPropertyName = "T7567")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<TimeOnly>>), TypeInfoPropertyName = "T7568")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<TimeOnly>, int>), TypeInfoPropertyName = "T7569")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<TimeOnly, int>>), TypeInfoPropertyName = "T7570")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<TimeOnly, int>>), TypeInfoPropertyName = "T7571")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<TimeOnly, int>>), TypeInfoPropertyName = "T7572")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<TimeOnly, int>>), TypeInfoPropertyName = "T7573")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<TimeOnly, int>, int>), TypeInfoPropertyName = "T7574")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, TimeOnly>>), TypeInfoPropertyName = "T7575")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, TimeOnly>>), TypeInfoPropertyName = "T7576")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, TimeOnly>>), TypeInfoPropertyName = "T7577")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, TimeOnly>>), TypeInfoPropertyName = "T7578")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, TimeOnly>, int>), TypeInfoPropertyName = "T7579")] +[JsonSerializable(typeof(Cn_class_C<TimeOnly>), TypeInfoPropertyName = "T7580")] +[JsonSerializable(typeof(Cn_record_class_C<TimeOnly>), TypeInfoPropertyName = "T7581")] +[JsonSerializable(typeof(Cn_struct_C<TimeOnly>), TypeInfoPropertyName = "T7582")] +[JsonSerializable(typeof(Cn_record_struct_C<TimeOnly>), TypeInfoPropertyName = "T7583")] +[JsonSerializable(typeof(Cn_generic_type_C<TimeOnly, int>), TypeInfoPropertyName = "T7584")] +[JsonSerializable(typeof(Cn_class_R<TimeOnly>), TypeInfoPropertyName = "T7585")] +[JsonSerializable(typeof(Cn_record_class_R<TimeOnly>), TypeInfoPropertyName = "T7586")] +[JsonSerializable(typeof(Cn_struct_R<TimeOnly>), TypeInfoPropertyName = "T7587")] +[JsonSerializable(typeof(Cn_record_struct_R<TimeOnly>), TypeInfoPropertyName = "T7588")] +[JsonSerializable(typeof(Cn_generic_type_R<TimeOnly, int>), TypeInfoPropertyName = "T7589")] +[JsonSerializable(typeof(Cn_class_P<Patch<TimeOnly>>), TypeInfoPropertyName = "T7590")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<TimeOnly>>), TypeInfoPropertyName = "T7591")] +[JsonSerializable(typeof(Cn_struct_P<Patch<TimeOnly>>), TypeInfoPropertyName = "T7592")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<TimeOnly>>), TypeInfoPropertyName = "T7593")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<TimeOnly>, int>), TypeInfoPropertyName = "T7594")] +[JsonSerializable(typeof(Cn_class_P<PBP_TimeOnly>), TypeInfoPropertyName = "T7595")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_TimeOnly>), TypeInfoPropertyName = "T7596")] +[JsonSerializable(typeof(Cn_struct_P<PBP_TimeOnly>), TypeInfoPropertyName = "T7597")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_TimeOnly>), TypeInfoPropertyName = "T7598")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_TimeOnly, int>), TypeInfoPropertyName = "T7599")] +[JsonSerializable(typeof(TimeSpan))] +[JsonSerializable(typeof(Cn_class_P<TimeSpan>), TypeInfoPropertyName = "T7600")] +[JsonSerializable(typeof(Cn_record_class_P<TimeSpan>), TypeInfoPropertyName = "T7601")] +[JsonSerializable(typeof(Cn_struct_P<TimeSpan>), TypeInfoPropertyName = "T7602")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeSpan>), TypeInfoPropertyName = "T7603")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeSpan, int>), TypeInfoPropertyName = "T7604")] +[JsonSerializable(typeof(Cn_class_P<TimeSpan?>), TypeInfoPropertyName = "T7605")] +[JsonSerializable(typeof(Cn_record_class_P<TimeSpan?>), TypeInfoPropertyName = "T7606")] +[JsonSerializable(typeof(Cn_struct_P<TimeSpan?>), TypeInfoPropertyName = "T7607")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeSpan?>), TypeInfoPropertyName = "T7608")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeSpan?, int>), TypeInfoPropertyName = "T7609")] +[JsonSerializable(typeof(Cn_class_F<TimeSpan>), TypeInfoPropertyName = "T7610")] +[JsonSerializable(typeof(Cn_record_class_F<TimeSpan>), TypeInfoPropertyName = "T7611")] +[JsonSerializable(typeof(Cn_struct_F<TimeSpan>), TypeInfoPropertyName = "T7612")] +[JsonSerializable(typeof(Cn_record_struct_F<TimeSpan>), TypeInfoPropertyName = "T7613")] +[JsonSerializable(typeof(Cn_generic_type_F<TimeSpan, int>), TypeInfoPropertyName = "T7614")] +[JsonSerializable(typeof(Cn_class_P<List<TimeSpan>>), TypeInfoPropertyName = "T7615")] +[JsonSerializable(typeof(Cn_record_class_P<List<TimeSpan>>), TypeInfoPropertyName = "T7616")] +[JsonSerializable(typeof(Cn_struct_P<List<TimeSpan>>), TypeInfoPropertyName = "T7617")] +[JsonSerializable(typeof(Cn_record_struct_P<List<TimeSpan>>), TypeInfoPropertyName = "T7618")] +[JsonSerializable(typeof(Cn_generic_type_P<List<TimeSpan>, int>), TypeInfoPropertyName = "T7619")] +[JsonSerializable(typeof(Cn_class_P<TimeSpan[]>), TypeInfoPropertyName = "T7620")] +[JsonSerializable(typeof(Cn_record_class_P<TimeSpan[]>), TypeInfoPropertyName = "T7621")] +[JsonSerializable(typeof(Cn_struct_P<TimeSpan[]>), TypeInfoPropertyName = "T7622")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeSpan[]>), TypeInfoPropertyName = "T7623")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeSpan[], int>), TypeInfoPropertyName = "T7624")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<TimeSpan>>), TypeInfoPropertyName = "T7625")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<TimeSpan>>), TypeInfoPropertyName = "T7626")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<TimeSpan>>), TypeInfoPropertyName = "T7627")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<TimeSpan>>), TypeInfoPropertyName = "T7628")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<TimeSpan>, int>), TypeInfoPropertyName = "T7629")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<TimeSpan>>), TypeInfoPropertyName = "T7630")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<TimeSpan>>), TypeInfoPropertyName = "T7631")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<TimeSpan>>), TypeInfoPropertyName = "T7632")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<TimeSpan>>), TypeInfoPropertyName = "T7633")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<TimeSpan>, int>), TypeInfoPropertyName = "T7634")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<TimeSpan, int>>), TypeInfoPropertyName = "T7635")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<TimeSpan, int>>), TypeInfoPropertyName = "T7636")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<TimeSpan, int>>), TypeInfoPropertyName = "T7637")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<TimeSpan, int>>), TypeInfoPropertyName = "T7638")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<TimeSpan, int>, int>), TypeInfoPropertyName = "T7639")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, TimeSpan>>), TypeInfoPropertyName = "T7640")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, TimeSpan>>), TypeInfoPropertyName = "T7641")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, TimeSpan>>), TypeInfoPropertyName = "T7642")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, TimeSpan>>), TypeInfoPropertyName = "T7643")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, TimeSpan>, int>), TypeInfoPropertyName = "T7644")] +[JsonSerializable(typeof(Cn_class_C<TimeSpan>), TypeInfoPropertyName = "T7645")] +[JsonSerializable(typeof(Cn_record_class_C<TimeSpan>), TypeInfoPropertyName = "T7646")] +[JsonSerializable(typeof(Cn_struct_C<TimeSpan>), TypeInfoPropertyName = "T7647")] +[JsonSerializable(typeof(Cn_record_struct_C<TimeSpan>), TypeInfoPropertyName = "T7648")] +[JsonSerializable(typeof(Cn_generic_type_C<TimeSpan, int>), TypeInfoPropertyName = "T7649")] +[JsonSerializable(typeof(Cn_class_R<TimeSpan>), TypeInfoPropertyName = "T7650")] +[JsonSerializable(typeof(Cn_record_class_R<TimeSpan>), TypeInfoPropertyName = "T7651")] +[JsonSerializable(typeof(Cn_struct_R<TimeSpan>), TypeInfoPropertyName = "T7652")] +[JsonSerializable(typeof(Cn_record_struct_R<TimeSpan>), TypeInfoPropertyName = "T7653")] +[JsonSerializable(typeof(Cn_generic_type_R<TimeSpan, int>), TypeInfoPropertyName = "T7654")] +[JsonSerializable(typeof(Cn_class_P<Patch<TimeSpan>>), TypeInfoPropertyName = "T7655")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<TimeSpan>>), TypeInfoPropertyName = "T7656")] +[JsonSerializable(typeof(Cn_struct_P<Patch<TimeSpan>>), TypeInfoPropertyName = "T7657")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<TimeSpan>>), TypeInfoPropertyName = "T7658")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<TimeSpan>, int>), TypeInfoPropertyName = "T7659")] +[JsonSerializable(typeof(Cn_class_P<PBP_TimeSpan>), TypeInfoPropertyName = "T7660")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_TimeSpan>), TypeInfoPropertyName = "T7661")] +[JsonSerializable(typeof(Cn_struct_P<PBP_TimeSpan>), TypeInfoPropertyName = "T7662")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_TimeSpan>), TypeInfoPropertyName = "T7663")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_TimeSpan, int>), TypeInfoPropertyName = "T7664")] +[JsonSerializable(typeof(PEnum))] +[JsonSerializable(typeof(Cn_class_P<PEnum>), TypeInfoPropertyName = "T7665")] +[JsonSerializable(typeof(Cn_record_class_P<PEnum>), TypeInfoPropertyName = "T7666")] +[JsonSerializable(typeof(Cn_struct_P<PEnum>), TypeInfoPropertyName = "T7667")] +[JsonSerializable(typeof(Cn_record_struct_P<PEnum>), TypeInfoPropertyName = "T7668")] +[JsonSerializable(typeof(Cn_generic_type_P<PEnum, int>), TypeInfoPropertyName = "T7669")] +[JsonSerializable(typeof(Cn_class_P<PEnum?>), TypeInfoPropertyName = "T7670")] +[JsonSerializable(typeof(Cn_record_class_P<PEnum?>), TypeInfoPropertyName = "T7671")] +[JsonSerializable(typeof(Cn_struct_P<PEnum?>), TypeInfoPropertyName = "T7672")] +[JsonSerializable(typeof(Cn_record_struct_P<PEnum?>), TypeInfoPropertyName = "T7673")] +[JsonSerializable(typeof(Cn_generic_type_P<PEnum?, int>), TypeInfoPropertyName = "T7674")] +[JsonSerializable(typeof(Cn_class_F<PEnum>), TypeInfoPropertyName = "T7675")] +[JsonSerializable(typeof(Cn_record_class_F<PEnum>), TypeInfoPropertyName = "T7676")] +[JsonSerializable(typeof(Cn_struct_F<PEnum>), TypeInfoPropertyName = "T7677")] +[JsonSerializable(typeof(Cn_record_struct_F<PEnum>), TypeInfoPropertyName = "T7678")] +[JsonSerializable(typeof(Cn_generic_type_F<PEnum, int>), TypeInfoPropertyName = "T7679")] +[JsonSerializable(typeof(Cn_class_P<List<PEnum>>), TypeInfoPropertyName = "T7680")] +[JsonSerializable(typeof(Cn_record_class_P<List<PEnum>>), TypeInfoPropertyName = "T7681")] +[JsonSerializable(typeof(Cn_struct_P<List<PEnum>>), TypeInfoPropertyName = "T7682")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PEnum>>), TypeInfoPropertyName = "T7683")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PEnum>, int>), TypeInfoPropertyName = "T7684")] +[JsonSerializable(typeof(Cn_class_P<PEnum[]>), TypeInfoPropertyName = "T7685")] +[JsonSerializable(typeof(Cn_record_class_P<PEnum[]>), TypeInfoPropertyName = "T7686")] +[JsonSerializable(typeof(Cn_struct_P<PEnum[]>), TypeInfoPropertyName = "T7687")] +[JsonSerializable(typeof(Cn_record_struct_P<PEnum[]>), TypeInfoPropertyName = "T7688")] +[JsonSerializable(typeof(Cn_generic_type_P<PEnum[], int>), TypeInfoPropertyName = "T7689")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PEnum>>), TypeInfoPropertyName = "T7690")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PEnum>>), TypeInfoPropertyName = "T7691")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PEnum>>), TypeInfoPropertyName = "T7692")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PEnum>>), TypeInfoPropertyName = "T7693")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PEnum>, int>), TypeInfoPropertyName = "T7694")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PEnum>>), TypeInfoPropertyName = "T7695")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PEnum>>), TypeInfoPropertyName = "T7696")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PEnum>>), TypeInfoPropertyName = "T7697")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PEnum>>), TypeInfoPropertyName = "T7698")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PEnum>, int>), TypeInfoPropertyName = "T7699")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<PEnum, int>>), TypeInfoPropertyName = "T7700")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<PEnum, int>>), TypeInfoPropertyName = "T7701")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<PEnum, int>>), TypeInfoPropertyName = "T7702")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<PEnum, int>>), TypeInfoPropertyName = "T7703")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<PEnum, int>, int>), TypeInfoPropertyName = "T7704")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PEnum>>), TypeInfoPropertyName = "T7705")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PEnum>>), TypeInfoPropertyName = "T7706")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PEnum>>), TypeInfoPropertyName = "T7707")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PEnum>>), TypeInfoPropertyName = "T7708")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PEnum>, int>), TypeInfoPropertyName = "T7709")] +[JsonSerializable(typeof(Cn_class_C<PEnum>), TypeInfoPropertyName = "T7710")] +[JsonSerializable(typeof(Cn_record_class_C<PEnum>), TypeInfoPropertyName = "T7711")] +[JsonSerializable(typeof(Cn_struct_C<PEnum>), TypeInfoPropertyName = "T7712")] +[JsonSerializable(typeof(Cn_record_struct_C<PEnum>), TypeInfoPropertyName = "T7713")] +[JsonSerializable(typeof(Cn_generic_type_C<PEnum, int>), TypeInfoPropertyName = "T7714")] +[JsonSerializable(typeof(Cn_class_R<PEnum>), TypeInfoPropertyName = "T7715")] +[JsonSerializable(typeof(Cn_record_class_R<PEnum>), TypeInfoPropertyName = "T7716")] +[JsonSerializable(typeof(Cn_struct_R<PEnum>), TypeInfoPropertyName = "T7717")] +[JsonSerializable(typeof(Cn_record_struct_R<PEnum>), TypeInfoPropertyName = "T7718")] +[JsonSerializable(typeof(Cn_generic_type_R<PEnum, int>), TypeInfoPropertyName = "T7719")] +[JsonSerializable(typeof(Cn_class_P<Patch<PEnum>>), TypeInfoPropertyName = "T7720")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<PEnum>>), TypeInfoPropertyName = "T7721")] +[JsonSerializable(typeof(Cn_struct_P<Patch<PEnum>>), TypeInfoPropertyName = "T7722")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<PEnum>>), TypeInfoPropertyName = "T7723")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<PEnum>, int>), TypeInfoPropertyName = "T7724")] +[JsonSerializable(typeof(Cn_class_P<PBP_enum>), TypeInfoPropertyName = "T7725")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_enum>), TypeInfoPropertyName = "T7726")] +[JsonSerializable(typeof(Cn_struct_P<PBP_enum>), TypeInfoPropertyName = "T7727")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_enum>), TypeInfoPropertyName = "T7728")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_enum, int>), TypeInfoPropertyName = "T7729")] +[JsonSerializable(typeof(PFlags))] +[JsonSerializable(typeof(Cn_class_P<PFlags>), TypeInfoPropertyName = "T7730")] +[JsonSerializable(typeof(Cn_record_class_P<PFlags>), TypeInfoPropertyName = "T7731")] +[JsonSerializable(typeof(Cn_struct_P<PFlags>), TypeInfoPropertyName = "T7732")] +[JsonSerializable(typeof(Cn_record_struct_P<PFlags>), TypeInfoPropertyName = "T7733")] +[JsonSerializable(typeof(Cn_generic_type_P<PFlags, int>), TypeInfoPropertyName = "T7734")] +[JsonSerializable(typeof(Cn_class_P<PFlags?>), TypeInfoPropertyName = "T7735")] +[JsonSerializable(typeof(Cn_record_class_P<PFlags?>), TypeInfoPropertyName = "T7736")] +[JsonSerializable(typeof(Cn_struct_P<PFlags?>), TypeInfoPropertyName = "T7737")] +[JsonSerializable(typeof(Cn_record_struct_P<PFlags?>), TypeInfoPropertyName = "T7738")] +[JsonSerializable(typeof(Cn_generic_type_P<PFlags?, int>), TypeInfoPropertyName = "T7739")] +[JsonSerializable(typeof(Cn_class_F<PFlags>), TypeInfoPropertyName = "T7740")] +[JsonSerializable(typeof(Cn_record_class_F<PFlags>), TypeInfoPropertyName = "T7741")] +[JsonSerializable(typeof(Cn_struct_F<PFlags>), TypeInfoPropertyName = "T7742")] +[JsonSerializable(typeof(Cn_record_struct_F<PFlags>), TypeInfoPropertyName = "T7743")] +[JsonSerializable(typeof(Cn_generic_type_F<PFlags, int>), TypeInfoPropertyName = "T7744")] +[JsonSerializable(typeof(Cn_class_P<List<PFlags>>), TypeInfoPropertyName = "T7745")] +[JsonSerializable(typeof(Cn_record_class_P<List<PFlags>>), TypeInfoPropertyName = "T7746")] +[JsonSerializable(typeof(Cn_struct_P<List<PFlags>>), TypeInfoPropertyName = "T7747")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PFlags>>), TypeInfoPropertyName = "T7748")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PFlags>, int>), TypeInfoPropertyName = "T7749")] +[JsonSerializable(typeof(Cn_class_P<PFlags[]>), TypeInfoPropertyName = "T7750")] +[JsonSerializable(typeof(Cn_record_class_P<PFlags[]>), TypeInfoPropertyName = "T7751")] +[JsonSerializable(typeof(Cn_struct_P<PFlags[]>), TypeInfoPropertyName = "T7752")] +[JsonSerializable(typeof(Cn_record_struct_P<PFlags[]>), TypeInfoPropertyName = "T7753")] +[JsonSerializable(typeof(Cn_generic_type_P<PFlags[], int>), TypeInfoPropertyName = "T7754")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PFlags>>), TypeInfoPropertyName = "T7755")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PFlags>>), TypeInfoPropertyName = "T7756")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PFlags>>), TypeInfoPropertyName = "T7757")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PFlags>>), TypeInfoPropertyName = "T7758")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PFlags>, int>), TypeInfoPropertyName = "T7759")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PFlags>>), TypeInfoPropertyName = "T7760")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PFlags>>), TypeInfoPropertyName = "T7761")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PFlags>>), TypeInfoPropertyName = "T7762")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PFlags>>), TypeInfoPropertyName = "T7763")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PFlags>, int>), TypeInfoPropertyName = "T7764")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<PFlags, int>>), TypeInfoPropertyName = "T7765")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<PFlags, int>>), TypeInfoPropertyName = "T7766")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<PFlags, int>>), TypeInfoPropertyName = "T7767")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<PFlags, int>>), TypeInfoPropertyName = "T7768")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<PFlags, int>, int>), TypeInfoPropertyName = "T7769")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PFlags>>), TypeInfoPropertyName = "T7770")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PFlags>>), TypeInfoPropertyName = "T7771")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PFlags>>), TypeInfoPropertyName = "T7772")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PFlags>>), TypeInfoPropertyName = "T7773")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PFlags>, int>), TypeInfoPropertyName = "T7774")] +[JsonSerializable(typeof(Cn_class_C<PFlags>), TypeInfoPropertyName = "T7775")] +[JsonSerializable(typeof(Cn_record_class_C<PFlags>), TypeInfoPropertyName = "T7776")] +[JsonSerializable(typeof(Cn_struct_C<PFlags>), TypeInfoPropertyName = "T7777")] +[JsonSerializable(typeof(Cn_record_struct_C<PFlags>), TypeInfoPropertyName = "T7778")] +[JsonSerializable(typeof(Cn_generic_type_C<PFlags, int>), TypeInfoPropertyName = "T7779")] +[JsonSerializable(typeof(Cn_class_R<PFlags>), TypeInfoPropertyName = "T7780")] +[JsonSerializable(typeof(Cn_record_class_R<PFlags>), TypeInfoPropertyName = "T7781")] +[JsonSerializable(typeof(Cn_struct_R<PFlags>), TypeInfoPropertyName = "T7782")] +[JsonSerializable(typeof(Cn_record_struct_R<PFlags>), TypeInfoPropertyName = "T7783")] +[JsonSerializable(typeof(Cn_generic_type_R<PFlags, int>), TypeInfoPropertyName = "T7784")] +[JsonSerializable(typeof(Cn_class_P<Patch<PFlags>>), TypeInfoPropertyName = "T7785")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<PFlags>>), TypeInfoPropertyName = "T7786")] +[JsonSerializable(typeof(Cn_struct_P<Patch<PFlags>>), TypeInfoPropertyName = "T7787")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<PFlags>>), TypeInfoPropertyName = "T7788")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<PFlags>, int>), TypeInfoPropertyName = "T7789")] +[JsonSerializable(typeof(Cn_class_P<PBP_flags_enum>), TypeInfoPropertyName = "T7790")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_flags_enum>), TypeInfoPropertyName = "T7791")] +[JsonSerializable(typeof(Cn_struct_P<PBP_flags_enum>), TypeInfoPropertyName = "T7792")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_flags_enum>), TypeInfoPropertyName = "T7793")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_flags_enum, int>), TypeInfoPropertyName = "T7794")] +[JsonSerializable(typeof(Uri))] +[JsonSerializable(typeof(Cn_class_P<Uri>), TypeInfoPropertyName = "T7795")] +[JsonSerializable(typeof(Cn_record_class_P<Uri>), TypeInfoPropertyName = "T7796")] +[JsonSerializable(typeof(Cn_struct_P<Uri>), TypeInfoPropertyName = "T7797")] +[JsonSerializable(typeof(Cn_record_struct_P<Uri>), TypeInfoPropertyName = "T7798")] +[JsonSerializable(typeof(Cn_generic_type_P<Uri, int>), TypeInfoPropertyName = "T7799")] +[JsonSerializable(typeof(Cn_class_F<Uri>), TypeInfoPropertyName = "T7800")] +[JsonSerializable(typeof(Cn_record_class_F<Uri>), TypeInfoPropertyName = "T7801")] +[JsonSerializable(typeof(Cn_struct_F<Uri>), TypeInfoPropertyName = "T7802")] +[JsonSerializable(typeof(Cn_record_struct_F<Uri>), TypeInfoPropertyName = "T7803")] +[JsonSerializable(typeof(Cn_generic_type_F<Uri, int>), TypeInfoPropertyName = "T7804")] +[JsonSerializable(typeof(Cn_class_P<List<Uri>>), TypeInfoPropertyName = "T7805")] +[JsonSerializable(typeof(Cn_record_class_P<List<Uri>>), TypeInfoPropertyName = "T7806")] +[JsonSerializable(typeof(Cn_struct_P<List<Uri>>), TypeInfoPropertyName = "T7807")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Uri>>), TypeInfoPropertyName = "T7808")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Uri>, int>), TypeInfoPropertyName = "T7809")] +[JsonSerializable(typeof(Cn_class_P<Uri[]>), TypeInfoPropertyName = "T7810")] +[JsonSerializable(typeof(Cn_record_class_P<Uri[]>), TypeInfoPropertyName = "T7811")] +[JsonSerializable(typeof(Cn_struct_P<Uri[]>), TypeInfoPropertyName = "T7812")] +[JsonSerializable(typeof(Cn_record_struct_P<Uri[]>), TypeInfoPropertyName = "T7813")] +[JsonSerializable(typeof(Cn_generic_type_P<Uri[], int>), TypeInfoPropertyName = "T7814")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<Uri>>), TypeInfoPropertyName = "T7815")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Uri>>), TypeInfoPropertyName = "T7816")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Uri>>), TypeInfoPropertyName = "T7817")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<Uri>>), TypeInfoPropertyName = "T7818")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<Uri>, int>), TypeInfoPropertyName = "T7819")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<Uri>>), TypeInfoPropertyName = "T7820")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<Uri>>), TypeInfoPropertyName = "T7821")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<Uri>>), TypeInfoPropertyName = "T7822")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<Uri>>), TypeInfoPropertyName = "T7823")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<Uri>, int>), TypeInfoPropertyName = "T7824")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<Uri, int>>), TypeInfoPropertyName = "T7825")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<Uri, int>>), TypeInfoPropertyName = "T7826")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<Uri, int>>), TypeInfoPropertyName = "T7827")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<Uri, int>>), TypeInfoPropertyName = "T7828")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<Uri, int>, int>), TypeInfoPropertyName = "T7829")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, Uri>>), TypeInfoPropertyName = "T7830")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, Uri>>), TypeInfoPropertyName = "T7831")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, Uri>>), TypeInfoPropertyName = "T7832")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, Uri>>), TypeInfoPropertyName = "T7833")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, Uri>, int>), TypeInfoPropertyName = "T7834")] +[JsonSerializable(typeof(Cn_class_C<Uri>), TypeInfoPropertyName = "T7835")] +[JsonSerializable(typeof(Cn_record_class_C<Uri>), TypeInfoPropertyName = "T7836")] +[JsonSerializable(typeof(Cn_struct_C<Uri>), TypeInfoPropertyName = "T7837")] +[JsonSerializable(typeof(Cn_record_struct_C<Uri>), TypeInfoPropertyName = "T7838")] +[JsonSerializable(typeof(Cn_generic_type_C<Uri, int>), TypeInfoPropertyName = "T7839")] +[JsonSerializable(typeof(Cn_class_R<Uri>), TypeInfoPropertyName = "T7840")] +[JsonSerializable(typeof(Cn_record_class_R<Uri>), TypeInfoPropertyName = "T7841")] +[JsonSerializable(typeof(Cn_struct_R<Uri>), TypeInfoPropertyName = "T7842")] +[JsonSerializable(typeof(Cn_record_struct_R<Uri>), TypeInfoPropertyName = "T7843")] +[JsonSerializable(typeof(Cn_generic_type_R<Uri, int>), TypeInfoPropertyName = "T7844")] +[JsonSerializable(typeof(Cn_class_P<Patch<Uri>>), TypeInfoPropertyName = "T7845")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<Uri>>), TypeInfoPropertyName = "T7846")] +[JsonSerializable(typeof(Cn_struct_P<Patch<Uri>>), TypeInfoPropertyName = "T7847")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<Uri>>), TypeInfoPropertyName = "T7848")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<Uri>, int>), TypeInfoPropertyName = "T7849")] +[JsonSerializable(typeof(Cn_class_P<PBP_Uri>), TypeInfoPropertyName = "T7850")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_Uri>), TypeInfoPropertyName = "T7851")] +[JsonSerializable(typeof(Cn_struct_P<PBP_Uri>), TypeInfoPropertyName = "T7852")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_Uri>), TypeInfoPropertyName = "T7853")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_Uri, int>), TypeInfoPropertyName = "T7854")] +[JsonSerializable(typeof(global::NodaTime.Instant))] +[JsonSerializable(typeof(Cn_class_P<global::NodaTime.Instant>), TypeInfoPropertyName = "T7855")] +[JsonSerializable(typeof(Cn_record_class_P<global::NodaTime.Instant>), TypeInfoPropertyName = "T7856")] +[JsonSerializable(typeof(Cn_struct_P<global::NodaTime.Instant>), TypeInfoPropertyName = "T7857")] +[JsonSerializable(typeof(Cn_record_struct_P<global::NodaTime.Instant>), TypeInfoPropertyName = "T7858")] +[JsonSerializable(typeof(Cn_generic_type_P<global::NodaTime.Instant, int>), TypeInfoPropertyName = "T7859")] +[JsonSerializable(typeof(Cn_class_P<global::NodaTime.Instant?>), TypeInfoPropertyName = "T7860")] +[JsonSerializable(typeof(Cn_record_class_P<global::NodaTime.Instant?>), TypeInfoPropertyName = "T7861")] +[JsonSerializable(typeof(Cn_struct_P<global::NodaTime.Instant?>), TypeInfoPropertyName = "T7862")] +[JsonSerializable(typeof(Cn_record_struct_P<global::NodaTime.Instant?>), TypeInfoPropertyName = "T7863")] +[JsonSerializable(typeof(Cn_generic_type_P<global::NodaTime.Instant?, int>), TypeInfoPropertyName = "T7864")] +[JsonSerializable(typeof(Cn_class_F<global::NodaTime.Instant>), TypeInfoPropertyName = "T7865")] +[JsonSerializable(typeof(Cn_record_class_F<global::NodaTime.Instant>), TypeInfoPropertyName = "T7866")] +[JsonSerializable(typeof(Cn_struct_F<global::NodaTime.Instant>), TypeInfoPropertyName = "T7867")] +[JsonSerializable(typeof(Cn_record_struct_F<global::NodaTime.Instant>), TypeInfoPropertyName = "T7868")] +[JsonSerializable(typeof(Cn_generic_type_F<global::NodaTime.Instant, int>), TypeInfoPropertyName = "T7869")] +[JsonSerializable(typeof(Cn_class_P<List<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7870")] +[JsonSerializable(typeof(Cn_record_class_P<List<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7871")] +[JsonSerializable(typeof(Cn_struct_P<List<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7872")] +[JsonSerializable(typeof(Cn_record_struct_P<List<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7873")] +[JsonSerializable(typeof(Cn_generic_type_P<List<global::NodaTime.Instant>, int>), TypeInfoPropertyName = "T7874")] +[JsonSerializable(typeof(Cn_class_P<global::NodaTime.Instant[]>), TypeInfoPropertyName = "T7875")] +[JsonSerializable(typeof(Cn_record_class_P<global::NodaTime.Instant[]>), TypeInfoPropertyName = "T7876")] +[JsonSerializable(typeof(Cn_struct_P<global::NodaTime.Instant[]>), TypeInfoPropertyName = "T7877")] +[JsonSerializable(typeof(Cn_record_struct_P<global::NodaTime.Instant[]>), TypeInfoPropertyName = "T7878")] +[JsonSerializable(typeof(Cn_generic_type_P<global::NodaTime.Instant[], int>), TypeInfoPropertyName = "T7879")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7880")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7881")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7882")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7883")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<global::NodaTime.Instant>, int>), TypeInfoPropertyName = "T7884")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7885")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7886")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7887")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7888")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<global::NodaTime.Instant>, int>), TypeInfoPropertyName = "T7889")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<global::NodaTime.Instant, int>>), TypeInfoPropertyName = "T7890")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<global::NodaTime.Instant, int>>), TypeInfoPropertyName = "T7891")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<global::NodaTime.Instant, int>>), TypeInfoPropertyName = "T7892")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<global::NodaTime.Instant, int>>), TypeInfoPropertyName = "T7893")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<global::NodaTime.Instant, int>, int>), TypeInfoPropertyName = "T7894")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, global::NodaTime.Instant>>), TypeInfoPropertyName = "T7895")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, global::NodaTime.Instant>>), TypeInfoPropertyName = "T7896")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, global::NodaTime.Instant>>), TypeInfoPropertyName = "T7897")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, global::NodaTime.Instant>>), TypeInfoPropertyName = "T7898")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, global::NodaTime.Instant>, int>), TypeInfoPropertyName = "T7899")] +[JsonSerializable(typeof(Cn_class_C<global::NodaTime.Instant>), TypeInfoPropertyName = "T7900")] +[JsonSerializable(typeof(Cn_record_class_C<global::NodaTime.Instant>), TypeInfoPropertyName = "T7901")] +[JsonSerializable(typeof(Cn_struct_C<global::NodaTime.Instant>), TypeInfoPropertyName = "T7902")] +[JsonSerializable(typeof(Cn_record_struct_C<global::NodaTime.Instant>), TypeInfoPropertyName = "T7903")] +[JsonSerializable(typeof(Cn_generic_type_C<global::NodaTime.Instant, int>), TypeInfoPropertyName = "T7904")] +[JsonSerializable(typeof(Cn_class_R<global::NodaTime.Instant>), TypeInfoPropertyName = "T7905")] +[JsonSerializable(typeof(Cn_record_class_R<global::NodaTime.Instant>), TypeInfoPropertyName = "T7906")] +[JsonSerializable(typeof(Cn_struct_R<global::NodaTime.Instant>), TypeInfoPropertyName = "T7907")] +[JsonSerializable(typeof(Cn_record_struct_R<global::NodaTime.Instant>), TypeInfoPropertyName = "T7908")] +[JsonSerializable(typeof(Cn_generic_type_R<global::NodaTime.Instant, int>), TypeInfoPropertyName = "T7909")] +[JsonSerializable(typeof(Cn_class_P<Patch<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7910")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7911")] +[JsonSerializable(typeof(Cn_struct_P<Patch<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7912")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<global::NodaTime.Instant>>), TypeInfoPropertyName = "T7913")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<global::NodaTime.Instant>, int>), TypeInfoPropertyName = "T7914")] +[JsonSerializable(typeof(Cn_class_P<PBP_NodaTime_Instant>), TypeInfoPropertyName = "T7915")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_NodaTime_Instant>), TypeInfoPropertyName = "T7916")] +[JsonSerializable(typeof(Cn_struct_P<PBP_NodaTime_Instant>), TypeInfoPropertyName = "T7917")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_NodaTime_Instant>), TypeInfoPropertyName = "T7918")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_NodaTime_Instant, int>), TypeInfoPropertyName = "T7919")] +[JsonSerializable(typeof(PRec))] +[JsonSerializable(typeof(Cn_class_P<PRec>), TypeInfoPropertyName = "T7920")] +[JsonSerializable(typeof(Cn_record_class_P<PRec>), TypeInfoPropertyName = "T7921")] +[JsonSerializable(typeof(Cn_struct_P<PRec>), TypeInfoPropertyName = "T7922")] +[JsonSerializable(typeof(Cn_record_struct_P<PRec>), TypeInfoPropertyName = "T7923")] +[JsonSerializable(typeof(Cn_generic_type_P<PRec, int>), TypeInfoPropertyName = "T7924")] +[JsonSerializable(typeof(Cn_class_F<PRec>), TypeInfoPropertyName = "T7925")] +[JsonSerializable(typeof(Cn_record_class_F<PRec>), TypeInfoPropertyName = "T7926")] +[JsonSerializable(typeof(Cn_struct_F<PRec>), TypeInfoPropertyName = "T7927")] +[JsonSerializable(typeof(Cn_record_struct_F<PRec>), TypeInfoPropertyName = "T7928")] +[JsonSerializable(typeof(Cn_generic_type_F<PRec, int>), TypeInfoPropertyName = "T7929")] +[JsonSerializable(typeof(Cn_class_P<List<PRec>>), TypeInfoPropertyName = "T7930")] +[JsonSerializable(typeof(Cn_record_class_P<List<PRec>>), TypeInfoPropertyName = "T7931")] +[JsonSerializable(typeof(Cn_struct_P<List<PRec>>), TypeInfoPropertyName = "T7932")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PRec>>), TypeInfoPropertyName = "T7933")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PRec>, int>), TypeInfoPropertyName = "T7934")] +[JsonSerializable(typeof(Cn_class_P<PRec[]>), TypeInfoPropertyName = "T7935")] +[JsonSerializable(typeof(Cn_record_class_P<PRec[]>), TypeInfoPropertyName = "T7936")] +[JsonSerializable(typeof(Cn_struct_P<PRec[]>), TypeInfoPropertyName = "T7937")] +[JsonSerializable(typeof(Cn_record_struct_P<PRec[]>), TypeInfoPropertyName = "T7938")] +[JsonSerializable(typeof(Cn_generic_type_P<PRec[], int>), TypeInfoPropertyName = "T7939")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PRec>>), TypeInfoPropertyName = "T7940")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PRec>>), TypeInfoPropertyName = "T7941")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PRec>>), TypeInfoPropertyName = "T7942")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PRec>>), TypeInfoPropertyName = "T7943")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PRec>, int>), TypeInfoPropertyName = "T7944")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PRec>>), TypeInfoPropertyName = "T7945")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PRec>>), TypeInfoPropertyName = "T7946")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PRec>>), TypeInfoPropertyName = "T7947")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PRec>>), TypeInfoPropertyName = "T7948")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PRec>, int>), TypeInfoPropertyName = "T7949")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<PRec, int>>), TypeInfoPropertyName = "T7950")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<PRec, int>>), TypeInfoPropertyName = "T7951")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<PRec, int>>), TypeInfoPropertyName = "T7952")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<PRec, int>>), TypeInfoPropertyName = "T7953")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<PRec, int>, int>), TypeInfoPropertyName = "T7954")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PRec>>), TypeInfoPropertyName = "T7955")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PRec>>), TypeInfoPropertyName = "T7956")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PRec>>), TypeInfoPropertyName = "T7957")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PRec>>), TypeInfoPropertyName = "T7958")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PRec>, int>), TypeInfoPropertyName = "T7959")] +[JsonSerializable(typeof(Cn_class_C<PRec>), TypeInfoPropertyName = "T7960")] +[JsonSerializable(typeof(Cn_record_class_C<PRec>), TypeInfoPropertyName = "T7961")] +[JsonSerializable(typeof(Cn_struct_C<PRec>), TypeInfoPropertyName = "T7962")] +[JsonSerializable(typeof(Cn_record_struct_C<PRec>), TypeInfoPropertyName = "T7963")] +[JsonSerializable(typeof(Cn_generic_type_C<PRec, int>), TypeInfoPropertyName = "T7964")] +[JsonSerializable(typeof(Cn_class_R<PRec>), TypeInfoPropertyName = "T7965")] +[JsonSerializable(typeof(Cn_record_class_R<PRec>), TypeInfoPropertyName = "T7966")] +[JsonSerializable(typeof(Cn_struct_R<PRec>), TypeInfoPropertyName = "T7967")] +[JsonSerializable(typeof(Cn_record_struct_R<PRec>), TypeInfoPropertyName = "T7968")] +[JsonSerializable(typeof(Cn_generic_type_R<PRec, int>), TypeInfoPropertyName = "T7969")] +[JsonSerializable(typeof(Cn_class_P<Patch<PRec>>), TypeInfoPropertyName = "T7970")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<PRec>>), TypeInfoPropertyName = "T7971")] +[JsonSerializable(typeof(Cn_struct_P<Patch<PRec>>), TypeInfoPropertyName = "T7972")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<PRec>>), TypeInfoPropertyName = "T7973")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<PRec>, int>), TypeInfoPropertyName = "T7974")] +[JsonSerializable(typeof(Cn_class_P<PBP_own_record_class>), TypeInfoPropertyName = "T7975")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_own_record_class>), TypeInfoPropertyName = "T7976")] +[JsonSerializable(typeof(Cn_struct_P<PBP_own_record_class>), TypeInfoPropertyName = "T7977")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_own_record_class>), TypeInfoPropertyName = "T7978")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_own_record_class, int>), TypeInfoPropertyName = "T7979")] +[JsonSerializable(typeof(Guid?), TypeInfoPropertyName = "T7980")] +[JsonSerializable(typeof(Cn_class_F<Guid?>), TypeInfoPropertyName = "T7981")] +[JsonSerializable(typeof(Cn_record_class_F<Guid?>), TypeInfoPropertyName = "T7982")] +[JsonSerializable(typeof(Cn_struct_F<Guid?>), TypeInfoPropertyName = "T7983")] +[JsonSerializable(typeof(Cn_record_struct_F<Guid?>), TypeInfoPropertyName = "T7984")] +[JsonSerializable(typeof(Cn_generic_type_F<Guid?, int>), TypeInfoPropertyName = "T7985")] +[JsonSerializable(typeof(Cn_class_P<List<Guid?>>), TypeInfoPropertyName = "T7986")] +[JsonSerializable(typeof(Cn_record_class_P<List<Guid?>>), TypeInfoPropertyName = "T7987")] +[JsonSerializable(typeof(Cn_struct_P<List<Guid?>>), TypeInfoPropertyName = "T7988")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Guid?>>), TypeInfoPropertyName = "T7989")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Guid?>, int>), TypeInfoPropertyName = "T7990")] +[JsonSerializable(typeof(Cn_class_P<Guid?[]>), TypeInfoPropertyName = "T7991")] +[JsonSerializable(typeof(Cn_record_class_P<Guid?[]>), TypeInfoPropertyName = "T7992")] +[JsonSerializable(typeof(Cn_struct_P<Guid?[]>), TypeInfoPropertyName = "T7993")] +[JsonSerializable(typeof(Cn_record_struct_P<Guid?[]>), TypeInfoPropertyName = "T7994")] +[JsonSerializable(typeof(Cn_generic_type_P<Guid?[], int>), TypeInfoPropertyName = "T7995")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<Guid?>>), TypeInfoPropertyName = "T7996")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Guid?>>), TypeInfoPropertyName = "T7997")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Guid?>>), TypeInfoPropertyName = "T7998")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<Guid?>>), TypeInfoPropertyName = "T7999")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<Guid?>, int>), TypeInfoPropertyName = "T8000")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<Guid?>>), TypeInfoPropertyName = "T8001")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<Guid?>>), TypeInfoPropertyName = "T8002")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<Guid?>>), TypeInfoPropertyName = "T8003")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<Guid?>>), TypeInfoPropertyName = "T8004")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<Guid?>, int>), TypeInfoPropertyName = "T8005")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, Guid?>>), TypeInfoPropertyName = "T8006")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, Guid?>>), TypeInfoPropertyName = "T8007")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, Guid?>>), TypeInfoPropertyName = "T8008")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, Guid?>>), TypeInfoPropertyName = "T8009")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, Guid?>, int>), TypeInfoPropertyName = "T8010")] +[JsonSerializable(typeof(Cn_class_C<Guid?>), TypeInfoPropertyName = "T8011")] +[JsonSerializable(typeof(Cn_record_class_C<Guid?>), TypeInfoPropertyName = "T8012")] +[JsonSerializable(typeof(Cn_struct_C<Guid?>), TypeInfoPropertyName = "T8013")] +[JsonSerializable(typeof(Cn_record_struct_C<Guid?>), TypeInfoPropertyName = "T8014")] +[JsonSerializable(typeof(Cn_generic_type_C<Guid?, int>), TypeInfoPropertyName = "T8015")] +[JsonSerializable(typeof(Cn_class_R<Guid?>), TypeInfoPropertyName = "T8016")] +[JsonSerializable(typeof(Cn_record_class_R<Guid?>), TypeInfoPropertyName = "T8017")] +[JsonSerializable(typeof(Cn_struct_R<Guid?>), TypeInfoPropertyName = "T8018")] +[JsonSerializable(typeof(Cn_record_struct_R<Guid?>), TypeInfoPropertyName = "T8019")] +[JsonSerializable(typeof(Cn_generic_type_R<Guid?, int>), TypeInfoPropertyName = "T8020")] +[JsonSerializable(typeof(Cn_class_P<PBP_Guid_N>), TypeInfoPropertyName = "T8021")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_Guid_N>), TypeInfoPropertyName = "T8022")] +[JsonSerializable(typeof(Cn_struct_P<PBP_Guid_N>), TypeInfoPropertyName = "T8023")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_Guid_N>), TypeInfoPropertyName = "T8024")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_Guid_N, int>), TypeInfoPropertyName = "T8025")] +[JsonSerializable(typeof(int?), TypeInfoPropertyName = "T8026")] +[JsonSerializable(typeof(Cn_class_F<int?>), TypeInfoPropertyName = "T8027")] +[JsonSerializable(typeof(Cn_record_class_F<int?>), TypeInfoPropertyName = "T8028")] +[JsonSerializable(typeof(Cn_struct_F<int?>), TypeInfoPropertyName = "T8029")] +[JsonSerializable(typeof(Cn_record_struct_F<int?>), TypeInfoPropertyName = "T8030")] +[JsonSerializable(typeof(Cn_generic_type_F<int?, int>), TypeInfoPropertyName = "T8031")] +[JsonSerializable(typeof(Cn_class_P<List<int?>>), TypeInfoPropertyName = "T8032")] +[JsonSerializable(typeof(Cn_record_class_P<List<int?>>), TypeInfoPropertyName = "T8033")] +[JsonSerializable(typeof(Cn_struct_P<List<int?>>), TypeInfoPropertyName = "T8034")] +[JsonSerializable(typeof(Cn_record_struct_P<List<int?>>), TypeInfoPropertyName = "T8035")] +[JsonSerializable(typeof(Cn_generic_type_P<List<int?>, int>), TypeInfoPropertyName = "T8036")] +[JsonSerializable(typeof(Cn_class_P<int?[]>), TypeInfoPropertyName = "T8037")] +[JsonSerializable(typeof(Cn_record_class_P<int?[]>), TypeInfoPropertyName = "T8038")] +[JsonSerializable(typeof(Cn_struct_P<int?[]>), TypeInfoPropertyName = "T8039")] +[JsonSerializable(typeof(Cn_record_struct_P<int?[]>), TypeInfoPropertyName = "T8040")] +[JsonSerializable(typeof(Cn_generic_type_P<int?[], int>), TypeInfoPropertyName = "T8041")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<int?>>), TypeInfoPropertyName = "T8042")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<int?>>), TypeInfoPropertyName = "T8043")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<int?>>), TypeInfoPropertyName = "T8044")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<int?>>), TypeInfoPropertyName = "T8045")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<int?>, int>), TypeInfoPropertyName = "T8046")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<int?>>), TypeInfoPropertyName = "T8047")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<int?>>), TypeInfoPropertyName = "T8048")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<int?>>), TypeInfoPropertyName = "T8049")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<int?>>), TypeInfoPropertyName = "T8050")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<int?>, int>), TypeInfoPropertyName = "T8051")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, int?>>), TypeInfoPropertyName = "T8052")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, int?>>), TypeInfoPropertyName = "T8053")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, int?>>), TypeInfoPropertyName = "T8054")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, int?>>), TypeInfoPropertyName = "T8055")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, int?>, int>), TypeInfoPropertyName = "T8056")] +[JsonSerializable(typeof(Cn_class_C<int?>), TypeInfoPropertyName = "T8057")] +[JsonSerializable(typeof(Cn_record_class_C<int?>), TypeInfoPropertyName = "T8058")] +[JsonSerializable(typeof(Cn_struct_C<int?>), TypeInfoPropertyName = "T8059")] +[JsonSerializable(typeof(Cn_record_struct_C<int?>), TypeInfoPropertyName = "T8060")] +[JsonSerializable(typeof(Cn_generic_type_C<int?, int>), TypeInfoPropertyName = "T8061")] +[JsonSerializable(typeof(Cn_class_R<int?>), TypeInfoPropertyName = "T8062")] +[JsonSerializable(typeof(Cn_record_class_R<int?>), TypeInfoPropertyName = "T8063")] +[JsonSerializable(typeof(Cn_struct_R<int?>), TypeInfoPropertyName = "T8064")] +[JsonSerializable(typeof(Cn_record_struct_R<int?>), TypeInfoPropertyName = "T8065")] +[JsonSerializable(typeof(Cn_generic_type_R<int?, int>), TypeInfoPropertyName = "T8066")] +[JsonSerializable(typeof(Cn_class_P<PBP_int_N>), TypeInfoPropertyName = "T8067")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_int_N>), TypeInfoPropertyName = "T8068")] +[JsonSerializable(typeof(Cn_struct_P<PBP_int_N>), TypeInfoPropertyName = "T8069")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_int_N>), TypeInfoPropertyName = "T8070")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_int_N, int>), TypeInfoPropertyName = "T8071")] +[JsonSerializable(typeof(long?), TypeInfoPropertyName = "T8072")] +[JsonSerializable(typeof(Cn_class_F<long?>), TypeInfoPropertyName = "T8073")] +[JsonSerializable(typeof(Cn_record_class_F<long?>), TypeInfoPropertyName = "T8074")] +[JsonSerializable(typeof(Cn_struct_F<long?>), TypeInfoPropertyName = "T8075")] +[JsonSerializable(typeof(Cn_record_struct_F<long?>), TypeInfoPropertyName = "T8076")] +[JsonSerializable(typeof(Cn_generic_type_F<long?, int>), TypeInfoPropertyName = "T8077")] +[JsonSerializable(typeof(Cn_class_P<List<long?>>), TypeInfoPropertyName = "T8078")] +[JsonSerializable(typeof(Cn_record_class_P<List<long?>>), TypeInfoPropertyName = "T8079")] +[JsonSerializable(typeof(Cn_struct_P<List<long?>>), TypeInfoPropertyName = "T8080")] +[JsonSerializable(typeof(Cn_record_struct_P<List<long?>>), TypeInfoPropertyName = "T8081")] +[JsonSerializable(typeof(Cn_generic_type_P<List<long?>, int>), TypeInfoPropertyName = "T8082")] +[JsonSerializable(typeof(Cn_class_P<long?[]>), TypeInfoPropertyName = "T8083")] +[JsonSerializable(typeof(Cn_record_class_P<long?[]>), TypeInfoPropertyName = "T8084")] +[JsonSerializable(typeof(Cn_struct_P<long?[]>), TypeInfoPropertyName = "T8085")] +[JsonSerializable(typeof(Cn_record_struct_P<long?[]>), TypeInfoPropertyName = "T8086")] +[JsonSerializable(typeof(Cn_generic_type_P<long?[], int>), TypeInfoPropertyName = "T8087")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<long?>>), TypeInfoPropertyName = "T8088")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<long?>>), TypeInfoPropertyName = "T8089")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<long?>>), TypeInfoPropertyName = "T8090")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<long?>>), TypeInfoPropertyName = "T8091")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<long?>, int>), TypeInfoPropertyName = "T8092")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<long?>>), TypeInfoPropertyName = "T8093")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<long?>>), TypeInfoPropertyName = "T8094")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<long?>>), TypeInfoPropertyName = "T8095")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<long?>>), TypeInfoPropertyName = "T8096")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<long?>, int>), TypeInfoPropertyName = "T8097")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, long?>>), TypeInfoPropertyName = "T8098")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, long?>>), TypeInfoPropertyName = "T8099")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, long?>>), TypeInfoPropertyName = "T8100")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, long?>>), TypeInfoPropertyName = "T8101")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, long?>, int>), TypeInfoPropertyName = "T8102")] +[JsonSerializable(typeof(Cn_class_C<long?>), TypeInfoPropertyName = "T8103")] +[JsonSerializable(typeof(Cn_record_class_C<long?>), TypeInfoPropertyName = "T8104")] +[JsonSerializable(typeof(Cn_struct_C<long?>), TypeInfoPropertyName = "T8105")] +[JsonSerializable(typeof(Cn_record_struct_C<long?>), TypeInfoPropertyName = "T8106")] +[JsonSerializable(typeof(Cn_generic_type_C<long?, int>), TypeInfoPropertyName = "T8107")] +[JsonSerializable(typeof(Cn_class_R<long?>), TypeInfoPropertyName = "T8108")] +[JsonSerializable(typeof(Cn_record_class_R<long?>), TypeInfoPropertyName = "T8109")] +[JsonSerializable(typeof(Cn_struct_R<long?>), TypeInfoPropertyName = "T8110")] +[JsonSerializable(typeof(Cn_record_struct_R<long?>), TypeInfoPropertyName = "T8111")] +[JsonSerializable(typeof(Cn_generic_type_R<long?, int>), TypeInfoPropertyName = "T8112")] +[JsonSerializable(typeof(Cn_class_P<PBP_long_N>), TypeInfoPropertyName = "T8113")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_long_N>), TypeInfoPropertyName = "T8114")] +[JsonSerializable(typeof(Cn_struct_P<PBP_long_N>), TypeInfoPropertyName = "T8115")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_long_N>), TypeInfoPropertyName = "T8116")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_long_N, int>), TypeInfoPropertyName = "T8117")] +[JsonSerializable(typeof(byte?), TypeInfoPropertyName = "T8118")] +[JsonSerializable(typeof(Cn_class_F<byte?>), TypeInfoPropertyName = "T8119")] +[JsonSerializable(typeof(Cn_record_class_F<byte?>), TypeInfoPropertyName = "T8120")] +[JsonSerializable(typeof(Cn_struct_F<byte?>), TypeInfoPropertyName = "T8121")] +[JsonSerializable(typeof(Cn_record_struct_F<byte?>), TypeInfoPropertyName = "T8122")] +[JsonSerializable(typeof(Cn_generic_type_F<byte?, int>), TypeInfoPropertyName = "T8123")] +[JsonSerializable(typeof(Cn_class_P<List<byte?>>), TypeInfoPropertyName = "T8124")] +[JsonSerializable(typeof(Cn_record_class_P<List<byte?>>), TypeInfoPropertyName = "T8125")] +[JsonSerializable(typeof(Cn_struct_P<List<byte?>>), TypeInfoPropertyName = "T8126")] +[JsonSerializable(typeof(Cn_record_struct_P<List<byte?>>), TypeInfoPropertyName = "T8127")] +[JsonSerializable(typeof(Cn_generic_type_P<List<byte?>, int>), TypeInfoPropertyName = "T8128")] +[JsonSerializable(typeof(Cn_class_P<byte?[]>), TypeInfoPropertyName = "T8129")] +[JsonSerializable(typeof(Cn_record_class_P<byte?[]>), TypeInfoPropertyName = "T8130")] +[JsonSerializable(typeof(Cn_struct_P<byte?[]>), TypeInfoPropertyName = "T8131")] +[JsonSerializable(typeof(Cn_record_struct_P<byte?[]>), TypeInfoPropertyName = "T8132")] +[JsonSerializable(typeof(Cn_generic_type_P<byte?[], int>), TypeInfoPropertyName = "T8133")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<byte?>>), TypeInfoPropertyName = "T8134")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<byte?>>), TypeInfoPropertyName = "T8135")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<byte?>>), TypeInfoPropertyName = "T8136")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<byte?>>), TypeInfoPropertyName = "T8137")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<byte?>, int>), TypeInfoPropertyName = "T8138")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<byte?>>), TypeInfoPropertyName = "T8139")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<byte?>>), TypeInfoPropertyName = "T8140")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<byte?>>), TypeInfoPropertyName = "T8141")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<byte?>>), TypeInfoPropertyName = "T8142")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<byte?>, int>), TypeInfoPropertyName = "T8143")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, byte?>>), TypeInfoPropertyName = "T8144")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, byte?>>), TypeInfoPropertyName = "T8145")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, byte?>>), TypeInfoPropertyName = "T8146")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, byte?>>), TypeInfoPropertyName = "T8147")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, byte?>, int>), TypeInfoPropertyName = "T8148")] +[JsonSerializable(typeof(Cn_class_C<byte?>), TypeInfoPropertyName = "T8149")] +[JsonSerializable(typeof(Cn_record_class_C<byte?>), TypeInfoPropertyName = "T8150")] +[JsonSerializable(typeof(Cn_struct_C<byte?>), TypeInfoPropertyName = "T8151")] +[JsonSerializable(typeof(Cn_record_struct_C<byte?>), TypeInfoPropertyName = "T8152")] +[JsonSerializable(typeof(Cn_generic_type_C<byte?, int>), TypeInfoPropertyName = "T8153")] +[JsonSerializable(typeof(Cn_class_R<byte?>), TypeInfoPropertyName = "T8154")] +[JsonSerializable(typeof(Cn_record_class_R<byte?>), TypeInfoPropertyName = "T8155")] +[JsonSerializable(typeof(Cn_struct_R<byte?>), TypeInfoPropertyName = "T8156")] +[JsonSerializable(typeof(Cn_record_struct_R<byte?>), TypeInfoPropertyName = "T8157")] +[JsonSerializable(typeof(Cn_generic_type_R<byte?, int>), TypeInfoPropertyName = "T8158")] +[JsonSerializable(typeof(Cn_class_P<PBP_byte_N>), TypeInfoPropertyName = "T8159")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_byte_N>), TypeInfoPropertyName = "T8160")] +[JsonSerializable(typeof(Cn_struct_P<PBP_byte_N>), TypeInfoPropertyName = "T8161")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_byte_N>), TypeInfoPropertyName = "T8162")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_byte_N, int>), TypeInfoPropertyName = "T8163")] +[JsonSerializable(typeof(ulong?), TypeInfoPropertyName = "T8164")] +[JsonSerializable(typeof(Cn_class_F<ulong?>), TypeInfoPropertyName = "T8165")] +[JsonSerializable(typeof(Cn_record_class_F<ulong?>), TypeInfoPropertyName = "T8166")] +[JsonSerializable(typeof(Cn_struct_F<ulong?>), TypeInfoPropertyName = "T8167")] +[JsonSerializable(typeof(Cn_record_struct_F<ulong?>), TypeInfoPropertyName = "T8168")] +[JsonSerializable(typeof(Cn_generic_type_F<ulong?, int>), TypeInfoPropertyName = "T8169")] +[JsonSerializable(typeof(Cn_class_P<List<ulong?>>), TypeInfoPropertyName = "T8170")] +[JsonSerializable(typeof(Cn_record_class_P<List<ulong?>>), TypeInfoPropertyName = "T8171")] +[JsonSerializable(typeof(Cn_struct_P<List<ulong?>>), TypeInfoPropertyName = "T8172")] +[JsonSerializable(typeof(Cn_record_struct_P<List<ulong?>>), TypeInfoPropertyName = "T8173")] +[JsonSerializable(typeof(Cn_generic_type_P<List<ulong?>, int>), TypeInfoPropertyName = "T8174")] +[JsonSerializable(typeof(Cn_class_P<ulong?[]>), TypeInfoPropertyName = "T8175")] +[JsonSerializable(typeof(Cn_record_class_P<ulong?[]>), TypeInfoPropertyName = "T8176")] +[JsonSerializable(typeof(Cn_struct_P<ulong?[]>), TypeInfoPropertyName = "T8177")] +[JsonSerializable(typeof(Cn_record_struct_P<ulong?[]>), TypeInfoPropertyName = "T8178")] +[JsonSerializable(typeof(Cn_generic_type_P<ulong?[], int>), TypeInfoPropertyName = "T8179")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<ulong?>>), TypeInfoPropertyName = "T8180")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<ulong?>>), TypeInfoPropertyName = "T8181")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<ulong?>>), TypeInfoPropertyName = "T8182")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<ulong?>>), TypeInfoPropertyName = "T8183")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<ulong?>, int>), TypeInfoPropertyName = "T8184")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<ulong?>>), TypeInfoPropertyName = "T8185")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<ulong?>>), TypeInfoPropertyName = "T8186")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<ulong?>>), TypeInfoPropertyName = "T8187")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<ulong?>>), TypeInfoPropertyName = "T8188")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<ulong?>, int>), TypeInfoPropertyName = "T8189")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, ulong?>>), TypeInfoPropertyName = "T8190")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, ulong?>>), TypeInfoPropertyName = "T8191")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, ulong?>>), TypeInfoPropertyName = "T8192")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, ulong?>>), TypeInfoPropertyName = "T8193")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, ulong?>, int>), TypeInfoPropertyName = "T8194")] +[JsonSerializable(typeof(Cn_class_C<ulong?>), TypeInfoPropertyName = "T8195")] +[JsonSerializable(typeof(Cn_record_class_C<ulong?>), TypeInfoPropertyName = "T8196")] +[JsonSerializable(typeof(Cn_struct_C<ulong?>), TypeInfoPropertyName = "T8197")] +[JsonSerializable(typeof(Cn_record_struct_C<ulong?>), TypeInfoPropertyName = "T8198")] +[JsonSerializable(typeof(Cn_generic_type_C<ulong?, int>), TypeInfoPropertyName = "T8199")] +[JsonSerializable(typeof(Cn_class_R<ulong?>), TypeInfoPropertyName = "T8200")] +[JsonSerializable(typeof(Cn_record_class_R<ulong?>), TypeInfoPropertyName = "T8201")] +[JsonSerializable(typeof(Cn_struct_R<ulong?>), TypeInfoPropertyName = "T8202")] +[JsonSerializable(typeof(Cn_record_struct_R<ulong?>), TypeInfoPropertyName = "T8203")] +[JsonSerializable(typeof(Cn_generic_type_R<ulong?, int>), TypeInfoPropertyName = "T8204")] +[JsonSerializable(typeof(Cn_class_P<PBP_ulong_N>), TypeInfoPropertyName = "T8205")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_ulong_N>), TypeInfoPropertyName = "T8206")] +[JsonSerializable(typeof(Cn_struct_P<PBP_ulong_N>), TypeInfoPropertyName = "T8207")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_ulong_N>), TypeInfoPropertyName = "T8208")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_ulong_N, int>), TypeInfoPropertyName = "T8209")] +[JsonSerializable(typeof(decimal?), TypeInfoPropertyName = "T8210")] +[JsonSerializable(typeof(Cn_class_F<decimal?>), TypeInfoPropertyName = "T8211")] +[JsonSerializable(typeof(Cn_record_class_F<decimal?>), TypeInfoPropertyName = "T8212")] +[JsonSerializable(typeof(Cn_struct_F<decimal?>), TypeInfoPropertyName = "T8213")] +[JsonSerializable(typeof(Cn_record_struct_F<decimal?>), TypeInfoPropertyName = "T8214")] +[JsonSerializable(typeof(Cn_generic_type_F<decimal?, int>), TypeInfoPropertyName = "T8215")] +[JsonSerializable(typeof(Cn_class_P<List<decimal?>>), TypeInfoPropertyName = "T8216")] +[JsonSerializable(typeof(Cn_record_class_P<List<decimal?>>), TypeInfoPropertyName = "T8217")] +[JsonSerializable(typeof(Cn_struct_P<List<decimal?>>), TypeInfoPropertyName = "T8218")] +[JsonSerializable(typeof(Cn_record_struct_P<List<decimal?>>), TypeInfoPropertyName = "T8219")] +[JsonSerializable(typeof(Cn_generic_type_P<List<decimal?>, int>), TypeInfoPropertyName = "T8220")] +[JsonSerializable(typeof(Cn_class_P<decimal?[]>), TypeInfoPropertyName = "T8221")] +[JsonSerializable(typeof(Cn_record_class_P<decimal?[]>), TypeInfoPropertyName = "T8222")] +[JsonSerializable(typeof(Cn_struct_P<decimal?[]>), TypeInfoPropertyName = "T8223")] +[JsonSerializable(typeof(Cn_record_struct_P<decimal?[]>), TypeInfoPropertyName = "T8224")] +[JsonSerializable(typeof(Cn_generic_type_P<decimal?[], int>), TypeInfoPropertyName = "T8225")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<decimal?>>), TypeInfoPropertyName = "T8226")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<decimal?>>), TypeInfoPropertyName = "T8227")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<decimal?>>), TypeInfoPropertyName = "T8228")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<decimal?>>), TypeInfoPropertyName = "T8229")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<decimal?>, int>), TypeInfoPropertyName = "T8230")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<decimal?>>), TypeInfoPropertyName = "T8231")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<decimal?>>), TypeInfoPropertyName = "T8232")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<decimal?>>), TypeInfoPropertyName = "T8233")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<decimal?>>), TypeInfoPropertyName = "T8234")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<decimal?>, int>), TypeInfoPropertyName = "T8235")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, decimal?>>), TypeInfoPropertyName = "T8236")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, decimal?>>), TypeInfoPropertyName = "T8237")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, decimal?>>), TypeInfoPropertyName = "T8238")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, decimal?>>), TypeInfoPropertyName = "T8239")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, decimal?>, int>), TypeInfoPropertyName = "T8240")] +[JsonSerializable(typeof(Cn_class_C<decimal?>), TypeInfoPropertyName = "T8241")] +[JsonSerializable(typeof(Cn_record_class_C<decimal?>), TypeInfoPropertyName = "T8242")] +[JsonSerializable(typeof(Cn_struct_C<decimal?>), TypeInfoPropertyName = "T8243")] +[JsonSerializable(typeof(Cn_record_struct_C<decimal?>), TypeInfoPropertyName = "T8244")] +[JsonSerializable(typeof(Cn_generic_type_C<decimal?, int>), TypeInfoPropertyName = "T8245")] +[JsonSerializable(typeof(Cn_class_R<decimal?>), TypeInfoPropertyName = "T8246")] +[JsonSerializable(typeof(Cn_record_class_R<decimal?>), TypeInfoPropertyName = "T8247")] +[JsonSerializable(typeof(Cn_struct_R<decimal?>), TypeInfoPropertyName = "T8248")] +[JsonSerializable(typeof(Cn_record_struct_R<decimal?>), TypeInfoPropertyName = "T8249")] +[JsonSerializable(typeof(Cn_generic_type_R<decimal?, int>), TypeInfoPropertyName = "T8250")] +[JsonSerializable(typeof(Cn_class_P<PBP_decimal_N>), TypeInfoPropertyName = "T8251")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_decimal_N>), TypeInfoPropertyName = "T8252")] +[JsonSerializable(typeof(Cn_struct_P<PBP_decimal_N>), TypeInfoPropertyName = "T8253")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_decimal_N>), TypeInfoPropertyName = "T8254")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_decimal_N, int>), TypeInfoPropertyName = "T8255")] +[JsonSerializable(typeof(double?), TypeInfoPropertyName = "T8256")] +[JsonSerializable(typeof(Cn_class_F<double?>), TypeInfoPropertyName = "T8257")] +[JsonSerializable(typeof(Cn_record_class_F<double?>), TypeInfoPropertyName = "T8258")] +[JsonSerializable(typeof(Cn_struct_F<double?>), TypeInfoPropertyName = "T8259")] +[JsonSerializable(typeof(Cn_record_struct_F<double?>), TypeInfoPropertyName = "T8260")] +[JsonSerializable(typeof(Cn_generic_type_F<double?, int>), TypeInfoPropertyName = "T8261")] +[JsonSerializable(typeof(Cn_class_P<List<double?>>), TypeInfoPropertyName = "T8262")] +[JsonSerializable(typeof(Cn_record_class_P<List<double?>>), TypeInfoPropertyName = "T8263")] +[JsonSerializable(typeof(Cn_struct_P<List<double?>>), TypeInfoPropertyName = "T8264")] +[JsonSerializable(typeof(Cn_record_struct_P<List<double?>>), TypeInfoPropertyName = "T8265")] +[JsonSerializable(typeof(Cn_generic_type_P<List<double?>, int>), TypeInfoPropertyName = "T8266")] +[JsonSerializable(typeof(Cn_class_P<double?[]>), TypeInfoPropertyName = "T8267")] +[JsonSerializable(typeof(Cn_record_class_P<double?[]>), TypeInfoPropertyName = "T8268")] +[JsonSerializable(typeof(Cn_struct_P<double?[]>), TypeInfoPropertyName = "T8269")] +[JsonSerializable(typeof(Cn_record_struct_P<double?[]>), TypeInfoPropertyName = "T8270")] +[JsonSerializable(typeof(Cn_generic_type_P<double?[], int>), TypeInfoPropertyName = "T8271")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<double?>>), TypeInfoPropertyName = "T8272")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<double?>>), TypeInfoPropertyName = "T8273")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<double?>>), TypeInfoPropertyName = "T8274")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<double?>>), TypeInfoPropertyName = "T8275")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<double?>, int>), TypeInfoPropertyName = "T8276")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<double?>>), TypeInfoPropertyName = "T8277")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<double?>>), TypeInfoPropertyName = "T8278")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<double?>>), TypeInfoPropertyName = "T8279")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<double?>>), TypeInfoPropertyName = "T8280")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<double?>, int>), TypeInfoPropertyName = "T8281")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, double?>>), TypeInfoPropertyName = "T8282")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, double?>>), TypeInfoPropertyName = "T8283")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, double?>>), TypeInfoPropertyName = "T8284")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, double?>>), TypeInfoPropertyName = "T8285")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, double?>, int>), TypeInfoPropertyName = "T8286")] +[JsonSerializable(typeof(Cn_class_C<double?>), TypeInfoPropertyName = "T8287")] +[JsonSerializable(typeof(Cn_record_class_C<double?>), TypeInfoPropertyName = "T8288")] +[JsonSerializable(typeof(Cn_struct_C<double?>), TypeInfoPropertyName = "T8289")] +[JsonSerializable(typeof(Cn_record_struct_C<double?>), TypeInfoPropertyName = "T8290")] +[JsonSerializable(typeof(Cn_generic_type_C<double?, int>), TypeInfoPropertyName = "T8291")] +[JsonSerializable(typeof(Cn_class_R<double?>), TypeInfoPropertyName = "T8292")] +[JsonSerializable(typeof(Cn_record_class_R<double?>), TypeInfoPropertyName = "T8293")] +[JsonSerializable(typeof(Cn_struct_R<double?>), TypeInfoPropertyName = "T8294")] +[JsonSerializable(typeof(Cn_record_struct_R<double?>), TypeInfoPropertyName = "T8295")] +[JsonSerializable(typeof(Cn_generic_type_R<double?, int>), TypeInfoPropertyName = "T8296")] +[JsonSerializable(typeof(Cn_class_P<PBP_double_N>), TypeInfoPropertyName = "T8297")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_double_N>), TypeInfoPropertyName = "T8298")] +[JsonSerializable(typeof(Cn_struct_P<PBP_double_N>), TypeInfoPropertyName = "T8299")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_double_N>), TypeInfoPropertyName = "T8300")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_double_N, int>), TypeInfoPropertyName = "T8301")] +[JsonSerializable(typeof(bool?), TypeInfoPropertyName = "T8302")] +[JsonSerializable(typeof(Cn_class_F<bool?>), TypeInfoPropertyName = "T8303")] +[JsonSerializable(typeof(Cn_record_class_F<bool?>), TypeInfoPropertyName = "T8304")] +[JsonSerializable(typeof(Cn_struct_F<bool?>), TypeInfoPropertyName = "T8305")] +[JsonSerializable(typeof(Cn_record_struct_F<bool?>), TypeInfoPropertyName = "T8306")] +[JsonSerializable(typeof(Cn_generic_type_F<bool?, int>), TypeInfoPropertyName = "T8307")] +[JsonSerializable(typeof(Cn_class_P<List<bool?>>), TypeInfoPropertyName = "T8308")] +[JsonSerializable(typeof(Cn_record_class_P<List<bool?>>), TypeInfoPropertyName = "T8309")] +[JsonSerializable(typeof(Cn_struct_P<List<bool?>>), TypeInfoPropertyName = "T8310")] +[JsonSerializable(typeof(Cn_record_struct_P<List<bool?>>), TypeInfoPropertyName = "T8311")] +[JsonSerializable(typeof(Cn_generic_type_P<List<bool?>, int>), TypeInfoPropertyName = "T8312")] +[JsonSerializable(typeof(Cn_class_P<bool?[]>), TypeInfoPropertyName = "T8313")] +[JsonSerializable(typeof(Cn_record_class_P<bool?[]>), TypeInfoPropertyName = "T8314")] +[JsonSerializable(typeof(Cn_struct_P<bool?[]>), TypeInfoPropertyName = "T8315")] +[JsonSerializable(typeof(Cn_record_struct_P<bool?[]>), TypeInfoPropertyName = "T8316")] +[JsonSerializable(typeof(Cn_generic_type_P<bool?[], int>), TypeInfoPropertyName = "T8317")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<bool?>>), TypeInfoPropertyName = "T8318")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<bool?>>), TypeInfoPropertyName = "T8319")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<bool?>>), TypeInfoPropertyName = "T8320")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<bool?>>), TypeInfoPropertyName = "T8321")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<bool?>, int>), TypeInfoPropertyName = "T8322")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<bool?>>), TypeInfoPropertyName = "T8323")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<bool?>>), TypeInfoPropertyName = "T8324")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<bool?>>), TypeInfoPropertyName = "T8325")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<bool?>>), TypeInfoPropertyName = "T8326")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<bool?>, int>), TypeInfoPropertyName = "T8327")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, bool?>>), TypeInfoPropertyName = "T8328")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, bool?>>), TypeInfoPropertyName = "T8329")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, bool?>>), TypeInfoPropertyName = "T8330")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, bool?>>), TypeInfoPropertyName = "T8331")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, bool?>, int>), TypeInfoPropertyName = "T8332")] +[JsonSerializable(typeof(Cn_class_C<bool?>), TypeInfoPropertyName = "T8333")] +[JsonSerializable(typeof(Cn_record_class_C<bool?>), TypeInfoPropertyName = "T8334")] +[JsonSerializable(typeof(Cn_struct_C<bool?>), TypeInfoPropertyName = "T8335")] +[JsonSerializable(typeof(Cn_record_struct_C<bool?>), TypeInfoPropertyName = "T8336")] +[JsonSerializable(typeof(Cn_generic_type_C<bool?, int>), TypeInfoPropertyName = "T8337")] +[JsonSerializable(typeof(Cn_class_R<bool?>), TypeInfoPropertyName = "T8338")] +[JsonSerializable(typeof(Cn_record_class_R<bool?>), TypeInfoPropertyName = "T8339")] +[JsonSerializable(typeof(Cn_struct_R<bool?>), TypeInfoPropertyName = "T8340")] +[JsonSerializable(typeof(Cn_record_struct_R<bool?>), TypeInfoPropertyName = "T8341")] +[JsonSerializable(typeof(Cn_generic_type_R<bool?, int>), TypeInfoPropertyName = "T8342")] +[JsonSerializable(typeof(Cn_class_P<PBP_bool_N>), TypeInfoPropertyName = "T8343")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_bool_N>), TypeInfoPropertyName = "T8344")] +[JsonSerializable(typeof(Cn_struct_P<PBP_bool_N>), TypeInfoPropertyName = "T8345")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_bool_N>), TypeInfoPropertyName = "T8346")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_bool_N, int>), TypeInfoPropertyName = "T8347")] +[JsonSerializable(typeof(char?), TypeInfoPropertyName = "T8348")] +[JsonSerializable(typeof(Cn_class_F<char?>), TypeInfoPropertyName = "T8349")] +[JsonSerializable(typeof(Cn_record_class_F<char?>), TypeInfoPropertyName = "T8350")] +[JsonSerializable(typeof(Cn_struct_F<char?>), TypeInfoPropertyName = "T8351")] +[JsonSerializable(typeof(Cn_record_struct_F<char?>), TypeInfoPropertyName = "T8352")] +[JsonSerializable(typeof(Cn_generic_type_F<char?, int>), TypeInfoPropertyName = "T8353")] +[JsonSerializable(typeof(Cn_class_P<List<char?>>), TypeInfoPropertyName = "T8354")] +[JsonSerializable(typeof(Cn_record_class_P<List<char?>>), TypeInfoPropertyName = "T8355")] +[JsonSerializable(typeof(Cn_struct_P<List<char?>>), TypeInfoPropertyName = "T8356")] +[JsonSerializable(typeof(Cn_record_struct_P<List<char?>>), TypeInfoPropertyName = "T8357")] +[JsonSerializable(typeof(Cn_generic_type_P<List<char?>, int>), TypeInfoPropertyName = "T8358")] +[JsonSerializable(typeof(Cn_class_P<char?[]>), TypeInfoPropertyName = "T8359")] +[JsonSerializable(typeof(Cn_record_class_P<char?[]>), TypeInfoPropertyName = "T8360")] +[JsonSerializable(typeof(Cn_struct_P<char?[]>), TypeInfoPropertyName = "T8361")] +[JsonSerializable(typeof(Cn_record_struct_P<char?[]>), TypeInfoPropertyName = "T8362")] +[JsonSerializable(typeof(Cn_generic_type_P<char?[], int>), TypeInfoPropertyName = "T8363")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<char?>>), TypeInfoPropertyName = "T8364")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<char?>>), TypeInfoPropertyName = "T8365")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<char?>>), TypeInfoPropertyName = "T8366")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<char?>>), TypeInfoPropertyName = "T8367")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<char?>, int>), TypeInfoPropertyName = "T8368")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<char?>>), TypeInfoPropertyName = "T8369")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<char?>>), TypeInfoPropertyName = "T8370")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<char?>>), TypeInfoPropertyName = "T8371")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<char?>>), TypeInfoPropertyName = "T8372")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<char?>, int>), TypeInfoPropertyName = "T8373")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, char?>>), TypeInfoPropertyName = "T8374")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, char?>>), TypeInfoPropertyName = "T8375")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, char?>>), TypeInfoPropertyName = "T8376")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, char?>>), TypeInfoPropertyName = "T8377")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, char?>, int>), TypeInfoPropertyName = "T8378")] +[JsonSerializable(typeof(Cn_class_C<char?>), TypeInfoPropertyName = "T8379")] +[JsonSerializable(typeof(Cn_record_class_C<char?>), TypeInfoPropertyName = "T8380")] +[JsonSerializable(typeof(Cn_struct_C<char?>), TypeInfoPropertyName = "T8381")] +[JsonSerializable(typeof(Cn_record_struct_C<char?>), TypeInfoPropertyName = "T8382")] +[JsonSerializable(typeof(Cn_generic_type_C<char?, int>), TypeInfoPropertyName = "T8383")] +[JsonSerializable(typeof(Cn_class_R<char?>), TypeInfoPropertyName = "T8384")] +[JsonSerializable(typeof(Cn_record_class_R<char?>), TypeInfoPropertyName = "T8385")] +[JsonSerializable(typeof(Cn_struct_R<char?>), TypeInfoPropertyName = "T8386")] +[JsonSerializable(typeof(Cn_record_struct_R<char?>), TypeInfoPropertyName = "T8387")] +[JsonSerializable(typeof(Cn_generic_type_R<char?, int>), TypeInfoPropertyName = "T8388")] +[JsonSerializable(typeof(Cn_class_P<PBP_char_N>), TypeInfoPropertyName = "T8389")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_char_N>), TypeInfoPropertyName = "T8390")] +[JsonSerializable(typeof(Cn_struct_P<PBP_char_N>), TypeInfoPropertyName = "T8391")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_char_N>), TypeInfoPropertyName = "T8392")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_char_N, int>), TypeInfoPropertyName = "T8393")] +[JsonSerializable(typeof(DateTime?), TypeInfoPropertyName = "T8394")] +[JsonSerializable(typeof(Cn_class_F<DateTime?>), TypeInfoPropertyName = "T8395")] +[JsonSerializable(typeof(Cn_record_class_F<DateTime?>), TypeInfoPropertyName = "T8396")] +[JsonSerializable(typeof(Cn_struct_F<DateTime?>), TypeInfoPropertyName = "T8397")] +[JsonSerializable(typeof(Cn_record_struct_F<DateTime?>), TypeInfoPropertyName = "T8398")] +[JsonSerializable(typeof(Cn_generic_type_F<DateTime?, int>), TypeInfoPropertyName = "T8399")] +[JsonSerializable(typeof(Cn_class_P<List<DateTime?>>), TypeInfoPropertyName = "T8400")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateTime?>>), TypeInfoPropertyName = "T8401")] +[JsonSerializable(typeof(Cn_struct_P<List<DateTime?>>), TypeInfoPropertyName = "T8402")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateTime?>>), TypeInfoPropertyName = "T8403")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateTime?>, int>), TypeInfoPropertyName = "T8404")] +[JsonSerializable(typeof(Cn_class_P<DateTime?[]>), TypeInfoPropertyName = "T8405")] +[JsonSerializable(typeof(Cn_record_class_P<DateTime?[]>), TypeInfoPropertyName = "T8406")] +[JsonSerializable(typeof(Cn_struct_P<DateTime?[]>), TypeInfoPropertyName = "T8407")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTime?[]>), TypeInfoPropertyName = "T8408")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTime?[], int>), TypeInfoPropertyName = "T8409")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateTime?>>), TypeInfoPropertyName = "T8410")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateTime?>>), TypeInfoPropertyName = "T8411")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateTime?>>), TypeInfoPropertyName = "T8412")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateTime?>>), TypeInfoPropertyName = "T8413")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateTime?>, int>), TypeInfoPropertyName = "T8414")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateTime?>>), TypeInfoPropertyName = "T8415")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateTime?>>), TypeInfoPropertyName = "T8416")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateTime?>>), TypeInfoPropertyName = "T8417")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateTime?>>), TypeInfoPropertyName = "T8418")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateTime?>, int>), TypeInfoPropertyName = "T8419")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateTime?>>), TypeInfoPropertyName = "T8420")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateTime?>>), TypeInfoPropertyName = "T8421")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateTime?>>), TypeInfoPropertyName = "T8422")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateTime?>>), TypeInfoPropertyName = "T8423")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateTime?>, int>), TypeInfoPropertyName = "T8424")] +[JsonSerializable(typeof(Cn_class_C<DateTime?>), TypeInfoPropertyName = "T8425")] +[JsonSerializable(typeof(Cn_record_class_C<DateTime?>), TypeInfoPropertyName = "T8426")] +[JsonSerializable(typeof(Cn_struct_C<DateTime?>), TypeInfoPropertyName = "T8427")] +[JsonSerializable(typeof(Cn_record_struct_C<DateTime?>), TypeInfoPropertyName = "T8428")] +[JsonSerializable(typeof(Cn_generic_type_C<DateTime?, int>), TypeInfoPropertyName = "T8429")] +[JsonSerializable(typeof(Cn_class_R<DateTime?>), TypeInfoPropertyName = "T8430")] +[JsonSerializable(typeof(Cn_record_class_R<DateTime?>), TypeInfoPropertyName = "T8431")] +[JsonSerializable(typeof(Cn_struct_R<DateTime?>), TypeInfoPropertyName = "T8432")] +[JsonSerializable(typeof(Cn_record_struct_R<DateTime?>), TypeInfoPropertyName = "T8433")] +[JsonSerializable(typeof(Cn_generic_type_R<DateTime?, int>), TypeInfoPropertyName = "T8434")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateTime_N>), TypeInfoPropertyName = "T8435")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateTime_N>), TypeInfoPropertyName = "T8436")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateTime_N>), TypeInfoPropertyName = "T8437")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateTime_N>), TypeInfoPropertyName = "T8438")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateTime_N, int>), TypeInfoPropertyName = "T8439")] +[JsonSerializable(typeof(DateTimeOffset?), TypeInfoPropertyName = "T8440")] +[JsonSerializable(typeof(Cn_class_F<DateTimeOffset?>), TypeInfoPropertyName = "T8441")] +[JsonSerializable(typeof(Cn_record_class_F<DateTimeOffset?>), TypeInfoPropertyName = "T8442")] +[JsonSerializable(typeof(Cn_struct_F<DateTimeOffset?>), TypeInfoPropertyName = "T8443")] +[JsonSerializable(typeof(Cn_record_struct_F<DateTimeOffset?>), TypeInfoPropertyName = "T8444")] +[JsonSerializable(typeof(Cn_generic_type_F<DateTimeOffset?, int>), TypeInfoPropertyName = "T8445")] +[JsonSerializable(typeof(Cn_class_P<List<DateTimeOffset?>>), TypeInfoPropertyName = "T8446")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateTimeOffset?>>), TypeInfoPropertyName = "T8447")] +[JsonSerializable(typeof(Cn_struct_P<List<DateTimeOffset?>>), TypeInfoPropertyName = "T8448")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateTimeOffset?>>), TypeInfoPropertyName = "T8449")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateTimeOffset?>, int>), TypeInfoPropertyName = "T8450")] +[JsonSerializable(typeof(Cn_class_P<DateTimeOffset?[]>), TypeInfoPropertyName = "T8451")] +[JsonSerializable(typeof(Cn_record_class_P<DateTimeOffset?[]>), TypeInfoPropertyName = "T8452")] +[JsonSerializable(typeof(Cn_struct_P<DateTimeOffset?[]>), TypeInfoPropertyName = "T8453")] +[JsonSerializable(typeof(Cn_record_struct_P<DateTimeOffset?[]>), TypeInfoPropertyName = "T8454")] +[JsonSerializable(typeof(Cn_generic_type_P<DateTimeOffset?[], int>), TypeInfoPropertyName = "T8455")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateTimeOffset?>>), TypeInfoPropertyName = "T8456")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateTimeOffset?>>), TypeInfoPropertyName = "T8457")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateTimeOffset?>>), TypeInfoPropertyName = "T8458")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateTimeOffset?>>), TypeInfoPropertyName = "T8459")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateTimeOffset?>, int>), TypeInfoPropertyName = "T8460")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateTimeOffset?>>), TypeInfoPropertyName = "T8461")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateTimeOffset?>>), TypeInfoPropertyName = "T8462")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateTimeOffset?>>), TypeInfoPropertyName = "T8463")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateTimeOffset?>>), TypeInfoPropertyName = "T8464")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateTimeOffset?>, int>), TypeInfoPropertyName = "T8465")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateTimeOffset?>>), TypeInfoPropertyName = "T8466")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateTimeOffset?>>), TypeInfoPropertyName = "T8467")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateTimeOffset?>>), TypeInfoPropertyName = "T8468")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateTimeOffset?>>), TypeInfoPropertyName = "T8469")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateTimeOffset?>, int>), TypeInfoPropertyName = "T8470")] +[JsonSerializable(typeof(Cn_class_C<DateTimeOffset?>), TypeInfoPropertyName = "T8471")] +[JsonSerializable(typeof(Cn_record_class_C<DateTimeOffset?>), TypeInfoPropertyName = "T8472")] +[JsonSerializable(typeof(Cn_struct_C<DateTimeOffset?>), TypeInfoPropertyName = "T8473")] +[JsonSerializable(typeof(Cn_record_struct_C<DateTimeOffset?>), TypeInfoPropertyName = "T8474")] +[JsonSerializable(typeof(Cn_generic_type_C<DateTimeOffset?, int>), TypeInfoPropertyName = "T8475")] +[JsonSerializable(typeof(Cn_class_R<DateTimeOffset?>), TypeInfoPropertyName = "T8476")] +[JsonSerializable(typeof(Cn_record_class_R<DateTimeOffset?>), TypeInfoPropertyName = "T8477")] +[JsonSerializable(typeof(Cn_struct_R<DateTimeOffset?>), TypeInfoPropertyName = "T8478")] +[JsonSerializable(typeof(Cn_record_struct_R<DateTimeOffset?>), TypeInfoPropertyName = "T8479")] +[JsonSerializable(typeof(Cn_generic_type_R<DateTimeOffset?, int>), TypeInfoPropertyName = "T8480")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateTimeOffset_N>), TypeInfoPropertyName = "T8481")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateTimeOffset_N>), TypeInfoPropertyName = "T8482")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateTimeOffset_N>), TypeInfoPropertyName = "T8483")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateTimeOffset_N>), TypeInfoPropertyName = "T8484")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateTimeOffset_N, int>), TypeInfoPropertyName = "T8485")] +[JsonSerializable(typeof(DateOnly?), TypeInfoPropertyName = "T8486")] +[JsonSerializable(typeof(Cn_class_F<DateOnly?>), TypeInfoPropertyName = "T8487")] +[JsonSerializable(typeof(Cn_record_class_F<DateOnly?>), TypeInfoPropertyName = "T8488")] +[JsonSerializable(typeof(Cn_struct_F<DateOnly?>), TypeInfoPropertyName = "T8489")] +[JsonSerializable(typeof(Cn_record_struct_F<DateOnly?>), TypeInfoPropertyName = "T8490")] +[JsonSerializable(typeof(Cn_generic_type_F<DateOnly?, int>), TypeInfoPropertyName = "T8491")] +[JsonSerializable(typeof(Cn_class_P<List<DateOnly?>>), TypeInfoPropertyName = "T8492")] +[JsonSerializable(typeof(Cn_record_class_P<List<DateOnly?>>), TypeInfoPropertyName = "T8493")] +[JsonSerializable(typeof(Cn_struct_P<List<DateOnly?>>), TypeInfoPropertyName = "T8494")] +[JsonSerializable(typeof(Cn_record_struct_P<List<DateOnly?>>), TypeInfoPropertyName = "T8495")] +[JsonSerializable(typeof(Cn_generic_type_P<List<DateOnly?>, int>), TypeInfoPropertyName = "T8496")] +[JsonSerializable(typeof(Cn_class_P<DateOnly?[]>), TypeInfoPropertyName = "T8497")] +[JsonSerializable(typeof(Cn_record_class_P<DateOnly?[]>), TypeInfoPropertyName = "T8498")] +[JsonSerializable(typeof(Cn_struct_P<DateOnly?[]>), TypeInfoPropertyName = "T8499")] +[JsonSerializable(typeof(Cn_record_struct_P<DateOnly?[]>), TypeInfoPropertyName = "T8500")] +[JsonSerializable(typeof(Cn_generic_type_P<DateOnly?[], int>), TypeInfoPropertyName = "T8501")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<DateOnly?>>), TypeInfoPropertyName = "T8502")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<DateOnly?>>), TypeInfoPropertyName = "T8503")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<DateOnly?>>), TypeInfoPropertyName = "T8504")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<DateOnly?>>), TypeInfoPropertyName = "T8505")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<DateOnly?>, int>), TypeInfoPropertyName = "T8506")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<DateOnly?>>), TypeInfoPropertyName = "T8507")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<DateOnly?>>), TypeInfoPropertyName = "T8508")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<DateOnly?>>), TypeInfoPropertyName = "T8509")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<DateOnly?>>), TypeInfoPropertyName = "T8510")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<DateOnly?>, int>), TypeInfoPropertyName = "T8511")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, DateOnly?>>), TypeInfoPropertyName = "T8512")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, DateOnly?>>), TypeInfoPropertyName = "T8513")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, DateOnly?>>), TypeInfoPropertyName = "T8514")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, DateOnly?>>), TypeInfoPropertyName = "T8515")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, DateOnly?>, int>), TypeInfoPropertyName = "T8516")] +[JsonSerializable(typeof(Cn_class_C<DateOnly?>), TypeInfoPropertyName = "T8517")] +[JsonSerializable(typeof(Cn_record_class_C<DateOnly?>), TypeInfoPropertyName = "T8518")] +[JsonSerializable(typeof(Cn_struct_C<DateOnly?>), TypeInfoPropertyName = "T8519")] +[JsonSerializable(typeof(Cn_record_struct_C<DateOnly?>), TypeInfoPropertyName = "T8520")] +[JsonSerializable(typeof(Cn_generic_type_C<DateOnly?, int>), TypeInfoPropertyName = "T8521")] +[JsonSerializable(typeof(Cn_class_R<DateOnly?>), TypeInfoPropertyName = "T8522")] +[JsonSerializable(typeof(Cn_record_class_R<DateOnly?>), TypeInfoPropertyName = "T8523")] +[JsonSerializable(typeof(Cn_struct_R<DateOnly?>), TypeInfoPropertyName = "T8524")] +[JsonSerializable(typeof(Cn_record_struct_R<DateOnly?>), TypeInfoPropertyName = "T8525")] +[JsonSerializable(typeof(Cn_generic_type_R<DateOnly?, int>), TypeInfoPropertyName = "T8526")] +[JsonSerializable(typeof(Cn_class_P<PBP_DateOnly_N>), TypeInfoPropertyName = "T8527")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_DateOnly_N>), TypeInfoPropertyName = "T8528")] +[JsonSerializable(typeof(Cn_struct_P<PBP_DateOnly_N>), TypeInfoPropertyName = "T8529")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_DateOnly_N>), TypeInfoPropertyName = "T8530")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_DateOnly_N, int>), TypeInfoPropertyName = "T8531")] +[JsonSerializable(typeof(TimeOnly?), TypeInfoPropertyName = "T8532")] +[JsonSerializable(typeof(Cn_class_F<TimeOnly?>), TypeInfoPropertyName = "T8533")] +[JsonSerializable(typeof(Cn_record_class_F<TimeOnly?>), TypeInfoPropertyName = "T8534")] +[JsonSerializable(typeof(Cn_struct_F<TimeOnly?>), TypeInfoPropertyName = "T8535")] +[JsonSerializable(typeof(Cn_record_struct_F<TimeOnly?>), TypeInfoPropertyName = "T8536")] +[JsonSerializable(typeof(Cn_generic_type_F<TimeOnly?, int>), TypeInfoPropertyName = "T8537")] +[JsonSerializable(typeof(Cn_class_P<List<TimeOnly?>>), TypeInfoPropertyName = "T8538")] +[JsonSerializable(typeof(Cn_record_class_P<List<TimeOnly?>>), TypeInfoPropertyName = "T8539")] +[JsonSerializable(typeof(Cn_struct_P<List<TimeOnly?>>), TypeInfoPropertyName = "T8540")] +[JsonSerializable(typeof(Cn_record_struct_P<List<TimeOnly?>>), TypeInfoPropertyName = "T8541")] +[JsonSerializable(typeof(Cn_generic_type_P<List<TimeOnly?>, int>), TypeInfoPropertyName = "T8542")] +[JsonSerializable(typeof(Cn_class_P<TimeOnly?[]>), TypeInfoPropertyName = "T8543")] +[JsonSerializable(typeof(Cn_record_class_P<TimeOnly?[]>), TypeInfoPropertyName = "T8544")] +[JsonSerializable(typeof(Cn_struct_P<TimeOnly?[]>), TypeInfoPropertyName = "T8545")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeOnly?[]>), TypeInfoPropertyName = "T8546")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeOnly?[], int>), TypeInfoPropertyName = "T8547")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<TimeOnly?>>), TypeInfoPropertyName = "T8548")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<TimeOnly?>>), TypeInfoPropertyName = "T8549")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<TimeOnly?>>), TypeInfoPropertyName = "T8550")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<TimeOnly?>>), TypeInfoPropertyName = "T8551")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<TimeOnly?>, int>), TypeInfoPropertyName = "T8552")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<TimeOnly?>>), TypeInfoPropertyName = "T8553")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<TimeOnly?>>), TypeInfoPropertyName = "T8554")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<TimeOnly?>>), TypeInfoPropertyName = "T8555")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<TimeOnly?>>), TypeInfoPropertyName = "T8556")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<TimeOnly?>, int>), TypeInfoPropertyName = "T8557")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, TimeOnly?>>), TypeInfoPropertyName = "T8558")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, TimeOnly?>>), TypeInfoPropertyName = "T8559")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, TimeOnly?>>), TypeInfoPropertyName = "T8560")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, TimeOnly?>>), TypeInfoPropertyName = "T8561")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, TimeOnly?>, int>), TypeInfoPropertyName = "T8562")] +[JsonSerializable(typeof(Cn_class_C<TimeOnly?>), TypeInfoPropertyName = "T8563")] +[JsonSerializable(typeof(Cn_record_class_C<TimeOnly?>), TypeInfoPropertyName = "T8564")] +[JsonSerializable(typeof(Cn_struct_C<TimeOnly?>), TypeInfoPropertyName = "T8565")] +[JsonSerializable(typeof(Cn_record_struct_C<TimeOnly?>), TypeInfoPropertyName = "T8566")] +[JsonSerializable(typeof(Cn_generic_type_C<TimeOnly?, int>), TypeInfoPropertyName = "T8567")] +[JsonSerializable(typeof(Cn_class_R<TimeOnly?>), TypeInfoPropertyName = "T8568")] +[JsonSerializable(typeof(Cn_record_class_R<TimeOnly?>), TypeInfoPropertyName = "T8569")] +[JsonSerializable(typeof(Cn_struct_R<TimeOnly?>), TypeInfoPropertyName = "T8570")] +[JsonSerializable(typeof(Cn_record_struct_R<TimeOnly?>), TypeInfoPropertyName = "T8571")] +[JsonSerializable(typeof(Cn_generic_type_R<TimeOnly?, int>), TypeInfoPropertyName = "T8572")] +[JsonSerializable(typeof(Cn_class_P<PBP_TimeOnly_N>), TypeInfoPropertyName = "T8573")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_TimeOnly_N>), TypeInfoPropertyName = "T8574")] +[JsonSerializable(typeof(Cn_struct_P<PBP_TimeOnly_N>), TypeInfoPropertyName = "T8575")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_TimeOnly_N>), TypeInfoPropertyName = "T8576")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_TimeOnly_N, int>), TypeInfoPropertyName = "T8577")] +[JsonSerializable(typeof(TimeSpan?), TypeInfoPropertyName = "T8578")] +[JsonSerializable(typeof(Cn_class_F<TimeSpan?>), TypeInfoPropertyName = "T8579")] +[JsonSerializable(typeof(Cn_record_class_F<TimeSpan?>), TypeInfoPropertyName = "T8580")] +[JsonSerializable(typeof(Cn_struct_F<TimeSpan?>), TypeInfoPropertyName = "T8581")] +[JsonSerializable(typeof(Cn_record_struct_F<TimeSpan?>), TypeInfoPropertyName = "T8582")] +[JsonSerializable(typeof(Cn_generic_type_F<TimeSpan?, int>), TypeInfoPropertyName = "T8583")] +[JsonSerializable(typeof(Cn_class_P<List<TimeSpan?>>), TypeInfoPropertyName = "T8584")] +[JsonSerializable(typeof(Cn_record_class_P<List<TimeSpan?>>), TypeInfoPropertyName = "T8585")] +[JsonSerializable(typeof(Cn_struct_P<List<TimeSpan?>>), TypeInfoPropertyName = "T8586")] +[JsonSerializable(typeof(Cn_record_struct_P<List<TimeSpan?>>), TypeInfoPropertyName = "T8587")] +[JsonSerializable(typeof(Cn_generic_type_P<List<TimeSpan?>, int>), TypeInfoPropertyName = "T8588")] +[JsonSerializable(typeof(Cn_class_P<TimeSpan?[]>), TypeInfoPropertyName = "T8589")] +[JsonSerializable(typeof(Cn_record_class_P<TimeSpan?[]>), TypeInfoPropertyName = "T8590")] +[JsonSerializable(typeof(Cn_struct_P<TimeSpan?[]>), TypeInfoPropertyName = "T8591")] +[JsonSerializable(typeof(Cn_record_struct_P<TimeSpan?[]>), TypeInfoPropertyName = "T8592")] +[JsonSerializable(typeof(Cn_generic_type_P<TimeSpan?[], int>), TypeInfoPropertyName = "T8593")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<TimeSpan?>>), TypeInfoPropertyName = "T8594")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<TimeSpan?>>), TypeInfoPropertyName = "T8595")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<TimeSpan?>>), TypeInfoPropertyName = "T8596")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<TimeSpan?>>), TypeInfoPropertyName = "T8597")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<TimeSpan?>, int>), TypeInfoPropertyName = "T8598")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<TimeSpan?>>), TypeInfoPropertyName = "T8599")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<TimeSpan?>>), TypeInfoPropertyName = "T8600")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<TimeSpan?>>), TypeInfoPropertyName = "T8601")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<TimeSpan?>>), TypeInfoPropertyName = "T8602")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<TimeSpan?>, int>), TypeInfoPropertyName = "T8603")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, TimeSpan?>>), TypeInfoPropertyName = "T8604")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, TimeSpan?>>), TypeInfoPropertyName = "T8605")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, TimeSpan?>>), TypeInfoPropertyName = "T8606")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, TimeSpan?>>), TypeInfoPropertyName = "T8607")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, TimeSpan?>, int>), TypeInfoPropertyName = "T8608")] +[JsonSerializable(typeof(Cn_class_C<TimeSpan?>), TypeInfoPropertyName = "T8609")] +[JsonSerializable(typeof(Cn_record_class_C<TimeSpan?>), TypeInfoPropertyName = "T8610")] +[JsonSerializable(typeof(Cn_struct_C<TimeSpan?>), TypeInfoPropertyName = "T8611")] +[JsonSerializable(typeof(Cn_record_struct_C<TimeSpan?>), TypeInfoPropertyName = "T8612")] +[JsonSerializable(typeof(Cn_generic_type_C<TimeSpan?, int>), TypeInfoPropertyName = "T8613")] +[JsonSerializable(typeof(Cn_class_R<TimeSpan?>), TypeInfoPropertyName = "T8614")] +[JsonSerializable(typeof(Cn_record_class_R<TimeSpan?>), TypeInfoPropertyName = "T8615")] +[JsonSerializable(typeof(Cn_struct_R<TimeSpan?>), TypeInfoPropertyName = "T8616")] +[JsonSerializable(typeof(Cn_record_struct_R<TimeSpan?>), TypeInfoPropertyName = "T8617")] +[JsonSerializable(typeof(Cn_generic_type_R<TimeSpan?, int>), TypeInfoPropertyName = "T8618")] +[JsonSerializable(typeof(Cn_class_P<PBP_TimeSpan_N>), TypeInfoPropertyName = "T8619")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_TimeSpan_N>), TypeInfoPropertyName = "T8620")] +[JsonSerializable(typeof(Cn_struct_P<PBP_TimeSpan_N>), TypeInfoPropertyName = "T8621")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_TimeSpan_N>), TypeInfoPropertyName = "T8622")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_TimeSpan_N, int>), TypeInfoPropertyName = "T8623")] +[JsonSerializable(typeof(PEnum?), TypeInfoPropertyName = "T8624")] +[JsonSerializable(typeof(Cn_class_F<PEnum?>), TypeInfoPropertyName = "T8625")] +[JsonSerializable(typeof(Cn_record_class_F<PEnum?>), TypeInfoPropertyName = "T8626")] +[JsonSerializable(typeof(Cn_struct_F<PEnum?>), TypeInfoPropertyName = "T8627")] +[JsonSerializable(typeof(Cn_record_struct_F<PEnum?>), TypeInfoPropertyName = "T8628")] +[JsonSerializable(typeof(Cn_generic_type_F<PEnum?, int>), TypeInfoPropertyName = "T8629")] +[JsonSerializable(typeof(Cn_class_P<List<PEnum?>>), TypeInfoPropertyName = "T8630")] +[JsonSerializable(typeof(Cn_record_class_P<List<PEnum?>>), TypeInfoPropertyName = "T8631")] +[JsonSerializable(typeof(Cn_struct_P<List<PEnum?>>), TypeInfoPropertyName = "T8632")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PEnum?>>), TypeInfoPropertyName = "T8633")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PEnum?>, int>), TypeInfoPropertyName = "T8634")] +[JsonSerializable(typeof(Cn_class_P<PEnum?[]>), TypeInfoPropertyName = "T8635")] +[JsonSerializable(typeof(Cn_record_class_P<PEnum?[]>), TypeInfoPropertyName = "T8636")] +[JsonSerializable(typeof(Cn_struct_P<PEnum?[]>), TypeInfoPropertyName = "T8637")] +[JsonSerializable(typeof(Cn_record_struct_P<PEnum?[]>), TypeInfoPropertyName = "T8638")] +[JsonSerializable(typeof(Cn_generic_type_P<PEnum?[], int>), TypeInfoPropertyName = "T8639")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PEnum?>>), TypeInfoPropertyName = "T8640")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PEnum?>>), TypeInfoPropertyName = "T8641")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PEnum?>>), TypeInfoPropertyName = "T8642")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PEnum?>>), TypeInfoPropertyName = "T8643")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PEnum?>, int>), TypeInfoPropertyName = "T8644")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PEnum?>>), TypeInfoPropertyName = "T8645")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PEnum?>>), TypeInfoPropertyName = "T8646")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PEnum?>>), TypeInfoPropertyName = "T8647")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PEnum?>>), TypeInfoPropertyName = "T8648")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PEnum?>, int>), TypeInfoPropertyName = "T8649")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PEnum?>>), TypeInfoPropertyName = "T8650")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PEnum?>>), TypeInfoPropertyName = "T8651")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PEnum?>>), TypeInfoPropertyName = "T8652")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PEnum?>>), TypeInfoPropertyName = "T8653")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PEnum?>, int>), TypeInfoPropertyName = "T8654")] +[JsonSerializable(typeof(Cn_class_C<PEnum?>), TypeInfoPropertyName = "T8655")] +[JsonSerializable(typeof(Cn_record_class_C<PEnum?>), TypeInfoPropertyName = "T8656")] +[JsonSerializable(typeof(Cn_struct_C<PEnum?>), TypeInfoPropertyName = "T8657")] +[JsonSerializable(typeof(Cn_record_struct_C<PEnum?>), TypeInfoPropertyName = "T8658")] +[JsonSerializable(typeof(Cn_generic_type_C<PEnum?, int>), TypeInfoPropertyName = "T8659")] +[JsonSerializable(typeof(Cn_class_R<PEnum?>), TypeInfoPropertyName = "T8660")] +[JsonSerializable(typeof(Cn_record_class_R<PEnum?>), TypeInfoPropertyName = "T8661")] +[JsonSerializable(typeof(Cn_struct_R<PEnum?>), TypeInfoPropertyName = "T8662")] +[JsonSerializable(typeof(Cn_record_struct_R<PEnum?>), TypeInfoPropertyName = "T8663")] +[JsonSerializable(typeof(Cn_generic_type_R<PEnum?, int>), TypeInfoPropertyName = "T8664")] +[JsonSerializable(typeof(Cn_class_P<PBP_enum_N>), TypeInfoPropertyName = "T8665")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_enum_N>), TypeInfoPropertyName = "T8666")] +[JsonSerializable(typeof(Cn_struct_P<PBP_enum_N>), TypeInfoPropertyName = "T8667")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_enum_N>), TypeInfoPropertyName = "T8668")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_enum_N, int>), TypeInfoPropertyName = "T8669")] +[JsonSerializable(typeof(PFlags?), TypeInfoPropertyName = "T8670")] +[JsonSerializable(typeof(Cn_class_F<PFlags?>), TypeInfoPropertyName = "T8671")] +[JsonSerializable(typeof(Cn_record_class_F<PFlags?>), TypeInfoPropertyName = "T8672")] +[JsonSerializable(typeof(Cn_struct_F<PFlags?>), TypeInfoPropertyName = "T8673")] +[JsonSerializable(typeof(Cn_record_struct_F<PFlags?>), TypeInfoPropertyName = "T8674")] +[JsonSerializable(typeof(Cn_generic_type_F<PFlags?, int>), TypeInfoPropertyName = "T8675")] +[JsonSerializable(typeof(Cn_class_P<List<PFlags?>>), TypeInfoPropertyName = "T8676")] +[JsonSerializable(typeof(Cn_record_class_P<List<PFlags?>>), TypeInfoPropertyName = "T8677")] +[JsonSerializable(typeof(Cn_struct_P<List<PFlags?>>), TypeInfoPropertyName = "T8678")] +[JsonSerializable(typeof(Cn_record_struct_P<List<PFlags?>>), TypeInfoPropertyName = "T8679")] +[JsonSerializable(typeof(Cn_generic_type_P<List<PFlags?>, int>), TypeInfoPropertyName = "T8680")] +[JsonSerializable(typeof(Cn_class_P<PFlags?[]>), TypeInfoPropertyName = "T8681")] +[JsonSerializable(typeof(Cn_record_class_P<PFlags?[]>), TypeInfoPropertyName = "T8682")] +[JsonSerializable(typeof(Cn_struct_P<PFlags?[]>), TypeInfoPropertyName = "T8683")] +[JsonSerializable(typeof(Cn_record_struct_P<PFlags?[]>), TypeInfoPropertyName = "T8684")] +[JsonSerializable(typeof(Cn_generic_type_P<PFlags?[], int>), TypeInfoPropertyName = "T8685")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<PFlags?>>), TypeInfoPropertyName = "T8686")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<PFlags?>>), TypeInfoPropertyName = "T8687")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<PFlags?>>), TypeInfoPropertyName = "T8688")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<PFlags?>>), TypeInfoPropertyName = "T8689")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<PFlags?>, int>), TypeInfoPropertyName = "T8690")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<PFlags?>>), TypeInfoPropertyName = "T8691")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<PFlags?>>), TypeInfoPropertyName = "T8692")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<PFlags?>>), TypeInfoPropertyName = "T8693")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<PFlags?>>), TypeInfoPropertyName = "T8694")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<PFlags?>, int>), TypeInfoPropertyName = "T8695")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, PFlags?>>), TypeInfoPropertyName = "T8696")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, PFlags?>>), TypeInfoPropertyName = "T8697")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, PFlags?>>), TypeInfoPropertyName = "T8698")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, PFlags?>>), TypeInfoPropertyName = "T8699")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, PFlags?>, int>), TypeInfoPropertyName = "T8700")] +[JsonSerializable(typeof(Cn_class_C<PFlags?>), TypeInfoPropertyName = "T8701")] +[JsonSerializable(typeof(Cn_record_class_C<PFlags?>), TypeInfoPropertyName = "T8702")] +[JsonSerializable(typeof(Cn_struct_C<PFlags?>), TypeInfoPropertyName = "T8703")] +[JsonSerializable(typeof(Cn_record_struct_C<PFlags?>), TypeInfoPropertyName = "T8704")] +[JsonSerializable(typeof(Cn_generic_type_C<PFlags?, int>), TypeInfoPropertyName = "T8705")] +[JsonSerializable(typeof(Cn_class_R<PFlags?>), TypeInfoPropertyName = "T8706")] +[JsonSerializable(typeof(Cn_record_class_R<PFlags?>), TypeInfoPropertyName = "T8707")] +[JsonSerializable(typeof(Cn_struct_R<PFlags?>), TypeInfoPropertyName = "T8708")] +[JsonSerializable(typeof(Cn_record_struct_R<PFlags?>), TypeInfoPropertyName = "T8709")] +[JsonSerializable(typeof(Cn_generic_type_R<PFlags?, int>), TypeInfoPropertyName = "T8710")] +[JsonSerializable(typeof(Cn_class_P<PBP_flags_enum_N>), TypeInfoPropertyName = "T8711")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_flags_enum_N>), TypeInfoPropertyName = "T8712")] +[JsonSerializable(typeof(Cn_struct_P<PBP_flags_enum_N>), TypeInfoPropertyName = "T8713")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_flags_enum_N>), TypeInfoPropertyName = "T8714")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_flags_enum_N, int>), TypeInfoPropertyName = "T8715")] +[JsonSerializable(typeof(global::NodaTime.Instant?), TypeInfoPropertyName = "T8716")] +[JsonSerializable(typeof(Cn_class_F<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8717")] +[JsonSerializable(typeof(Cn_record_class_F<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8718")] +[JsonSerializable(typeof(Cn_struct_F<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8719")] +[JsonSerializable(typeof(Cn_record_struct_F<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8720")] +[JsonSerializable(typeof(Cn_generic_type_F<global::NodaTime.Instant?, int>), TypeInfoPropertyName = "T8721")] +[JsonSerializable(typeof(Cn_class_P<List<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8722")] +[JsonSerializable(typeof(Cn_record_class_P<List<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8723")] +[JsonSerializable(typeof(Cn_struct_P<List<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8724")] +[JsonSerializable(typeof(Cn_record_struct_P<List<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8725")] +[JsonSerializable(typeof(Cn_generic_type_P<List<global::NodaTime.Instant?>, int>), TypeInfoPropertyName = "T8726")] +[JsonSerializable(typeof(Cn_class_P<global::NodaTime.Instant?[]>), TypeInfoPropertyName = "T8727")] +[JsonSerializable(typeof(Cn_record_class_P<global::NodaTime.Instant?[]>), TypeInfoPropertyName = "T8728")] +[JsonSerializable(typeof(Cn_struct_P<global::NodaTime.Instant?[]>), TypeInfoPropertyName = "T8729")] +[JsonSerializable(typeof(Cn_record_struct_P<global::NodaTime.Instant?[]>), TypeInfoPropertyName = "T8730")] +[JsonSerializable(typeof(Cn_generic_type_P<global::NodaTime.Instant?[], int>), TypeInfoPropertyName = "T8731")] +[JsonSerializable(typeof(Cn_class_P<ImmutableArray<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8732")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8733")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8734")] +[JsonSerializable(typeof(Cn_record_struct_P<ImmutableArray<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8735")] +[JsonSerializable(typeof(Cn_generic_type_P<ImmutableArray<global::NodaTime.Instant?>, int>), TypeInfoPropertyName = "T8736")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8737")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8738")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8739")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8740")] +[JsonSerializable(typeof(Cn_generic_type_P<IReadOnlyList<global::NodaTime.Instant?>, int>), TypeInfoPropertyName = "T8741")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8742")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<string, global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8743")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8744")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, global::NodaTime.Instant?>>), TypeInfoPropertyName = "T8745")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, global::NodaTime.Instant?>, int>), TypeInfoPropertyName = "T8746")] +[JsonSerializable(typeof(Cn_class_C<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8747")] +[JsonSerializable(typeof(Cn_record_class_C<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8748")] +[JsonSerializable(typeof(Cn_struct_C<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8749")] +[JsonSerializable(typeof(Cn_record_struct_C<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8750")] +[JsonSerializable(typeof(Cn_generic_type_C<global::NodaTime.Instant?, int>), TypeInfoPropertyName = "T8751")] +[JsonSerializable(typeof(Cn_class_R<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8752")] +[JsonSerializable(typeof(Cn_record_class_R<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8753")] +[JsonSerializable(typeof(Cn_struct_R<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8754")] +[JsonSerializable(typeof(Cn_record_struct_R<global::NodaTime.Instant?>), TypeInfoPropertyName = "T8755")] +[JsonSerializable(typeof(Cn_generic_type_R<global::NodaTime.Instant?, int>), TypeInfoPropertyName = "T8756")] +[JsonSerializable(typeof(Cn_class_P<PBP_NodaTime_Instant_N>), TypeInfoPropertyName = "T8757")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_NodaTime_Instant_N>), TypeInfoPropertyName = "T8758")] +[JsonSerializable(typeof(Cn_struct_P<PBP_NodaTime_Instant_N>), TypeInfoPropertyName = "T8759")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_NodaTime_Instant_N>), TypeInfoPropertyName = "T8760")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_NodaTime_Instant_N, int>), TypeInfoPropertyName = "T8761")] +[JsonSerializable(typeof(Cn_class_P<PBP_string_>), TypeInfoPropertyName = "T8762")] +[JsonSerializable(typeof(Cn_record_class_P<PBP_string_>), TypeInfoPropertyName = "T8763")] +[JsonSerializable(typeof(Cn_struct_P<PBP_string_>), TypeInfoPropertyName = "T8764")] +[JsonSerializable(typeof(Cn_record_struct_P<PBP_string_>), TypeInfoPropertyName = "T8765")] +[JsonSerializable(typeof(Cn_generic_type_P<PBP_string_, int>), TypeInfoPropertyName = "T8766")] +[JsonSerializable(typeof(Cn_class_P<List<Option<V_validated_struct_TimeSpan>>>), TypeInfoPropertyName = "T8767")] +[JsonSerializable(typeof(Cn_record_struct_F<Option<V_record_struct_DateTime>>), TypeInfoPropertyName = "T8768")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_sealed_record_class_bool>[]>), TypeInfoPropertyName = "T8769")] +[JsonSerializable(typeof(Cn_struct_P<Result<V_record_struct_DateTimeOffset, ShapeFault>>), TypeInfoPropertyName = "T8770")] +[JsonSerializable(typeof(Cn_record_class_P<global::Probes.Json.L06PB_PSet_RTE>), TypeInfoPropertyName = "T8771")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Result<V_sealed_record_class_string, ShapeFault>>, int>), TypeInfoPropertyName = "T8772")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, Result<V_validated_class_char, ShapeFault>>>), TypeInfoPropertyName = "T8773")] +[JsonSerializable(typeof(Result<V_validated_struct_byte, ShapeFault>), TypeInfoPropertyName = "T8774")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Result<V_record_struct_double, ShapeFault>>>), TypeInfoPropertyName = "T8775")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<Option<V_sealed_record_class_enum>>>), TypeInfoPropertyName = "T8776")] +[JsonSerializable(typeof(Cn_record_class_C<Result<V_record_struct_DateOnly, ShapeFault>>), TypeInfoPropertyName = "T8777")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<Result<V_record_struct_flags_enum, ShapeFault>>>), TypeInfoPropertyName = "T8778")] +[JsonSerializable(typeof(Cn_class_P<Option<PSet>[]>), TypeInfoPropertyName = "T8779")] +[JsonSerializable(typeof(Cn_struct_R<Option<V_sealed_record_class_int>>), TypeInfoPropertyName = "T8780")] +[JsonSerializable(typeof(Cn_record_class_P<Dictionary<Result<PSet>, int>>), TypeInfoPropertyName = "T8781")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_record_struct_decimal, ShapeFault>?>), TypeInfoPropertyName = "T8782")] +[JsonSerializable(typeof(Cn_generic_type_P<global::Probes.Json.L06PB_V_record_struct_long_RTE, int>), TypeInfoPropertyName = "T8783")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<Result<V_validated_struct_ulong, ShapeFault>>>), TypeInfoPropertyName = "T8784")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Result<V_validated_class_Uri>>>), TypeInfoPropertyName = "T8785")] +[JsonSerializable(typeof(Cn_struct_P<List<Result<V_sealed_record_class_DateOnly, ShapeFault>>>), TypeInfoPropertyName = "T8786")] +[JsonSerializable(typeof(Cn_class_P<Patch<Result<V_sealed_record_class_NodaTime_Instant>>>), TypeInfoPropertyName = "T8787")] +[JsonSerializable(typeof(Cn_record_class_P<IReadOnlyList<Result<V_validated_struct_flags_enum>>>), TypeInfoPropertyName = "T8788")] +[JsonSerializable(typeof(Result<V_record_struct_ulong>), TypeInfoPropertyName = "T8789")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, Result<V_validated_class_double>>>), TypeInfoPropertyName = "T8790")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Result<V_validated_class_int, ShapeFault>>>), TypeInfoPropertyName = "T8791")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_validated_class_byte, ShapeFault>>), TypeInfoPropertyName = "T8792")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_record_struct_Guid, ShapeFault>[]>), TypeInfoPropertyName = "T8793")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Result<V_sealed_record_class_char>>>), TypeInfoPropertyName = "T8794")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_validated_struct_double, ShapeFault>[]>), TypeInfoPropertyName = "T8795")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<Option<V_validated_struct_long>>>), TypeInfoPropertyName = "T8796")] +[JsonSerializable(typeof(Cn_struct_C<Result<V_validated_struct_enum, ShapeFault>>), TypeInfoPropertyName = "T8797")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<string, Result<V_validated_struct_decimal>>>), TypeInfoPropertyName = "T8798")] +[JsonSerializable(typeof(Cn_struct_C<Option<V_validated_struct_string>>), TypeInfoPropertyName = "T8799")] +[JsonSerializable(typeof(Cn_generic_type_P<Result<V_validated_struct_flags_enum, ShapeFault>?, int>), TypeInfoPropertyName = "T8800")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Result<V_validated_struct_int, ShapeFault>>>), TypeInfoPropertyName = "T8801")] +[JsonSerializable(typeof(Result<V_sealed_record_class_TimeOnly, ShapeFault>), TypeInfoPropertyName = "T8802")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, Option<PSet>>>), TypeInfoPropertyName = "T8803")] +[JsonSerializable(typeof(Result<V_validated_class_Guid, ShapeFault>), TypeInfoPropertyName = "T8804")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_validated_struct_DateTimeOffset, ShapeFault>>), TypeInfoPropertyName = "T8805")] +[JsonSerializable(typeof(Cn_class_P<Dictionary<Result<V_validated_class_NodaTime_Instant, ShapeFault>, int>>), TypeInfoPropertyName = "T8806")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_validated_class_long, ShapeFault>>), TypeInfoPropertyName = "T8807")] +[JsonSerializable(typeof(Cn_class_R<Result<V_validated_class_flags_enum, ShapeFault>>), TypeInfoPropertyName = "T8808")] +[JsonSerializable(typeof(Result<V_validated_class_string, ShapeFault>), TypeInfoPropertyName = "T8809")] +[JsonSerializable(typeof(Cn_record_class_P<Patch<Result<V_validated_class_own_record_class, ShapeFault>>>), TypeInfoPropertyName = "T8810")] +[JsonSerializable(typeof(Cn_class_P<global::Probes.Json.L06PB_V_validated_struct_TimeOnly_RTE>), TypeInfoPropertyName = "T8811")] +[JsonSerializable(typeof(Cn_generic_type_P<global::Probes.Json.L06PB_V_validated_class_char_RTE, int>), TypeInfoPropertyName = "T8812")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Option<V_sealed_record_class_byte>>>), TypeInfoPropertyName = "T8813")] +[JsonSerializable(typeof(Cn_record_struct_P<IReadOnlyList<Result<V_record_struct_DateOnly, ShapeFault>>>), TypeInfoPropertyName = "T8814")] +[JsonSerializable(typeof(Cn_class_P<global::Probes.Json.L06PB_V_sealed_record_class_int_RE>), TypeInfoPropertyName = "T8815")] +[JsonSerializable(typeof(Cn_class_P<List<Result<V_record_struct_bool, ShapeFault>>>), TypeInfoPropertyName = "T8816")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<Option<V_sealed_record_class_Guid>, int>>), TypeInfoPropertyName = "T8817")] +[JsonSerializable(typeof(Cn_struct_R<Result<V_validated_class_ulong, ShapeFault>>), TypeInfoPropertyName = "T8818")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_validated_class_own_record_class>[]>), TypeInfoPropertyName = "T8819")] +[JsonSerializable(typeof(Result<V_sealed_record_class_flags_enum>), TypeInfoPropertyName = "T8820")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Option<V_validated_struct_char>>>), TypeInfoPropertyName = "T8821")] +[JsonSerializable(typeof(Cn_record_struct_C<Result<V_sealed_record_class_own_record_class, ShapeFault>>), TypeInfoPropertyName = "T8822")] +[JsonSerializable(typeof(Cn_class_C<Result<V_record_struct_Uri, ShapeFault>>), TypeInfoPropertyName = "T8823")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Option<V_validated_class_DateOnly>>>), TypeInfoPropertyName = "T8824")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_validated_class_DateTime, ShapeFault>[]>), TypeInfoPropertyName = "T8825")] +[JsonSerializable(typeof(Result<V_record_struct_int, ShapeFault>), TypeInfoPropertyName = "T8826")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<Result<V_validated_class_ulong, ShapeFault>>>), TypeInfoPropertyName = "T8827")] +[JsonSerializable(typeof(Cn_record_struct_P<Result<V_validated_struct_ulong, ShapeFault>?>), TypeInfoPropertyName = "T8828")] +[JsonSerializable(typeof(Cn_class_P<IReadOnlyList<Result<V_record_struct_TimeSpan>>>), TypeInfoPropertyName = "T8829")] +[JsonSerializable(typeof(Cn_struct_P<List<Result<V_validated_class_Uri, ShapeFault>>>), TypeInfoPropertyName = "T8830")] +[JsonSerializable(typeof(Cn_struct_P<Result<V_sealed_record_class_DateOnly, ShapeFault>[]>), TypeInfoPropertyName = "T8831")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_validated_class_int, ShapeFault>>), TypeInfoPropertyName = "T8832")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Result<V_sealed_record_class_ulong>>, int>), TypeInfoPropertyName = "T8833")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<Option<V_record_struct_long>, int>>), TypeInfoPropertyName = "T8834")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<Result<V_sealed_record_class_Uri, ShapeFault>, int>, int>), TypeInfoPropertyName = "T8835")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<Result<V_sealed_record_class_int>, int>>), TypeInfoPropertyName = "T8836")] +[JsonSerializable(typeof(Cn_record_struct_P<global::Probes.Json.L06PB_V_validated_class_enum_O>), TypeInfoPropertyName = "T8837")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<string, Result<V_validated_class_DateOnly>>>), TypeInfoPropertyName = "T8838")] +[JsonSerializable(typeof(Cn_record_struct_P<Result<V_record_struct_bool, ShapeFault>>), TypeInfoPropertyName = "T8839")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_validated_class_bool>?>), TypeInfoPropertyName = "T8840")] +[JsonSerializable(typeof(Cn_record_struct_P<List<Result<V_validated_struct_flags_enum, ShapeFault>>>), TypeInfoPropertyName = "T8841")] +[JsonSerializable(typeof(Cn_class_P<Option<V_validated_struct_int>?>), TypeInfoPropertyName = "T8842")] +[JsonSerializable(typeof(Cn_record_struct_P<global::Probes.Json.L06PB_V_validated_struct_DateTimeOffset_O>), TypeInfoPropertyName = "T8843")] +[JsonSerializable(typeof(Cn_class_P<global::Probes.Json.L06PB_PSet_RTE>), TypeInfoPropertyName = "T8844")] +[JsonSerializable(typeof(Cn_record_struct_C<Result<V_record_struct_flags_enum, ShapeFault>>), TypeInfoPropertyName = "T8845")] +[JsonSerializable(typeof(Cn_record_struct_P<Dictionary<Result<V_validated_class_char, ShapeFault>, int>>), TypeInfoPropertyName = "T8846")] +[JsonSerializable(typeof(Cn_generic_type_F<Result<V_validated_class_DateOnly, ShapeFault>, int>), TypeInfoPropertyName = "T8847")] +[JsonSerializable(typeof(Result<PSet, ShapeFault>), TypeInfoPropertyName = "T8848")] +[JsonSerializable(typeof(Cn_struct_C<Result<PSet>>), TypeInfoPropertyName = "T8849")] +[JsonSerializable(typeof(Cn_generic_type_P<List<Result<V_record_struct_DateTime, ShapeFault>>, int>), TypeInfoPropertyName = "T8850")] +[JsonSerializable(typeof(Cn_record_struct_P<global::Probes.Json.L06PB_V_record_struct_byte_RE>), TypeInfoPropertyName = "T8851")] +[JsonSerializable(typeof(Cn_struct_P<ImmutableArray<Option<V_sealed_record_class_TimeSpan>>>), TypeInfoPropertyName = "T8852")] +[JsonSerializable(typeof(Cn_class_R<Result<V_record_struct_NodaTime_Instant, ShapeFault>>), TypeInfoPropertyName = "T8853")] +[JsonSerializable(typeof(Cn_struct_R<Option<V_validated_class_byte>>), TypeInfoPropertyName = "T8854")] +[JsonSerializable(typeof(Cn_struct_P<Option<V_validated_struct_char>>), TypeInfoPropertyName = "T8855")] +[JsonSerializable(typeof(Cn_record_class_P<Result<V_record_struct_string, ShapeFault>>), TypeInfoPropertyName = "T8856")] +[JsonSerializable(typeof(Cn_record_struct_R<Option<V_validated_struct_long>>), TypeInfoPropertyName = "T8857")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<Result<V_validated_struct_DateOnly, ShapeFault>>>), TypeInfoPropertyName = "T8858")] +[JsonSerializable(typeof(Cn_record_class_P<ImmutableArray<Result<V_validated_class_ulong, ShapeFault>>>), TypeInfoPropertyName = "T8859")] +[JsonSerializable(typeof(Cn_record_struct_P<Patch<Result<V_record_struct_Uri, ShapeFault>>>), TypeInfoPropertyName = "T8860")] +[JsonSerializable(typeof(Result<V_validated_struct_char, ShapeFault>), TypeInfoPropertyName = "T8861")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<string, Result<V_validated_struct_Uri, ShapeFault>>>), TypeInfoPropertyName = "T8862")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<Result<V_validated_struct_DateTimeOffset, ShapeFault>>, int>), TypeInfoPropertyName = "T8863")] +[JsonSerializable(typeof(Cn_struct_P<Result<V_record_struct_TimeOnly, ShapeFault>>), TypeInfoPropertyName = "T8864")] +[JsonSerializable(typeof(Cn_generic_type_P<Dictionary<string, Result<V_validated_class_TimeSpan, ShapeFault>>, int>), TypeInfoPropertyName = "T8865")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_sealed_record_class_char, ShapeFault>>), TypeInfoPropertyName = "T8866")] +[JsonSerializable(typeof(Cn_struct_P<Dictionary<Option<V_validated_class_own_record_class>, int>>), TypeInfoPropertyName = "T8867")] +[JsonSerializable(typeof(Cn_record_class_P<List<Result<PSet>>>), TypeInfoPropertyName = "T8868")] +[JsonSerializable(typeof(Cn_class_F<Result<V_sealed_record_class_flags_enum>>), TypeInfoPropertyName = "T8869")] +[JsonSerializable(typeof(Cn_record_struct_F<Option<V_record_struct_int>>), TypeInfoPropertyName = "T8870")] +[JsonSerializable(typeof(Cn_struct_P<IReadOnlyList<Result<V_record_struct_DateTime, ShapeFault>>>), TypeInfoPropertyName = "T8871")] +[JsonSerializable(typeof(Cn_generic_type_P<Patch<Result<V_record_struct_double, ShapeFault>>, int>), TypeInfoPropertyName = "T8872")] +[JsonSerializable(typeof(Cn_record_struct_F<Result<V_record_struct_string, ShapeFault>>), TypeInfoPropertyName = "T8873")] public partial class ProbeContext : JsonSerializerContext { } diff --git a/tools/probe/corpus/harness/Sides.cs b/tools/probe/corpus/harness/Sides.cs new file mode 100644 index 00000000..aedc9f05 --- /dev/null +++ b/tools/probe/corpus/harness/Sides.cs @@ -0,0 +1,317 @@ +// The three-sided differential: the rules every comparison of a value object with its wrapped twin follows, in one place. A comparison +// has three sides: +// (1) the wrapped twin without TypeKit: for http a host without AddTypeKit() and without TypeKit's JSON converters +// (HostCfg.WithoutTypeKit); in process, the twin under the application's options less TypeKit's converters (WithoutTypeKit); +// (2) the wrapped twin with TypeKit, as the row configures it; +// (3) the value object. +// Side (1) is read from a fact's fixture where one covers the row's configuration (Fact, FactFixture), and the check's baseline is then +// Baseline.Fact("KB-NNN"); otherwise it is measured, and the baseline is Baseline.Framework. A configuration measured here that no fact +// covers and that decides rows is pinned as a fact with its test and fixture (design/knowledge), never as a note key. +// The rules: +// - (1) and (3) refuse alike, at the same operation and stage with the same exception type and the same HTTP status: the framework +// refuses, and the check ends expected-refusal against side (1)'s baseline (NoteKeys.FrameworkRefuses). No message is compared: the +// stage is what an exception's properties say (a JsonException's Path), never its text. +// - (1) answers and (3) refuses, or (1) refuses and (3) answers: a violation, unless a sentence makes the difference intended, cited by +// the check (a not-promised entry of the claim, or the docs section a calibration cites). +// - (2) differs from (1): a violation, "TypeKit changed the wrapped type's behaviour" (Interferes). (2) and (3) agreeing on a wrong answer +// is why side (1) runs on every row of a claim whose obligations promise non-interference (NonInterference). +// - Elsewhere side (1) runs lazily, only where (2) and (3) disagree or refuse. +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using CodoMetis.TypeKit.Facts; +namespace Probes.Shapes; + +public static class Sides +{ + /// <summary> + /// One side's answer to one operation: the text it gave, or the exception it threw or the status it answered with. A side read from a + /// fixture has no exception object, only the type the fixture names (<see cref="Refusal"/>). + /// </summary> + public sealed record Answer(string Operation, string? Value, Exception? Thrown = null, int? Status = null, string? FixtureRefusal = null) + { + public bool Refused => Thrown is not null || FixtureRefusal is not null || Status is { } s && s is not (>= 200 and < 300); + /// <summary>The refusal's exception type, by its full name; null for an answer or a refusal by status alone.</summary> + public string? Refusal => Thrown?.GetType().FullName ?? FixtureRefusal; + /// <summary> + /// Where the refusal was met, from the exception's properties and never from its message: the JSON path a JsonException names. A side + /// read from a fixture has none: the settings its answer covers (the depth a value is written at) say where. + /// </summary> + public string? Stage => FixtureRefusal is not null ? null : Thrown is JsonException j ? j.Path ?? "" : ""; + public override string ToString() => + Refused ? $"{Operation} refused ({string.Join(" ", new[] { Thrown?.GetType().Name ?? FixtureRefusal, Status?.ToString() }.Where(x => x is not null))}{(Stage is { Length: > 0 } at ? " at " + at : "")})" + : $"{Operation} {Cut(Value)}"; + } + + /// <summary>(1) and (3) refuse alike: at the same operation and stage, with the same exception type and the same status. No message is compared.</summary> + public static bool Alike(Answer one, Answer three) => + one.Refused && three.Refused && one.Operation == three.Operation && one.Refusal == three.Refusal && one.Status == three.Status + && (one.Stage is null || SameStage(one.Stage, three.Stage!)); + + /// <summary> + /// Two refusals' stages are the same: the same JSON path, or, where one side writes or reads its value through a nested call of the + /// serializer, whose path starts at that value (a value object over a class the serializer walks), the other's path ending in it. + /// </summary> + static bool SameStage(string a, string b) => + a == b || (b.Length > 1 && b[0] == '$' && a.EndsWith(b[1..], StringComparison.Ordinal)) || (a.Length > 1 && a[0] == '$' && b.EndsWith(a[1..], StringComparison.Ordinal)); + + /// <summary> + /// (2) is not (1): TypeKit changed what the wrapped type does. The failure, or null where the two are the same answer: the same text, or + /// a refusal alike (a refusal's body, such as a problem's trace id, is not compared). + /// </summary> + public static string? Interferes(Answer one, Answer two) => + one.Refused == two.Refused && one.Refusal == two.Refusal && one.Status == two.Status && one.Stage == two.Stage && (one.Refused || one.Value == two.Value) ? null + : $"{two.Operation}: TypeKit changed the wrapped type's behaviour: without TypeKit {one}, with it {two}"; + + /// <summary> + /// Ends a check whose sides (1) and (3) refuse alike: the framework's refusal, an expected refusal that earns nothing. The check runs + /// against side (1)'s baseline, which its caller chose (<see cref="Side.Baseline"/>). + /// </summary> + public static string? Refuses(Check c, Side one, Answer three) + { + c.Note(NoteKeys.FrameworkRefuses, $"{three}, {one.Source}"); + return null; + } + + /// <summary> + /// One check of the differential. Where side (1) refuses alike with (3) (<paramref name="framework"/>), the check is the framework's + /// refusal, against side (1)'s baseline; otherwise <paramref name="body"/> compares, against <paramref name="against"/>, once (2) is held + /// to (1) where side (1) ran (<paramref name="one"/>, <paramref name="two"/>). + /// </summary> + public static string? Check(Row r, string obligation, Baseline against, Side? framework, Answer three, Answer? one, Answer? two, Func<Check, string?> body) => + framework is not null && Alike(framework.Answer, three) + ? r.Check(obligation, framework.Baseline, c => Refuses(c, framework, three)) + : r.Check(obligation, against, c => (one is not null && two is not null ? Interferes(one, two) : null) ?? body(c)); + + /// <summary>The asynchronous form of <see cref="Check(Row, string, Baseline, Side?, Answer, Answer?, Answer?, Func{Check, string?})"/>.</summary> + public static Task<string?> Check(Row r, string obligation, Baseline against, Side? framework, Answer three, Answer? one, Answer? two, Func<Check, Task<string?>> body) => + framework is not null && Alike(framework.Answer, three) + ? Task.FromResult(r.Check(obligation, framework.Baseline, c => Refuses(c, framework, three))) + : r.Check(obligation, against, async c => (one is not null && two is not null ? Interferes(one, two) : null) ?? await body(c)); + + /// <summary> + /// Side (1) where the value object refused (<paramref name="three"/>), as the framework's: the twin without TypeKit refusing alike + /// (<paramref name="one"/>), or, where the twin answered, a converter's value under the row's depth limit (<see cref="WrittenByAConverter"/>). + /// Null where the framework does not refuse alike, and the check compares. + /// </summary> + public static Side? Framework(Row r, Entry e, Held twin, JsonSerializerOptions oneOptions, Answer one, Answer three) + { + if (!three.Refused) return null; + if (one.Refused) return Alike(one, three) ? Measured(one, "the twin under the application's options, less TypeKit's converters") : null; + return WrittenByAConverter(r, e, twin, oneOptions, three) is { } s && Alike(s.Answer, three) ? s : null; + } + + /// <summary>Side (1) as a check uses it: its answer, the baseline it stands for, and where it came from.</summary> + public sealed record Side(Answer Answer, Baseline Baseline, string Source); + + /// <summary>Side (1) measured: the framework without TypeKit, run in the same row.</summary> + public static Side Measured(Answer a, string how) => new(a, Baseline.Framework, "measured: " + how); + + // ---- the twin + + /// <summary> + /// The twin and the value object at the row's position in its containing type, each around its sample: the one place where both sides + /// of a two-sided comparison are built, and where the precondition of every such comparison is held, that the twin and the value object + /// hold the same shape (Equivalence.Shapes: the twin stands where the value object stands, at its nullability). A mismatch is a harness + /// error, never a pass: it throws, and the check or row that asked ends as one. + /// </summary> + public static (Held Twin, Held Vo) Twins(string position, string containing, Entry e, object plain, object vo) + { + var hp = Pos.Build(position, containing, e, false, plain); + var hv = Pos.Build(position, containing, e, true, vo); + if (Equivalence.Shapes(hp.Declared, hv.Declared, e) is { } why) throw new InputsNotEquivalent(why); + return (hp, hv); + } + + /// <summary>The twin and the value object do not hold the same shape: the harness built a comparison that compares nothing.</summary> + public sealed class InputsNotEquivalent(string why) : Exception("inputs not equivalent: " + why); + + // ---- which obligations promise non-interference + + /// <summary> + /// The obligations whose sentence promises that the value object leaves the wrapped type as ASP.NET or System.Text.Json have it ("as the + /// underlying type's", "the one ASP.NET Core publishes for the underlying type", "unaffected"), read from each claim's obligations in + /// claims.json. Side (1) runs on every row that checks one of them. C12's parameter-keeps-aspnet and C01's covers-every-decimal already + /// compare with the framework without TypeKit's converters on every row (SchemaClaim's Baseline.Framework, DecimalSpelling's serializer's + /// own decimal); the text claims compare with the wrapped type's own text, which TypeKit adds nothing to. + /// </summary> + static readonly Dictionary<string, string[]> NonInterference = new() + { + ["C21-openapi-schema-parity"] = ["the-underlying-types-schema", "nullable-admits-null-as-the-wrapped-does", "container-elements", "dictionary-key", "every-document-version"], + ["L01-json-bytes"] = ["the-underlying-types-json", "as-a-dictionary-key-too", "read-as-the-underlying-type-reads"], + ["C10-preserve-guard"] = ["value-converters-unaffected", "own-converters-unaffected", "ignore-cycles-unaffected"], + }; + + /// <summary>Whether side (1) runs on every row for this obligation of the row's claim, not only where (2) and (3) disagree or refuse.</summary> + public static bool Unconditional(Row r, string obligation) => + r.Claim is { } claim && NonInterference.TryGetValue(claim, out var obligations) && obligations.Contains(obligation); + + /// <summary> + /// Whether a type is or holds one of TypeKit's own (a Patch, an Option), through its type arguments and element type: a twin that holds + /// one has no side without TypeKit. + /// </summary> + public static bool HoldsTypeKit(Type t) => + t.Assembly == typeof(CodoMetis.TypeKit.ValueObjects.StoredJsonConverterFactory).Assembly + || (t.IsArray && HoldsTypeKit(t.GetElementType()!)) + || (t.IsGenericType && t.GetGenericArguments().Any(HoldsTypeKit)); + + // ---- side (1) in process: the twin under the application's options, less TypeKit's converters + + /// <summary>The application's options less what TypeKit adds to them: its converters (StoredJsonConverterFactory, the decimal converters), told by their assembly.</summary> + public static JsonSerializerOptions WithoutTypeKit(JsonSerializerOptions o) + { + var one = new JsonSerializerOptions(o); + DropTypeKit(one); + return one; + } + + /// <summary>Removes TypeKit's converters from options in place: a host's side (1) (HostCfg.WithoutTypeKit).</summary> + public static void DropTypeKit(JsonSerializerOptions o) + { + var typeKit = typeof(CodoMetis.TypeKit.ValueObjects.StoredJsonConverterFactory).Assembly; + foreach (var x in o.Converters.Where(x => x.GetType().Assembly == typeKit).ToList()) o.Converters.Remove(x); + } + + /// <summary>A side's write of a held shape.</summary> + public static Answer Write(Held h, JsonSerializerOptions o, string operation = "write") + { + try { return new(operation, JsonSerializer.Serialize(h.Value, h.Declared, o)); } + catch (Exception x) { return new(operation, null, x); } + } + + /// <summary>A side's read of a text into a held shape, and what it read written again: a refusal of the write back is the operation "write back".</summary> + public static Answer Read(string text, Held h, JsonSerializerOptions o, string operation = "read") + { + object? v; + try { v = JsonSerializer.Deserialize(text, h.Declared, o); } + catch (Exception x) { return new(operation, null, x); } + try { return new(operation, v is null ? "null" : JsonSerializer.Serialize(v, h.Declared, o)); } + catch (Exception x) { return new("write back", null, x); } + } + + // ---- side (1) of a value a converter writes, under a depth limit (KB-040) + + /// <summary> + /// Side (1) where the value object, which its converter writes, refused a write that the twin, which the serializer's own converter writes, + /// answered, under the row's json.MaxDepth: System.Text.Json charges a converter's value a level against the limit (KB-040), so the + /// framework's side for a converter-written value is a converter of the application's for the wrapped type, without TypeKit. Read from + /// KB-040's fixture where an answer covers the row's depth limit and the depth the value is written at, else measured with that + /// converter in the same row. Null where the row sets no depth limit, (3) refused no write, or the depth cannot be read. + /// </summary> + public static Side? WrittenByAConverter(Row r, Entry e, Held twin, JsonSerializerOptions one, Answer three) + { + if (r.Get("json.MaxDepth") is null or "default" || three.Thrown is null || three.Operation is not ("write" or "write back")) return null; + if (ConverterDepth(e, twin, one) is not { } depth) { r.Log.AppendLine(Why = $"side (1) of a converter's value: no depth read for {Make.Held(e.Plain).Name} ({Unread})"); return null; } + var settings = Settings(r, ("written-by", "a-converter"), ("writer-depth", depth.ToString(System.Globalization.CultureInfo.InvariantCulture))); + if (Covered(r, "KB-040", settings) is { Kind: FactAnswer.Exception } answer) + return new(new(three.Operation, null, FixtureRefusal: (string?)answer.Value["type"]), Baseline.Fact("KB-040"), + $"KB-040 {answer.Id}: a converter's value at depth {depth} under MaxDepth {r["json.MaxDepth"]}"); + var measured = ByAConverter(e, twin, one, three.Operation); + Why = $"side (1) of a converter's value at depth {depth}: no KB-040 answer covers it, measured {measured?.ToString() ?? "nothing"}"; + return measured is null ? null : Measured(measured, $"the twin written by a converter of the application's at depth {depth}"); + } + + /// <summary>The writer's depth where the serializer hands the wrapped value to a converter, read by one that records it, without a limit.</summary> + static int? ConverterDepth(Entry e, Held twin, JsonSerializerOptions one) + { + try + { + var open = new JsonSerializerOptions(one) { MaxDepth = 64 }; + var serializers = new JsonSerializerOptions(open); + var converter = (IRecordsDepth)Activator.CreateInstance(typeof(PassThrough<>).MakeGenericType(Make.Held(e.Plain)), serializers)!; + open.Converters.Insert(0, (JsonConverter)converter); + JsonSerializer.Serialize(twin.Value, twin.Declared, open); + Unread = converter.Depth >= 0 ? null : "no value handed to the converter"; + return converter.Depth >= 0 ? converter.Depth : null; + } + catch (Exception x) { Unread = x.GetType().Name + ": " + Cut(x.Message); return null; } // the twin is not written with a converter of the application's here: no depth to read + } + [ThreadStatic] static string? Unread; + /// <summary>Why the last <see cref="WrittenByAConverter"/> on this thread gave no fixture's side, for a failure to name.</summary> + [ThreadStatic] public static string? Why; + + /// <summary>The twin written (and, for a write back, read and written again) by a converter of the application's that writes as the serializer does.</summary> + static Answer? ByAConverter(Entry e, Held twin, JsonSerializerOptions one, string operation) + { + try + { + var with = new JsonSerializerOptions(one); + with.Converters.Insert(0, (JsonConverter)Activator.CreateInstance(typeof(PassThrough<>).MakeGenericType(Make.Held(e.Plain)), new JsonSerializerOptions(one))!); + return Write(twin, with, operation); + } + catch (Exception) { return null; } + } + + interface IRecordsDepth { int Depth { get; } } + + /// <summary>A converter of the application's for the wrapped type, without TypeKit: it writes and reads through the serializer's own contract for it, and records the writer's depth.</summary> + sealed class PassThrough<T>(JsonSerializerOptions serializers) : JsonConverter<T>, IRecordsDepth where T : notnull + { + // options a host or a row built without a resolver get the serializer's default one, as JsonSerializer gives them on first use + readonly JsonTypeInfo<T> info = (JsonTypeInfo<T>)Resolving(serializers).GetTypeInfo(typeof(T)); + static JsonSerializerOptions Resolving(JsonSerializerOptions o) { o.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); return o; } + public int Depth { get; private set; } = -1; + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonSerializer.Deserialize(ref reader, info); + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { Depth = Math.Max(Depth, writer.CurrentDepth); JsonSerializer.Serialize(writer, value, info); } + public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ((JsonConverter<T>)info.Converter).ReadAsPropertyName(ref reader, typeToConvert, serializers); + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { Depth = Math.Max(Depth, writer.CurrentDepth); ((JsonConverter<T>)info.Converter).WriteAsPropertyName(writer, value, serializers); } + } + + // ---- facts' fixtures + + static readonly Dictionary<string, FactFixture?> Fixtures = new(); + + /// <summary> + /// A fact's fixture as side (1), as FactFixture's rule says: only a fixture the fact's entry names on its <c>fixture:</c> line, read from the + /// repository (the Facts project's test/CodoMetis.TypeKit.Facts, found by walking up to CodoMetis.TypeKit.slnx), never written. Null where + /// the fact names none. Whether `docs-query check` is clean is CI's to hold: the harness does not run it. + /// </summary> + public static FactFixture? Fact(string id) + { + lock (Fixtures) + { + if (Fixtures.TryGetValue(id, out var known)) return known; + var root = new DirectoryInfo(AppContext.BaseDirectory); + while (root is not null && !File.Exists(Path.Combine(root.FullName, "CodoMetis.TypeKit.slnx"))) root = root.Parent; + FactFixture? fixture = null; + if (root is not null) + { + var file = FactFixture.FileName(id); + var entry = Directory.EnumerateFiles(Path.Combine(root.FullName, "design", "knowledge"), id + "-*.md").FirstOrDefault(); + // the entry's frontmatter, between its first two "---" lines, names the fixture + var named = entry is not null && File.ReadLines(entry).Skip(1).TakeWhile(l => l != "---") + .Any(l => l.Trim() == $"fixture: test/CodoMetis.TypeKit.Facts/{file}"); + var path = Path.Combine(root.FullName, "test", "CodoMetis.TypeKit.Facts", file); + if (named && File.Exists(path)) fixture = FactFixture.Load(path); + } + return Fixtures[id] = fixture; + } + } + + /// <summary> + /// The row's settings as a fixture's answers name them: every axis at the value that applied (a substitution's, else the row's), unread, + /// and the settings the harness measured (<paramref name="measured"/>), such as the depth a value is written at. The caller marks read + /// the axes of the answer that covers them (<see cref="Covered"/>). + /// </summary> + public static Dictionary<string, string> Settings(Row r, params (string Key, string Value)[] measured) + { + var s = new Dictionary<string, string>(StringComparer.Ordinal); + Dictionary<string, string> applied; + lock (r.Ev) applied = new(r.Ev.Applied); + foreach (var (axis, value) in r.Values) s[axis] = applied.TryGetValue(axis, out var a) ? a : value; + foreach (var (key, value) in measured) s[key] = value; + return s; + } + + /// <summary>The answer of a fact's fixture that covers the row's settings, its axes then read for the running check; null where none does.</summary> + public static FactAnswer? Covered(Row r, string fact, Dictionary<string, string> settings) + { + if (Fact(fact)?.For(settings) is not { } answer) return null; + foreach (var axis in answer.When.Keys) _ = r.Get(axis); + return answer; + } + + static string Cut(string? s) => s is null ? "(null)" : s.Length > 160 ? s[..160] + "..." : s; +} diff --git a/tools/probe/corpus/harness/TextRow.cs b/tools/probe/corpus/harness/TextRow.cs index b8e71187..a2df1d53 100644 --- a/tools/probe/corpus/harness/TextRow.cs +++ b/tools/probe/corpus/harness/TextRow.cs @@ -33,7 +33,7 @@ public static void Prepare(Row r, Entry e) bool gi; try { gi = new CultureInfo("de-DE").NumberFormat.NumberDecimalSeparator == "."; } catch (CultureNotFoundException) { gi = true; } if (c == "globalization-invariant" && !gi) r.Substituted("text.culture", "invariant", "the process is not in globalization-invariant mode"); if (c != "globalization-invariant" && gi) r.Substituted("text.culture", "globalization-invariant", "the process is in globalization-invariant mode"); - if (r["process.converter-lookup"] == "wrapped-type-converter-looked-up-first") _ = TypeDescriptor.GetConverter(e.Plain); + if (r["process.converter-lookup"] == "wrapped-type-converter-looked-up-first") _ = TypeDescriptor.GetConverter(Make.Held(e.Plain)); } static Out Unwrap(Func<Out> f) @@ -46,7 +46,10 @@ static Out Unwrap(Func<Out> f) /// <summary>Plain text of a wrapped value the way the value object formats it (invariant), used as the sample's text.</summary> public static string PlainText(Entry e, object plain) => plain is IFormattable f ? f.ToString(Fmt(e), CultureInfo.InvariantCulture) : plain.ToString()!; - public static bool HasText(Entry e) => e.Vo.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]) is not null; + /// <summary>The value object reads text: a generated Parse(string, IFormatProvider), or a closed set's Parse(string) by name.</summary> + public static bool HasText(Entry e) => e.Vo.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]) is not null || ByName(e, "Parse", [typeof(string)]) is not null; + /// <summary>A closed set's own Parse and TryParse, by name and ordinal (Generators README#matching-parsing-and-binding): the provider means nothing to a name.</summary> + static MethodInfo? ByName(Entry e, string name, Type[] parameters) => e.Kind == "closed-set" ? e.Vo.GetMethod(name, parameters) : null; public static Out Read(Entry e, string entry, string text) => Unwrap(() => { @@ -54,14 +57,17 @@ public static Out Read(Entry e, string entry, string text) => Unwrap(() => { case "Parse": { - var m = e.Vo.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); if (m is null) return Out.NA; - return new(false, true, m.Invoke(null, [text, null]), null, null); + var m = e.Vo.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); + if (m is not null) return new(false, true, m.Invoke(null, [text, null]), null, null); + return ByName(e, "Parse", [typeof(string)]) is { } n ? new(false, true, n.Invoke(null, [text]), null, null) : Out.NA; } case "TryParse": { - var m = e.Vo.GetMethod("TryParse", [typeof(string), typeof(IFormatProvider), e.Vo.MakeByRefType()]); if (m is null) return Out.NA; - var a = new object?[] { text, null, null }; var ok = (bool)m.Invoke(null, a)!; - return new(false, ok, a[2], null, null); + var m = e.Vo.GetMethod("TryParse", [typeof(string), typeof(IFormatProvider), e.Vo.MakeByRefType()]); + if (m is not null) { var a = new object?[] { text, null, null }; var ok = (bool)m.Invoke(null, a)!; return new(false, ok, a[2], null, null); } + if (ByName(e, "TryParse", [typeof(string), e.Vo.MakeByRefType()]) is not { } n) return Out.NA; + var b = new object?[] { text, null }; var found = (bool)n.Invoke(null, b)!; + return new(false, found, b[1], null, null); } case "IParsable": { @@ -112,7 +118,7 @@ public static Out TryFrom(Entry e, object plain) => Unwrap(() => if (e.Kind == "closed-set") return Out.NA; if (ValidatedKind(e)) { - var m = e.Vo.GetMethod("TryFrom", [e.Plain]); if (m is null) return Out.NA; + var m = e.Vo.GetMethod("TryFrom", [Make.Held(e.Plain)]); if (m is null) return Out.NA; var opt = m.Invoke(null, [plain])!; var g = opt.GetType().GetMethod("TryGetValue")!; var a = new object?[] { null }; var ok = (bool)g.Invoke(opt, a)!; return new(false, ok, a[0], null, null); } @@ -123,10 +129,11 @@ public static Out TryFrom(Entry e, object plain) => Unwrap(() => public static class TextClaims { static readonly string[] Entries = ["Parse", "TryParse", "TryFrom", "type-converter", "IParsable", "ISpanParsable", "ToString-format", "TryFormat-short-span"]; + static bool Reads(string entry) => entry is "Parse" or "TryParse" or "IParsable" or "ISpanParsable" or "type-converter"; static bool WrappedLosesItToo(Entry e, object plain) { - var parse = e.Plain.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); + var parse = Make.Held(e.Plain).GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); if (parse is null) return false; try { return !Equals(parse.Invoke(null, [Convert.ToString(plain), null]), plain); } catch (TargetInvocationException) { return true; } @@ -134,229 +141,524 @@ static bool WrappedLosesItToo(Entry e, object plain) static bool Same(Entry e, object? a, object? b) => a is not null && b is not null && (a.Equals(b) || (e.Wrapped == "DateTime" && ((DateTime)Make.Unwrap(e, a)).ToUniversalTime() == ((DateTime)Make.Unwrap(e, b)).ToUniversalTime())); + /// <summary> + /// Why a generated text entry is missing, where a document says it is not generated: a closed set's surface is its own (Generators + /// README#closed-sets), no parsing is generated over a type that cannot be parsed, and no span parsing over a type that has none + /// (#what-is-generated). Each is declared in claims.json too; null where nothing says it is missing. + /// </summary> + static string? Missing(Entry e, string entry) + { + if (e.Kind == "closed-set") return entry is "TryFrom" or "ISpanParsable" or "ToString-format" or "TryFormat-short-span" ? "closed-set" : null; + if (!TextOps.HasText(e) && entry is not ("ToString-format" or "TryFormat-short-span" or "TryFrom")) return "not-parsable"; + if (entry == "ISpanParsable" && e.Wrapped is "Uri" or "enum" or "flags-enum" or "NodaTime.Instant") return "no-span-parsing"; + return null; + } + /// <summary>The check does not apply: the entry <see cref="Missing"/> names is not generated, cited by the sentence that says so.</summary> + static string? NotGenerated(Check c, Entry e, string entry) => Missing(e, entry) switch + { + "closed-set" => c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#closed-sets"), entry), + _ => c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#what-is-generated"), entry), + }; + // ---- L04: write then read gives back the value; text half and JSON half + /// <summary> + /// L04's checks: the generated ToString() is the wrapped type's invariant text whatever the culture (invariant-whatever-the-culture); + /// the row's writing entry writes the same (the-type-converter-writes-the-same); that text, or TryFrom's value, reads back as the same + /// value wherever the wrapped type's own does (text-reads-back); and the JSON half, the value object's own document read back and the + /// row's changed input refused with a JsonException naming the value object (json-refusal-is-a-json-exception). Every check runs; + /// returns the first failure. + /// </summary> public static string? RoundTrip(Row r, Entry e) { TextOps.Prepare(r, e); var entry = r["text.entry"]; var plain = e.Valid(); var vo = Make.Vo(e, plain); + var fails = new List<string?>(); if (entry == "TryFrom") - { - var t = TextOps.TryFrom(e, plain); - if (t.NotApplicable) r.Note("TryFrom-NA"); else if (!t.Ok || !Same(e, t.Value, vo)) return $"TryFrom(valid) gave ok={t.Ok} value={t.Value}, expected {vo}"; - } - else if (!TextOps.HasText(e)) r.Note("no-text:" + e.Kind); + fails.Add(r.Check("text-reads-back", Baseline.RoundTrip, c => + { + var t = TextOps.TryFrom(e, plain); + if (t.NotApplicable) return Missing(e, entry) is not null ? NotGenerated(c, e, entry) : "TryFrom is not generated for the value object"; + if (!t.Ok || !Same(e, t.Value, vo)) return $"TryFrom(valid) gave ok={t.Ok} value={t.Value}, expected {vo}"; + c.Note(NoteKeys.Compared, "TryFrom(valid)"); + return null; + })); else { - var w = TextOps.Write(e, entry is "Parse" or "TryParse" or "IParsable" or "ISpanParsable" ? "ToString" : entry, vo); - if (w.NotApplicable) r.Note(entry + "-NA"); - else if (w.Ex is not null) return $"{entry}: writing text threw {w.Ex.GetType().Name}: {w.Ex.Message}"; - else + var invariant = TextOps.PlainText(e, plain); + var w0 = TextOps.Write(e, "ToString", vo); + fails.Add(r.Check("invariant-whatever-the-culture", Baseline.Twin, c => { - var text = w.Text!; - var wantText = entry is "TryFormat-short-span" or "type-converter" ? (plain is IFormattable pf ? pf.ToString(null, CultureInfo.InvariantCulture) : plain.ToString()!) : TextOps.PlainText(e, plain); - if (e.Kind != "closed-set" && entry is "ToString-format" or "TryFormat-short-span" or "type-converter" && text != wantText) return $"{entry}: text '{text}' differs from the wrapped type's invariant text '{wantText}' under culture {r["text.culture"]}"; - if (entry == "TryFormat-short-span") + if (w0.Ex is not null) return $"ToString(): threw {w0.Ex.GetType().Name}: {w0.Ex.Message}"; + if (w0.Text != invariant) return $"ToString(): text '{w0.Text}' differs from the wrapped type's invariant text '{invariant}' under culture {c["text.culture"]}"; + c.Note(NoteKeys.Compared, $"ToString() under {c["text.culture"]}"); + return null; + })); + var writes = entry is "ToString-format" or "TryFormat-short-span" or "type-converter"; + var w = writes ? TextOps.Write(e, entry, vo) : w0; + if (writes) + fails.Add(r.Check("the-type-converter-writes-the-same", Baseline.Twin, c => { - var args = new object?[] { vo, 1, null, null }; - try + if (w.NotApplicable) { - typeof(TextOps).GetMethod(nameof(TextOps.FS))!.MakeGenericMethod(e.Vo).Invoke(null, args); - if (text.Length > 1 && (bool)args[2]!) return $"TryFormat into a 1-char span said it fit text of {text.Length} chars"; - if (text.Length > 1 && (int)args[3]! != 0) return "TryFormat wrote chars into a too-short span and still returned false"; + if (Missing(e, entry) is not null) return NotGenerated(c, e, entry); + // TryFormat is generated where the wrapped type has it (ValueObjectFormattableAspect); the wrapped type has none either + if (entry == "TryFormat-short-span" && !typeof(ISpanFormattable).IsAssignableFrom(Make.Held(e.Plain))) + return c.Inconclusive($"no TryFormat: the wrapped {Make.Held(e.Plain).Name} has no ISpanFormattable either"); + return $"{entry}: the value object writes no text where the wrapped {Make.Held(e.Plain).Name} does"; } - catch (TargetInvocationException t) { return $"TryFormat into a short span threw {t.InnerException!.GetType().Name}"; } - } - var readEntry = entry is "ToString-format" or "TryFormat-short-span" ? "Parse" : entry; - if (!(entry == "type-converter" && TextOps.Lossy(e))) - { - var back = TextOps.Read(e, readEntry, text); - if (!back.NotApplicable) + if (w.Ex is not null) return $"{entry}: writing text threw {w.Ex.GetType().Name}: {w.Ex.Message}"; + var text = w.Text!; + var wantText = entry is "TryFormat-short-span" or "type-converter" ? (plain is IFormattable pf ? pf.ToString(null, CultureInfo.InvariantCulture) : plain.ToString()!) : invariant; + if (text != wantText) return $"{entry}: text '{text}' differs from the wrapped type's invariant text '{wantText}' under culture {c["text.culture"]}"; + if (entry == "TryFormat-short-span") { - if (back.Ex is not null) return $"{readEntry}: reading back '{text}' threw {back.Ex.GetType().Name}: {back.Ex.Message}"; - if (!back.Ok) return $"{readEntry}: could not read back its own text '{text}'"; - if (!Same(e, back.Value, vo)) + var args = new object?[] { vo, 1, null, null }; + try { - // Differential: the value object only has to round-trip where its wrapped type does - // (TimeOnly.ToString() under the invariant culture prints HH:mm and drops the seconds). - if (!WrappedLosesItToo(e, plain)) return $"{readEntry}: '{text}' read back as {back.Value}, expected {vo}"; - r.Note("text:wrapped-round-trip-lossy-too"); + typeof(TextOps).GetMethod(nameof(TextOps.FS))!.MakeGenericMethod(e.Vo).Invoke(null, args); + if (text.Length > 1 && (bool)args[2]!) return $"TryFormat into a 1-char span said it fit text of {text.Length} chars"; + if (text.Length > 1 && (int)args[3]! != 0) return "TryFormat wrote chars into a too-short span and still returned false"; } + catch (TargetInvocationException t) { return $"TryFormat into a short span threw {t.InnerException!.GetType().Name}"; } } + c.Note(NoteKeys.Compared, $"{entry} under {c["text.culture"]}"); + return null; + })); + fails.Add(r.Check("text-reads-back", Baseline.RoundTrip, c => + { + var readEntry = entry is "ToString-format" or "TryFormat-short-span" ? "Parse" : entry; + if (Missing(e, entry) is not null) return NotGenerated(c, e, entry); + if (Missing(e, readEntry) is not null) return NotGenerated(c, e, readEntry); + if (w.NotApplicable || w.Ex is not null || w.Text is null) return c.Inconclusive($"{entry}: no text written to read back, judged by the-type-converter-writes-the-same"); + // claims.json declares it: not-promised dates-and-times-lose-detail-as-text + if (entry == "type-converter" && TextOps.Lossy(e)) return c.Inapplicable(Cite.NotPromised("dates-and-times-lose-detail-as-text"), entry); + var text = w.Text; + var back = TextOps.Read(e, readEntry, text); + if (back.NotApplicable) return $"{readEntry}: the value object has no {readEntry} to read its own text '{text}' back"; + if (back.Ex is not null) return $"{readEntry}: reading back '{text}' threw {back.Ex.GetType().Name}: {back.Ex.Message}"; + if (!back.Ok) return $"{readEntry}: could not read back its own text '{text}'"; + if (!Same(e, back.Value, vo)) + { + // Differential: the value object only has to round-trip where its wrapped type does + // (TimeOnly.ToString() under the invariant culture prints HH:mm and drops the seconds). + if (!WrappedLosesItToo(e, plain)) return $"{readEntry}: '{text}' read back as {back.Value}, expected {vo}"; + if (TextOps.Lossy(e)) return c.Inapplicable(Cite.NotPromised("dates-and-times-lose-detail-as-text"), $"{readEntry}: the wrapped type's own text does not read back either"); + return c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#parsing-and-formatting"), $"{readEntry}: the wrapped type's own text does not read back either"); // "wherever the underlying type's own does" } - } + c.Note(NoteKeys.Compared, $"{readEntry} read back '{text}'"); + return null; + })); } - return JsonRoundTrip(r, e); + fails.AddRange(JsonRoundTrip(r, e)); + return fails.FirstOrDefault(f => f is not null); } - static string? JsonRoundTrip(Row r, Entry e) + /// <summary> + /// L04's JSON half, json-refusal-is-a-json-exception: the value object's own document reads back as written (the round trip the + /// refusal is measured from), and the row's changed input is read or refused with a JsonException, at the root naming the value object + /// and carrying no inner exception ("Input it can't read throws a JsonException naming the value object and the underlying type"). + /// Three-sided (Sides): where the value object (3) refuses, the twin without TypeKit's converters (1) refusing alike is the framework's + /// refusal, and so is a converter's value refused under the row's depth limit (KB-040). + /// </summary> + static List<string?> JsonRoundTrip(Row r, Entry e) { + const string Obligation = "json-refusal-is-a-json-exception"; var o = JsonRow.Apply(r, e).Options!; + var one = Sides.WithoutTypeKit(o); var pos = r["shape.position"]; var con = r.Get("shape.containing") ?? "class"; var plain = e.Valid(); var vo = Make.Vo(e, plain); - var hv = Pos.Build(pos, con, e, true, vo); var hp = Pos.Build(pos, con, e, false, plain); - string jv; - try { jv = JsonSerializer.Serialize(hv.Value, hv.Declared, o); } - catch (Exception ex) - { - try { JsonSerializer.Serialize(hp.Value, hp.Declared, o); } catch { r.Note("ser-both-threw"); return null; } - if (r.Is("json.MaxDepth", "3") && ex.Message.Contains("maximum allowed depth")) return null; - if (r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && ex is NotSupportedException) return null; // documented - return $"serializing threw {ex.GetType().Name} where the wrapped type did not: {ex.Message}"; - } - string? Back(string text, out Exception? ex) + var (hp, hv) = Sides.Twins(pos, con, e, plain, vo); + var w3 = Sides.Write(hv, o); var jv = w3.Value; + bool Guarded(Exception? x, Check c) => c.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && x is NotSupportedException; + var fails = new List<string?>(); + // the round trip: the value object's write, then its own document read back and written again; side (1) where it refuses + Sides.Answer w2 = Sides.Write(hp, o), three, two, first; + if (w3.Refused) { three = w3; two = w2; first = Sides.Write(hp, one); } + else { - ex = null; - try { var v = JsonSerializer.Deserialize(text, hv.Declared, o); return v is null ? "null" : JsonSerializer.Serialize(v, hv.Declared, o); } catch (Exception x) { ex = x; return null; } + three = Sides.Read(jv!, hv, o); + two = w2.Value is { } jp ? Sides.Read(jp, hp, o) : w2; + first = Sides.Write(hp, one) is { Value: { } j1 } ? Sides.Read(j1, hp, one) : Sides.Write(hp, one); } - var b = Back(jv, out var ex1); - if (ex1 is not null) + var lazy = three.Refused || two.Refused; + fails.Add(Sides.Check(r, Obligation, Baseline.RoundTrip, lazy ? Sides.Framework(r, e, hp, one, first, three) : null, three, lazy ? first : null, lazy ? two : null, c => { - if (JsonParity.ReadMayDiffer(r, e, "valid", ex1) || r.Get("json.UnmappedMemberHandling") == "Disallow" || (r.Is("json.MaxDepth", "3") && ex1.Message.Contains("maximum allowed depth")) || r.Is("json.ReferenceHandler", "Preserve")) { r.Note("read-back-refused:" + ex1.GetType().Name); } - else if (PlainFailsToo(r, hp, o, pos, e)) r.Note("plain-read-back-fails-too"); - else return $"own JSON {Cut(jv)} did not read back: {ex1.GetType().Name}: {Cut(ex1.Message)}"; - } - else if (b != jv && !r.Is("json.ReferenceHandler", "Preserve")) return $"JSON round trip differs: wrote {Cut(jv)}, read and wrote {Cut(b)}"; + if (w3.Refused) + { + if (w2.Refused) return c.Inconclusive($"write: both refuse, not alike: the wrapped type {w2}, the value object {w3}"); + if (Guarded(w3.Thrown, c)) return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "write"); + return $"serializing threw {w3.Thrown!.GetType().Name} where the wrapped type did not: {w3.Thrown.Message}"; + } + if (three.Refused) + { + if (Guarded(three.Thrown, c)) return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "read back"); + // the wrapped twin does not read its own document back under these options either, not alike: nothing to compare + if (two.Refused) return c.Inconclusive($"read back: the wrapped type does not read its own document back under these options either, not alike: {two}, the value object {three}"); + return $"own JSON {Cut(jv)} did not read back: {three.Thrown!.GetType().Name}: {Cut(three.Thrown.Message)}"; + } + if (three.Value != jv) + { + // under Preserve the serializer numbers references per call: the twin's document read back and written again, alike + if (c.Is("json.ReferenceHandler", "Preserve") && w2.Value is { } tj && two.Value != tj) + return c.Inconclusive($"JSON round trip: written again it differs, for the wrapped type alike ({Cut(tj)} / {Cut(two.Value)})"); + return $"JSON round trip differs: wrote {Cut(jv)}, read and wrote {Cut(three.Value)}"; + } + c.Note(NoteKeys.Compared, "JSON round trip"); + return null; + })); // a changed input must be read or refused with a JsonException (never another type) var input = r["input.json"]; - if (input != "valid") + if (input != "valid" && jv is not null) { var text = JsonParity.InputFor(input, e, o, jv, pos); - Back(text, out var ex2); - // The application's own converter is in charge under own-for-value-object: what it throws is its own. - if (ex2 is not null && r.Is("json.Converters", "own-for-value-object")) r.Note("input:own-vo-converter-threw-" + ex2.GetType().Name); - else if (ex2 is not null && ex2 is not (JsonException or NotSupportedException or InvalidOperationException { })) - return $"input {input}: {ex2.GetType().Name} instead of JsonException: {Cut(ex2.Message)}" - + (ex2.InnerException is { } inner ? $" / inner {inner.GetType().FullName}: {Cut(inner.Message)} @ {Cut(inner.StackTrace?.Split('\n').FirstOrDefault()?.Trim() ?? "")}" : ""); + var i3 = new Sides.Answer("read", null, Thrown(() => JsonSerializer.Deserialize(text, hv.Declared, o))); + // side (1) where the value object refuses with another exception than a JsonException: the twin without TypeKit, reading the same text + Sides.Answer? i1 = i3.Thrown is not null and not JsonException ? new("read", null, Thrown(() => JsonSerializer.Deserialize(text, hp.Declared, one))) : null; + var framework = i1 is not null && Sides.Alike(i1, i3) && !r.Is("json.Converters", "own-for-value-object") ? Sides.Measured(i1, "the twin reading the same text, less TypeKit's converters") : null; + fails.Add(Sides.Check(r, Obligation, Baseline.Refusal, framework, i3, null, null, c => + { + // "Your own converter wins": the application's converter reads the value object, so what it throws is its own. It shares the + // obligation with the round trip above, which runs under it, so it is decided here, not declared in claims.json. + if (c.Is("json.Converters", "own-for-value-object")) return c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), "input " + input); + if (text == jv) return c.Inconclusive($"input {input}: no place for it in {Cut(jv)}"); + if (i3.Thrown is { } ex2 and not JsonException) + { + if (Guarded(ex2, c)) return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "input " + input); + if (i1 is { Refused: true }) return c.Inconclusive($"input {input}: both refuse, not alike: the wrapped type {i1}, the value object {i3}"); + return $"input {input}: {ex2.GetType().Name} instead of JsonException: {Cut(ex2.Message)}" + + (ex2.InnerException is { } inner ? $" / inner {inner.GetType().FullName}: {Cut(inner.Message)} @ {Cut(inner.StackTrace?.Split('\n').FirstOrDefault()?.Trim() ?? "")}" : ""); + } + if (RootRefusal(e, o, input) is { } refusal) return refusal; + c.Note(NoteKeys.Compared, $"input {input}: {(i3.Thrown is null ? "read" : "a JsonException")}"); + return null; + })); } - return null; + return fails; } - static bool PlainFailsToo(Row r, Held hp, JsonSerializerOptions o, string pos, Entry e) + + static Exception? Thrown(Action a) { try { a(); return null; } catch (Exception x) { return x; } } + + /// <summary> + /// The changed input read at the root, where no container stands between the serializer and the generated converter: a + /// JsonException the value object throws there names it and carries no inner exception, "Input it can't read throws a + /// JsonException naming the value object and the underlying type" (Generators README#json). + /// </summary> + static string? RootRefusal(Entry e, JsonSerializerOptions o, string input) { - try { var jp = JsonSerializer.Serialize(hp.Value, hp.Declared, o); JsonSerializer.Deserialize(jp, hp.Declared, o); return false; } catch { return true; } + var vt = Make.Held(e.Vo); + string root; + try { root = JsonSerializer.Serialize(Make.Vo(e, e.Valid()), vt, o); } catch { return null; } // a root the options refuse to write is judged above + var text = JsonParity.InputFor(input, e, o, root, "root"); + if (text == root) return null; + try { JsonSerializer.Deserialize(text, vt, o); return null; } + catch (JsonException x) + { + if (x.InnerException is { } i) return $"input {input} at the root: the JsonException carries an inner {i.GetType().Name}: {Cut(x.Message)}\n text={Cut(text)}"; + if (!x.Message.Contains(vt.Name, StringComparison.Ordinal)) return $"input {input} at the root: the JsonException does not name {vt.Name}: {Cut(x.Message)}\n text={Cut(text)}"; + return null; + } + catch (Exception) { return null; } // another exception type is judged above, at the row's position } - static bool PlainHasInner(string text, Held hp, JsonSerializerOptions o) - { try { JsonSerializer.Deserialize(text, hp.Declared, o); return false; } catch (Exception x) { return x.InnerException is not null; } } // STJ's own inner (the container, a dictionary value) is not the value object's internal static string Cut(string? s) => s is null ? "(null)" : s.Length > 160 ? s[..160] + "..." : s; // ---- L02: a rejection never repeats the input - static string? Quotes(Exception ex, string needle, string how, bool stripPath) + /// <summary>How often a text holds the input: L02's claim is about the text of a refusal, so this is the check, never an excuse.</summary> + static int Occurrences(string text, string needle) { - string Msg(Exception x) => stripPath && x.Message.IndexOf(" Path:", StringComparison.Ordinal) is var i and >= 0 ? x.Message[..i] : x.Message; - if (needle.Length > 0 && Msg(ex).Contains(needle)) return $"{how}: {ex.GetType().Name} quotes the input: {Cut(ex.Message)}"; - if (ex.InnerException is { } inner && needle.Length > 0 && inner.Message.Contains(needle)) return $"{how}: inner {inner.GetType().Name} quotes the input"; - return null; + var n = 0; + for (var i = text.IndexOf(needle, StringComparison.Ordinal); i >= 0; i = text.IndexOf(needle, i + needle.Length, StringComparison.Ordinal)) n++; + return n; } + /// <summary> + /// L02's checks: a parsing entry's refusal of a secret, or of the text Create refuses, neither quotes it (parsing-never-repeats, a + /// FormatException where it is not the type converter's) nor carries an inner exception that does (no-inner-that-quotes); and a JSON + /// read of the secret, or of the row's changed input, where the wrapped value stands in the document refuses without quoting it, but + /// for the dictionary key System.Text.Json puts in the Path (json-never-repeats). Every check runs; returns the first failure. + /// </summary> public static string? NoQuote(Row r, Entry e) { TextOps.Prepare(r, e); var entry = r["text.entry"]; var plain = e.Valid(); var texts = new List<string> { TextOps.Secret }; if (TextOps.ValidatedKind(e) && e.Refused() is { } rf) texts.Add(TextOps.PlainText(e, rf)); - if (TextOps.HasText(e) && entry is "Parse" or "TryParse" or "IParsable" or "ISpanParsable" or "type-converter") + var fails = new List<string?>(); + if (Reads(entry)) { - foreach (var t in texts) + var outs = texts.Select(t => (Text: t, Out: TextOps.Read(e, entry, t))).ToList(); + string? Absent(Check c) => Missing(e, entry) is not null ? NotGenerated(c, e, entry) : $"{entry}: the value object does not read text through it"; + fails.Add(r.Check("parsing-never-repeats", Baseline.Refusal, c => { - var o = TextOps.Read(e, entry, t); - if (o.NotApplicable) { r.Note(entry + "-NA"); continue; } - if (o.Ex is not null) + if (outs.Any(x => x.Out.NotApplicable)) return Absent(c); + var refused = 0; + foreach (var (t, o) in outs) { + if (o.Ex is null) { if (!o.Ok) refused++; continue; } // TryParse answers false: a refusal with nothing to quote + refused++; if (entry == "TryParse") return $"TryParse threw {o.Ex.GetType().Name} on '{t}': {Cut(o.Ex.Message)}"; - if (Quotes(o.Ex, t, entry, false) is { } q) return q; - if (o.Ex.InnerException is not null && !(entry == "type-converter")) return $"{entry}: refusal of '{t}' carries an inner {o.Ex.InnerException.GetType().Name}"; + if (o.Ex.Message.Contains(t)) return $"{entry}: {o.Ex.GetType().Name} quotes the input: {Cut(o.Ex.Message)}"; if (entry != "type-converter" && o.Ex is not FormatException) return $"{entry}: refusal of '{t}' is {o.Ex.GetType().Name}, not FormatException: {Cut(o.Ex.Message)}"; - if (entry == "type-converter" && o.Ex.InnerException is not null) r.Note("converter-wraps-inner"); } - } - } - else if (entry == "TryFrom" && TextOps.ValidatedKind(e) && e.Refused() is { } rf2) - { - var o = TextOps.TryFrom(e, rf2); if (o.Ex is not null) return $"TryFrom(refused) threw {o.Ex.GetType().Name}"; if (o.Ok) return "TryFrom accepted the refused value"; + if (refused == 0) return c.Inconclusive($"{entry}: every text read, none refused: nothing to quote"); + c.Note(NoteKeys.Compared, $"{entry}: {refused} refused without the text"); + return null; + })); + fails.Add(r.Check("no-inner-that-quotes", Baseline.Refusal, c => + { + if (outs.Any(x => x.Out.NotApplicable)) return Absent(c); + var thrown = 0; + foreach (var (t, o) in outs) + { + if (o.Ex is null) continue; + thrown++; + if (o.Ex.InnerException is { } inner && inner.Message.Contains(t)) return $"{entry}: inner {inner.GetType().Name} quotes the input"; + if (o.Ex.InnerException is not null && entry != "type-converter") return $"{entry}: refusal of '{t}' carries an inner {o.Ex.InnerException.GetType().Name}"; + if (entry == "type-converter" && o.Ex.InnerException is not null) c.Note(NoteKeys.ConverterWrapsInner); + } + if (thrown == 0) return c.Inconclusive($"{entry}: no text refused with an exception: no inner to look at"); + c.Note(NoteKeys.Compared, $"{entry}: {thrown} refusals without a quoting inner"); + return null; + })); } // JSON half var opts = JsonRow.Apply(r, e).Options!; + var one = Sides.WithoutTypeKit(opts); var pos = r["shape.position"]; var con = r.Get("shape.containing") ?? "class"; - var hp = Pos.Build(pos, con, e, false, plain); var hv = Pos.Build(pos, con, e, true, Make.Vo(e, plain)); - string jp; string sample; - try { jp = JsonSerializer.Serialize(hv.Value, hv.Declared, opts); sample = JsonSerializer.Serialize(Make.Unwrap(e, Make.Vo(e, plain)), e.Plain, opts); } catch { r.Note("ser-refused"); return null; } - if (r.Get("json.Converters") is "own-for-wrapped" or "own-for-value-object") return null; // the application's own converter owns the message - var at = jp.IndexOf(sample, StringComparison.Ordinal); - var variants = new List<(string Text, string Needle)>(); - if (at >= 0) variants.Add((jp[..at] + "\"" + TextOps.Secret + "\"" + jp[(at + sample.Length)..], TextOps.Secret)); + var (hp, hv) = Sides.Twins(pos, con, e, plain, Make.Vo(e, plain)); var input = r["input.json"]; - if (input != "valid" && at >= 0) + fails.Add(r.Check("json-never-repeats", Baseline.Refusal, c => { - var text = JsonParity.InputFor(input, e, opts, jp, pos); - var x = text.Length - jp.Length + sample.Length; - if (x > 0 && text != jp) { var lit = text.Substring(at, x).Trim('"'); if (lit.Length >= 6 && lit.Length < 60 && lit.All(c => !char.IsPunctuation(c) || c is '-' or ':' or '.')) variants.Add((text, lit)); } - } - foreach (var (text, needle) in variants) - { - try { JsonSerializer.Deserialize(text, hv.Declared, opts); } - catch (Exception ex) + // the application's own converter owns the message; claims.json declares it + if (c.Get("json.Converters") is "own-for-wrapped" or "own-for-value-object") return c.Inapplicable(Cite.NotPromised("the-applications-own-converter")); + string? doc = null, sample; + try { doc = JsonSerializer.Serialize(hv.Value, hv.Declared, opts); } + catch (Exception vx) { - if (ex is JsonException je && Quotes(je, needle, "JSON read", true) is { } q) return q + (pos == "dictionary-key" ? " [dictionary-key]" : ""); - if (ex.InnerException is not null && ex is JsonException && !(ex.InnerException is JsonException) && !PlainHasInner(text, hp, opts)) return $"JSON read: refusal carries inner {ex.InnerException.GetType().Name}: {Cut(ex.InnerException.Message)}"; + // the value object's document is the wrapped type's (L01): where it cannot be written, the twin's places the input + try { doc = JsonSerializer.Serialize(hp.Value, hp.Declared, opts); } + catch (Exception px) { return c.Inconclusive($"the options refuse the shape, for the wrapped type too: {vx.GetType().Name} / {px.GetType().Name}"); } } - } - return null; + try { sample = JsonSerializer.Serialize(Make.Unwrap(e, Make.Vo(e, plain)), e.Plain, opts); } + catch (Exception sx) { return c.Inconclusive("the wrapped value alone is not written under these options: " + sx.GetType().Name); } + var at = doc.IndexOf(sample, StringComparison.Ordinal); + var variants = new List<(string Text, string Needle)>(); + if (at >= 0) variants.Add((doc[..at] + "\"" + TextOps.Secret + "\"" + doc[(at + sample.Length)..], TextOps.Secret)); + else if (pos == "dictionary-key" && Probes.Json.Jx.Tokens(doc).FirstOrDefault(t => t.Type == JsonTokenType.PropertyName && t.Text == sample.Trim('"')) is { } key) + // a key is a property name, the wrapped value's text without its quotes: the secret goes there + variants.Add((Probes.Json.Jx.Rewrite(doc, [key], _ => "\"" + TextOps.Secret + "\""), TextOps.Secret)); + if (input != "valid" && at >= 0) + { + var text = JsonParity.InputFor(input, e, opts, doc, pos); + var x = text.Length - doc.Length + sample.Length; + if (x > 0 && text != doc) { var lit = text.Substring(at, x).Trim('"'); if (lit.Length >= 6 && lit.Length < 60 && lit.All(ch => !char.IsPunctuation(ch) || ch is '-' or ':' or '.')) variants.Add((text, lit)); } + } + if (variants.Count == 0) return c.Inconclusive($"the wrapped value has no place in {Cut(doc)}: the options leave it out"); + var rejected = 0; var inPaths = new List<string>(); + string? Read(string text, string needle) + { + try { JsonSerializer.Deserialize(text, hv.Declared, opts); } + catch (Exception ex) + { + rejected++; + // System.Text.Json puts a rejected dictionary key in the JsonException's Path (not-promised a-rejected-dictionary-key-in-the-path): + // the Path is read as the exception's property, and the message must hold the input no more often than its Path does + var inPath = ex is JsonException { Path: { } path } ? Occurrences(path, needle) : 0; + if (Occurrences(ex.Message, needle) > inPath) return $"JSON read: {ex.GetType().Name} quotes the input: {Cut(ex.Message)}" + (pos == "dictionary-key" ? " [dictionary-key]" : ""); + if (ex.InnerException is { } inner && inner.Message.Contains(needle)) return $"JSON read: inner {inner.GetType().Name} quotes the input"; + // side (1): the twin without TypeKit's converters, reading the same text, shows the serializer's own inner and Path + var twin = Thrown(() => JsonSerializer.Deserialize(text, hp.Declared, one)); + if (ex.InnerException is not null && ex is JsonException && !(ex.InnerException is JsonException) && twin?.InnerException is null) return $"JSON read: refusal carries inner {ex.InnerException.GetType().Name}: {Cut(ex.InnerException.Message)}"; + if (inPath > 0) inPaths.Add(twin is JsonException { Path: { } tp } && Occurrences(tp, needle) > 0 ? "the key in the Path, the wrapped type's alike" : "the key in the Path, as not-promised a-rejected-dictionary-key-in-the-path says"); + } + return null; + } + foreach (var (text, needle) in variants) + if (Read(text, needle) is { } quoted) return quoted; + // A wrapped type that reads every string (a string, a Uri) refuses none of these: what a client sends that it cannot read is a value + // of another kind, and an array holding the secret where the wrapped value stands is one (a key is a property name, never an array). + if (rejected == 0 && at >= 0 && pos != "dictionary-key" + && Read(doc[..at] + "[\"" + TextOps.Secret + "\"]" + doc[(at + sample.Length)..], TextOps.Secret) is { } quotedInArray) return quotedInArray; + if (rejected == 0) return c.Inconclusive("every JSON input read, none refused: nothing to quote"); + c.Note(NoteKeys.Compared, $"JSON read: {rejected} refused without the input" + (inPaths.Count > 0 ? ", " + string.Join(", ", inPaths.Distinct()) : "")); + return null; + })); + return fails.FirstOrDefault(f => f is not null); } // ---- L03: one set of rules: every entry runs Create and reports its fault + /// <summary> + /// L03's checks: TryFrom (tryfrom-runs-create) and the parsing entries (parsing-runs-create) refuse the value Create refuses and accept + /// a valid one, the JSON converter reads back what it writes and refuses what Create refuses (json-runs-create), and every refusal, + /// FromKnownGood's, the FormatException and the JsonException, names the fault (refusals-name-the-fault). A value object without Create + /// is held to accepting the valid value. Every check runs; returns the first failure. + /// </summary> public static string? OneRule(Row r, Entry e) { TextOps.Prepare(r, e); var entry = r["text.entry"]; var plain = e.Valid(); - if (!TextOps.ValidatedKind(e) || e.Refused() is not { } refused) + var fails = new List<string?>(); + var validated = TextOps.ValidatedKind(e) && e.Refused() is not null; + if (!validated) { // no Create: the valid value passes every entry - if (entry == "TryFrom") { var t = TextOps.TryFrom(e, plain); if (!t.NotApplicable && (!t.Ok || t.Ex is not null)) return "TryFrom(valid) refused on a value object with no rule"; } - else if (TextOps.HasText(e) && entry is "Parse" or "TryParse" or "IParsable" or "ISpanParsable" or "type-converter") - { var o = TextOps.Read(e, entry, TextOps.PlainText(e, plain)); if (!o.NotApplicable && (o.Ex is not null || !o.Ok)) return $"{entry}: valid text refused: {o.Ex?.GetType().Name} {Cut(o.Ex?.Message)}"; } - return JsonOneRule(r, e, false); + if (entry == "TryFrom") + fails.Add(r.Check("tryfrom-runs-create", Baseline.RoundTrip, c => + { + var t = TextOps.TryFrom(e, plain); + if (t.NotApplicable) return Missing(e, entry) is not null ? NotGenerated(c, e, entry) : "TryFrom is not generated for the value object"; + if (!t.Ok || t.Ex is not null) return "TryFrom(valid) refused on a value object with no rule"; + c.Note(NoteKeys.Compared, "TryFrom(valid), no rule"); return null; + })); + else if (Reads(entry)) + fails.Add(r.Check("parsing-runs-create", Baseline.RoundTrip, c => + { + if (Missing(e, entry) is not null) return NotGenerated(c, e, entry); + var o = TextOps.Read(e, entry, TextOps.PlainText(e, plain)); + if (o.NotApplicable) return $"{entry}: the value object does not read text through it"; + if (o.Ex is not null || !o.Ok) return $"{entry}: valid text refused: {o.Ex?.GetType().Name} {Cut(o.Ex?.Message)}"; + c.Note(NoteKeys.Compared, $"{entry}(valid), no rule"); return null; + })); + fails.AddRange(JsonOneRule(r, e, false)); + return fails.FirstOrDefault(f => f is not null); } - var rtext = TextOps.PlainText(e, refused); + var refusedValue = e.Refused()!; + var rtext = TextOps.PlainText(e, refusedValue); if (entry == "TryFrom") { - var t = TextOps.TryFrom(e, refused); if (t.Ex is not null) return "TryFrom threw " + t.Ex.GetType().Name; if (t.Ok) return "TryFrom accepted the value Create refuses"; - var kg = Unwrap(() => e.Vo.GetMethod("FromKnownGood")!.Invoke(null, [refused, null])); - if (kg is not { } kex) return "FromKnownGood accepted the value Create refuses"; if (!kex.Message.Contains("Refused")) return "FromKnownGood does not name the fault: " + Cut(kex.Message); + fails.Add(r.Check("tryfrom-runs-create", Baseline.Refusal, c => + { + var t = TextOps.TryFrom(e, refusedValue); if (t.Ex is not null) return "TryFrom threw " + t.Ex.GetType().Name; if (t.Ok) return "TryFrom accepted the value Create refuses"; + c.Note(NoteKeys.Compared, "TryFrom refused"); return null; + })); + fails.Add(r.Check("refusals-name-the-fault", Baseline.Refusal, c => + { + var kg = Unwrap(() => e.Vo.GetMethod("FromKnownGood")!.Invoke(null, [refusedValue, null])); + if (kg is not { } kex) return "FromKnownGood accepted the value Create refuses"; if (!kex.Message.Contains("Refused")) return "FromKnownGood does not name the fault: " + Cut(kex.Message); + c.Note(NoteKeys.Compared, "FromKnownGood names the fault"); return null; + })); } - else if (TextOps.HasText(e) && entry is "Parse" or "TryParse" or "IParsable" or "ISpanParsable" or "type-converter") + else if (Reads(entry)) { - var o = TextOps.Read(e, entry, rtext); - if (!o.NotApplicable) + var o = Missing(e, entry) is null ? TextOps.Read(e, entry, rtext) : Out.NA; + fails.Add(r.Check("parsing-runs-create", Baseline.Refusal, c => { + if (Missing(e, entry) is not null) return NotGenerated(c, e, entry); + if (o.NotApplicable) return $"{entry}: the value object does not read text through it"; if (entry == "TryParse") { if (o.Ex is not null) return "TryParse threw " + o.Ex.GetType().Name; if (o.Ok) return "TryParse accepted text Create refuses"; } - else + else if (o.Ex is null) return $"{entry} accepted '{rtext}' that Create refuses"; + c.Note(NoteKeys.Compared, $"{entry} refused"); return null; + })); + if (entry != "TryParse") + fails.Add(r.Check("refusals-name-the-fault", Baseline.Refusal, c => { - if (o.Ex is null) return $"{entry} accepted '{rtext}' that Create refuses"; + if (Missing(e, entry) is not null) return NotGenerated(c, e, entry); + if (o.NotApplicable || o.Ex is null) return c.Inconclusive($"{entry}: no refusal to name the fault, judged by parsing-runs-create"); if (!o.Ex.Message.Contains("Refused")) return $"{entry}: refusal does not name the fault ({o.Ex.GetType().Name}): {Cut(o.Ex.Message)}"; if (entry != "type-converter" && o.Ex is not FormatException) return $"{entry}: refusal is {o.Ex.GetType().Name}, not FormatException"; - } - } + c.Note(NoteKeys.Compared, $"{entry}: the {o.Ex.GetType().Name} names the fault"); return null; + })); } - return JsonOneRule(r, e, true); + fails.AddRange(JsonOneRule(r, e, true)); + return fails.FirstOrDefault(f => f is not null); } static Exception? Unwrap(Func<object?> f) { try { f(); return null; } catch (TargetInvocationException t) { return t.InnerException; } catch (Exception ex) { return ex; } } - static string? JsonOneRule(Row r, Entry e, bool validated) + /// <summary> + /// The JSON half of "one set of rules", on every row under the row's options: the value object reads back what it writes, and a + /// validated one refuses the value Create refuses, where the wrapped type reads it, with a JsonException naming the fault. The two + /// named paths are checked as what they are: an application's own converter for the value object takes precedence (it reads its + /// own spelling back), and StoredJsonConverterFactory reads the refused value without validation (README, "Only two paths skip + /// validation"). Three-sided (Sides): where the value object (3) refuses, the twin without TypeKit's converters (1) refusing alike is + /// the framework's refusal, the check an expected refusal against it, and so is a converter's value under a depth limit (KB-040). + /// </summary> + static List<string?> JsonOneRule(Row r, Entry e, bool validated) { - if (!validated || r["input.json"] != "refused-by-create") return null; - if (r.Get("json.Converters") is "own-for-wrapped" or "own-for-value-object" or "StoredJsonConverterFactory") return null; // named: own converter / stored reads skip Create + const string Obligation = "json-runs-create"; var o = JsonRow.Apply(r, e).Options!; + var one = Sides.WithoutTypeKit(o); var pos = r["shape.position"]; var con = r.Get("shape.containing") ?? "class"; var plain = e.Valid(); - var hp = Pos.Build(pos, con, e, false, plain); var hv = Pos.Build(pos, con, e, true, Make.Vo(e, plain)); - string jp; try { jp = JsonSerializer.Serialize(hp.Value, hp.Declared, o); } catch { return null; } - var text = JsonParity.InputFor("refused-by-create", e, o, jp, pos); - if (text == jp) return null; - try { JsonSerializer.Deserialize(text, hp.Declared, o); } catch { return null; } // the wrapped type refuses this itself - try { JsonSerializer.Deserialize(text, hv.Declared, o); return $"JSON read accepted the value Create refuses: {Cut(text)}"; } - catch (JsonException ex) + var (hp, hv) = Sides.Twins(pos, con, e, plain, Make.Vo(e, plain)); + bool Guarded(Exception x) => r.Is("json.ReferenceHandler", "Preserve") && e.Wrapped == "own-record-class" && x is NotSupportedException; + var w2 = Sides.Write(hp, o); var w3 = Sides.Write(hv, o); + var (jp, xp) = (w2.Value, w2.Thrown); var (jv, xv) = (w3.Value, w3.Thrown); + var fails = new List<string?>(); + // 1. the valid value reads back as written: the write, then the value object's own document read back; side (1) where (3) refuses + Sides.Answer three = w3, two = w2, first = Sides.Write(hp, one); + if (!w3.Refused) { - if (ex.Message.Contains("Refused") || ex.Message.Contains("maximum allowed depth")) return null; - return $"JsonException does not name the fault: {Cut(ex.Message)}"; + three = Sides.Read(jv!, hv, o); + two = jp is null ? w2 : Sides.Read(jp, hp, o); + first = first.Value is { } j1 ? Sides.Read(j1, hp, one) : first; } - catch (Exception ex) { return $"JSON refusal is {ex.GetType().Name}, not JsonException: {Cut(ex.Message)}"; } + var framework = three.Refused ? Sides.Framework(r, e, hp, one, first, three) : null; + // Why the value object's own document has nothing to compare under the row's options, or null where it reads back: the refused + // value is then not told apart from a document the options do not let it read, and step 2 has nothing to check either. + string? nothing = framework is not null && Sides.Alike(framework.Answer, three) ? "the framework refuses it: " + three : null; + fails.Add(Sides.Check(r, Obligation, Baseline.RoundTrip, framework, three, three.Refused || two.Refused ? first : null, three.Refused || two.Refused ? two : null, c => + { + if (xp is not null && xv is not null) { nothing = "write"; return c.Inconclusive($"JSON write: both refuse, not alike: the wrapped type {w2}, the value object {w3}"); } + if (xp is not null) { nothing = "the wrapped type is not written"; return c.Inconclusive("JSON write: the wrapped type is not written under these options, the value object is: JSON parity (L01) judges it"); } + if (xv is not null) + { + if (Guarded(xv)) { nothing = "the Preserve guard refuses the value object"; return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "write"); } + return $"JSON: the value object is not written where the wrapped type is ({xv.GetType().Name}: {Cut(xv.Message)})"; + } + if (three.Refused) + { + if (two.Refused) { nothing = "read back"; return c.Inconclusive($"JSON read back: both refuse, not alike: the wrapped type {two}, the value object {three}"); } + return $"JSON: the value object does not read back what it writes ({three.Thrown!.GetType().Name}: {Cut(three.Thrown.Message)}): {Cut(jv)}"; + } + if (three.Value != jv) return $"JSON: read back as {Cut(three.Value)}, written as {Cut(jv)}"; + if (c.Is("json.Converters", "own-for-value-object")) c.Note(NoteKeys.OwnVo, "the application's converter takes precedence and reads its own spelling"); + else c.Note(NoteKeys.Compared, "JSON read back"); + return null; + })); + if (!validated || jp is null) return fails; + // 2. the value Create refuses, placed where the wrapped value stands in the wrapped type's document + var text = JsonParity.InputFor("refused-by-create", e, o, jp, pos); + var xr = Thrown(() => JsonSerializer.Deserialize(text, hv.Declared, o)); + var r1 = new Sides.Answer("read", null, Thrown(() => JsonSerializer.Deserialize(text, hp.Declared, one))); + var twinReads = Thrown(() => JsonSerializer.Deserialize(text, hp.Declared, o)) is null; // the wrapped type reads the refused value's document: only then is a refusal Create's + var r3 = new Sides.Answer("read", null, xr); + var stored = r.Is("json.Converters", "StoredJsonConverterFactory"); + var ownVo = r.Is("json.Converters", "own-for-value-object"); + // the twin without TypeKit refuses the refused value's document alike: the serializer's refusal, before Create + var refusedAlike = !twinReads && !ownVo && Sides.Alike(r1, r3) ? Sides.Measured(r1, "the twin reading the same text, less TypeKit's converters") : null; + fails.Add(Sides.Check(r, Obligation, stored ? Baseline.Docs("README.md#what-typekit-guarantees") : Baseline.Refusal, refusedAlike, r3, null, null, c => + { + // "Your own converter wins": the generated converter does not run, so no Create; the round trip above runs under it, which + // shares the obligation, so it is decided here, not declared in claims.json + if (ownVo) return c.Inapplicable(Cite.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says"), "the refused value"); + if (xr is not null && Guarded(xr)) return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "the refused value"); + if (nothing is not null) return c.Inconclusive($"the refused value: the value object's own document has nothing to compare under these options ({nothing})"); + if (text == jp) return c.Inconclusive($"the refused value has no place in {Cut(jp)} at {pos}"); + if (!twinReads) + { + if (xr is null) return $"JSON read accepted text the wrapped type refuses: {Cut(text)}"; + return c.Inconclusive($"the refused value's JSON: both refuse, not alike: the wrapped type {r1}, the value object {r3}"); + } + if (stored) + { + // "Only two paths skip validation, and both are named": StoredJsonConverterFactory reads the stored value without Create + if (xr is not null) return $"StoredJsonConverterFactory refused a value Create refuses ({xr.GetType().Name}): it reads stored JSON without validation"; + c.Note(NoteKeys.StoredFactoryReadUnvalidated); return null; // not-promised stored-json-skips-validation + } + if (xr is null) return $"JSON read accepted the value Create refuses: {Cut(text)}"; + if (xr is not JsonException) + { + if (Guarded(xr)) return c.Refused(Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json"), "the refused value"); + return $"JSON refusal is {xr.GetType().Name}, not JsonException: {Cut(xr.Message)}"; + } + c.Note(NoteKeys.Compared, "the refused value refused"); + return null; + })); + if (xr is JsonException && twinReads && !stored && nothing is null && !ownVo && text != jp) + fails.Add(r.Check("refusals-name-the-fault", Baseline.Refusal, c => + { + if (!xr.Message.Contains("Refused")) return $"JsonException does not name the fault: {Cut(xr.Message)}\n text={Cut(text)} (the wrapped type reads it; its own document {Cut(jp)})"; + c.Note(NoteKeys.Compared, "the JsonException names the fault"); + return null; + })); + return fails; } } diff --git a/tools/probe/corpus/probes/C01DecimalSpellingProbe.cs b/tools/probe/corpus/probes/C01DecimalSpellingProbe.cs new file mode 100644 index 00000000..d34c3fee --- /dev/null +++ b/tools/probe/corpus/probes/C01DecimalSpellingProbe.cs @@ -0,0 +1,12 @@ +// Claim C01-decimal-spelling (docs/json.md#decimals-in-one-spelling): both decimal converters write one spelling per value, the smallest +// scale, as a number or a string, for every decimal: nullable ones, dictionary keys and every value object over a decimal; both read a +// number or a string holding one and keep its scale. The check is Probes.Json.DecimalSpelling. +using Probes.Json; +using Probes.Shapes; +namespace Probes; +public sealed class C01DecimalSpellingProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C01DecimalSpellingProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(DecimalSpelling.Check(r))); +} diff --git a/tools/probe/corpus/probes/C02OptionalTextProbe.cs b/tools/probe/corpus/probes/C02OptionalTextProbe.cs new file mode 100644 index 00000000..30419505 --- /dev/null +++ b/tools/probe/corpus/probes/C02OptionalTextProbe.cs @@ -0,0 +1,75 @@ +// Claim C02-optional-text (docs/text.md#required-and-optional): "For a required field, Blank is an error. For an optional one, a blank means 'no value', and AsOptionalText() and AsOptionalIdentifier() return a Result<Option<string>, TextFault>. Hidden is an error in both cases. A null string is Blank." +// Oracle: claim + differential (AsOptionalX is AsX with Blank turned into None). Expected: blank input (null, empty, spaces, tab/newline, no-break space) -> required Error(Blank), optional Success(None); +// ZWSP-only or word+ZWSP -> Error(Hidden) in both; a word -> Some/Success of the word. In globalization-invariant mode AsText/AsOptionalText throw PlatformNotSupportedException (documented, a refusal). +#pragma warning disable xUnit1051 +using System.ComponentModel; +using System.Globalization; +using CodoMetis.TypeKit; +using Probes.Shapes; +namespace Probes; +public sealed class C02OptionalTextProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C02OptionalTextProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(Check(r))); + + static string? Input(string k) => k switch + { + "null" => null, "empty" => "", "spaces" => " ", "tab-newline" => "\t\r\n ", "word" => "word", "word+ZWSP" => "word\u200B", + "nbsp-only" => "\u00A0\u00A0", "ZWSP-only" => "\u200B", _ => throw new ArgumentException(k), + }; + + static string? Check(Row r) + { + var c = r["text.culture"]; + try { CultureInfo.CurrentCulture = c switch { "de-DE" => new("de-DE"), "tr-TR" => new("tr-TR"), "ar-SA" => new("ar-SA"), _ => CultureInfo.InvariantCulture }; } + catch (CultureNotFoundException) { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; r.Substituted("text.culture", "globalization-invariant", "culture unavailable in this process"); } + bool gi; try { gi = new CultureInfo("de-DE").NumberFormat.NumberDecimalSeparator == "."; } catch (CultureNotFoundException) { gi = true; } + if (c == "globalization-invariant" && !gi) r.Substituted("text.culture", "invariant", "the process is not in globalization-invariant mode"); + if (c != "globalization-invariant" && gi) r.Substituted("text.culture", "globalization-invariant", "the process is in globalization-invariant mode"); + if (r["process.converter-lookup"] == "wrapped-type-converter-looked-up-first") _ = TypeDescriptor.GetConverter(typeof(string)); + + var entry = r["entry"]; var kind = r["input"]; var s = Input(kind); + var expected = kind switch { "word" => "ok", "word+ZWSP" or "ZWSP-only" => "Hidden", _ => "Blank" }; + // AsText/AsOptionalText refuse in globalization-invariant mode (not-promised as-text-refused-in-globalization-invariant-mode): the row runs on the identifier counterpart, and the refusal itself is checked without credit. + if (gi && entry is "AsText" or "AsOptionalText") // (a normal process runs these rows as they are, under the invariant culture) + { + var refused = false; + try { _ = entry == "AsText" ? s.AsText() : (object)s.AsOptionalText(); } catch (PlatformNotSupportedException) { refused = true; } + if (!refused) return $"{entry} did not throw in globalization-invariant mode (documented PlatformNotSupportedException)"; + entry = entry == "AsText" ? "AsIdentifier" : "AsOptionalIdentifier"; + r.Substituted("entry", entry, "AsText/AsOptionalText refuse in globalization-invariant mode"); + } + var optional = entry.StartsWith("AsOptional"); + var text = entry.EndsWith("Text"); + string got, want; + if (optional) + { + var res = text ? s.AsOptionalText() : s.AsOptionalIdentifier(); + got = res.Match(o => o.Match(v => "Some:" + v, () => "None"), f => "Error:" + f); + want = expected switch { "ok" => "Some:word", "Blank" => "None", _ => "Error:Hidden" }; + } + else + { + var res = text ? s.AsText() : s.AsIdentifier(); + got = res.Match(v => "ok:" + v, f => "Error:" + f); + want = expected switch { "ok" => "ok:word", _ => "Error:" + expected }; + } + // The obligation the input selects: a hidden character is an error in both; a blank is an error for a required field and no value + // for an optional one, and a null is a blank too. A word is checked as the blank obligations' control: it is no blank, in either. + var obligations = new List<string> { expected == "Hidden" ? "hidden-is-an-error-in-both" : optional ? "optional-blank-is-no-value" : "required-blank-is-an-error" }; + if (kind == "null") obligations.Add("null-is-blank"); + string? fail = null; + foreach (var obligation in obligations) + { + var f = r.Check(obligation, Baseline.Docs("docs/text.md#required-and-optional"), c => + { + if (got != want) return $"{entry}({kind}) = {got}; expected {want}"; + c.Note(NoteKeys.Compared, $"{entry}({kind}) = {got}"); + return null; + }); + fail ??= f; + } + return fail; + } +} diff --git a/tools/probe/corpus/probes/C04WithoutFaultsProbe.cs b/tools/probe/corpus/probes/C04WithoutFaultsProbe.cs new file mode 100644 index 00000000..d050b5b1 --- /dev/null +++ b/tools/probe/corpus/probes/C04WithoutFaultsProbe.cs @@ -0,0 +1,68 @@ +// Claim C04-without-faults (AspNetCore README#leaving-out-what-a-route-cannot-answer): "The operation then lists no status and no problem +// type for a member left out. It lists no response for a status that no remaining member is mapped to." "Reporting a member left out +// anyway throws an InvalidOperationException naming the endpoint and the member". "If a group gives NotFound a status and one endpoint +// inside leaves NotFound out, that endpoint drops it, and the group's other endpoints keep the status." "These stop the host from starting +// ... a status that every endpoint it's set on leaves out (... on a group whose endpoints all leave the member out)". +// scope: who leaves HFault.Down (503, mapped to nothing else) out: one endpoint or action beside a sibling that keeps it; a route group or +// MapControllers()'s builder (README: "A route group or a controller says it for its endpoints"; "Any convention builder that covers several +// endpoints works the same"); a controller class beside a sibling controller. combined: a wider scope also gives Down 507 (a group, the +// controller class, or MapControllers()'s builder). With scope=group the 507's whole scope leaves Down out, so the host must not start. +// Oracle: FaultClaim (README rules over the declarations); the other members (Missing, Clash) answer as mapped. A check per obligation: +// doc-omits-left-out: every endpoint's operation (statuses, order, problem schema, problem types); +// reporting-left-out-throws: reporting the member left out throws naming the endpoint and the member, against the refusal; and, outside +// the rows of narrows-wider-status, what the other members answer, which the README sets against "a status the document doesn't list"; +// narrows-wider-status (scope endpoint or controller, combined): what every member but the one left out answers, the kept 507 among +// them, and which statuses each operation lists; +// left-out-everywhere-stops-start (scope group, combined): the host's refusal, naming HFault.Down and 507, the row's one check. +using Probes.Faults; +using Probes.Shapes; +namespace Probes; +public sealed class C04WithoutFaultsProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C04WithoutFaultsProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run("C04", idx, r => FaultClaim.Run(r, Spec(r)), probe); + + static readonly Decl Out = new("HFault", "Down", 0), Status = new("HFault", "Down", 507); + public static Spec Spec(Probes.Shapes.Row r) + { + var combined = r["combined"] == "with-WithFaultStatus-on-group"; + var narrows = combined && r["scope"] is "endpoint" or "controller"; + var s = new Spec + { + Errors = [FxErr.E("Missing"), FxErr.E("Clash"), FxErr.E("Down")], + Against = Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#leaving-out-what-a-route-cannot-answer"), + Refusals = ["reporting-left-out-throws", "left-out-everywhere-stops-start"], + Of = (aspect, _) => aspect switch + { + Aspect.Start => ["left-out-everywhere-stops-start"], + Aspect.LeftOut => ["reporting-left-out-throws"], + Aspect.DocListed when narrows => ["doc-omits-left-out", "narrows-wider-status"], + Aspect.DocListed or Aspect.DocOrder or Aspect.DocDescribes or Aspect.DocTypes => ["doc-omits-left-out"], + _ => [narrows ? "narrows-wider-status" : "reporting-left-out-throws"], + }, + }; + switch (r["scope"]) + { + case "endpoint": + s.Minimal = combined + ? new MGrp("/fx", [], [new MGrp("/ep", [Status], [], [new MEp("wo", [Out]), new MEp("keep", [])])], []) + : new MGrp("/fx", [], [], [new MEp("ep/wo", [Out]), new MEp("ep/keep", [])]); + s.Controllers = [combined ? "C04EpS" : "C04Ep"]; + break; + case "group": + s.Minimal = combined + ? new MGrp("/fx", [], [new MGrp("/gr", [Status], [new MGrp("/in", [Out], [], [new MEp("a", []), new MEp("b", [])])], [])], []) + : new MGrp("/fx", [], [new MGrp("/gr", [Out], [], [new MEp("a", []), new MEp("b", [])])], [new MEp("keep", [])]); + s.Controllers = ["C04Gr"]; + s.Builder = combined ? [Status, Out] : [Out]; + if (combined) s.Refused = ["HFault.Down", "507"]; + break; + default: // controller: MVC only (scope=controller x http.host minimal, route-group excluded in claims.json) + s.Controllers = ["C04CtWo", "C04CtKeep"]; + s.Builder = combined ? [Status] : []; + break; + } + return s; + } +} diff --git a/tools/probe/corpus/probes/C06FaultKindsProbe.cs b/tools/probe/corpus/probes/C06FaultKindsProbe.cs new file mode 100644 index 00000000..6b7fea30 --- /dev/null +++ b/tools/probe/corpus/probes/C06FaultKindsProbe.cs @@ -0,0 +1,53 @@ +// Claim C06-fault-kinds (AspNetCore README#a-closed-set-as-the-fault-type): "builder.Services.AddFaultResponses<AccountFault>(); // every member +// by its kind"; "The kind gives the status": PreconditionFailed 412, Malformed 400, Invalid 422, Unavailable 503; "A member without one +// [a description] has no detail."; "The description becomes the problem's detail"; "A member must have a kind. A member whose kind is +// default, which is no kind, fails this call." The member of the row's kind (HFault.Stale, Garbled, Bad, Down, none with a description) and +// HFault.Missing (described "Missing.") are reported on a minimal endpoint and an MVC action; the operation lists its kind's status among +// every member's, with no problem type (the mapping by kind gives none). The fault sets are mapped per set (AddFaultResponses<HFault>(), +// <GFault>(), <KFault>()). Oracle: FaultClaim; and AddFaultResponses<FxNoKind>() refused, naming its member of no kind. A check per obligation: +// kind-gives-status: each member's status, fault and type, and the operation (statuses, order, problem schema, problem types); +// description-as-detail: HFault.Missing's detail is its description; no-description-no-detail: the row's member has none; +// member-must-have-kind: AddFaultResponses<FxNoKind>() refused, against the refusal, in every row. +using CodoMetis.TypeKit.AspNetCore; +using Microsoft.Extensions.DependencyInjection; +using Probes.Faults; +using Probes.Shapes; +namespace Probes; +public sealed class C06FaultKindsProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C06FaultKindsProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run("C06", idx, Check, probe); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var noKind = r.Check("member-must-have-kind", Baseline.Refusal, _ => NoKindRefused()); + var answered = await FaultClaim.Run(r, Spec(r)); + return noKind ?? answered; + } + + /// <summary>"A member must have a kind. A member whose kind is default, which is no kind, fails this call."</summary> + static string? NoKindRefused() + { + try { new ServiceCollection().AddFaultResponses<FxNoKind>(); } + catch (ArgumentException ex) + { + if (!ex.Message.Contains("FxNoKind.Blank", StringComparison.Ordinal)) return $"AddFaultResponses<FxNoKind>() refused, but not with an ArgumentException naming FxNoKind.Blank: {ex.GetType().Name}: {ex.Message}"; + return null; + } + catch (Exception ex) { return $"AddFaultResponses<FxNoKind>() refused, but not with an ArgumentException naming FxNoKind.Blank: {ex.GetType().Name}: {ex.Message}"; } + return "AddFaultResponses<FxNoKind>() took FxNoKind.Blank, a member of no kind"; + } + + public static Spec Spec(Probes.Shapes.Row r) => new() + { + Minimal = new MGrp("/c6", [], [], [new MEp("k", [])]), + Controllers = ["C06K"], + Typed = false, + Against = Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#a-closed-set-as-the-fault-type"), + Of = (aspect, m) => aspect == Aspect.Detail + ? [m?.Q == "HFault.Missing" ? "description-as-detail" : "no-description-no-detail"] + : ["kind-gives-status"], + Errors = [FxErr.E(r["kind"] switch { "PreconditionFailed" => "Stale", "Malformed" => "Garbled", "Invalid" => "Bad", _ => "Down" }), FxErr.E("Missing")], + }; +} diff --git a/tools/probe/corpus/probes/C07BlanketMappingProbe.cs b/tools/probe/corpus/probes/C07BlanketMappingProbe.cs new file mode 100644 index 00000000..2b1fc3fd --- /dev/null +++ b/tools/probe/corpus/probes/C07BlanketMappingProbe.cs @@ -0,0 +1,84 @@ +// Claim C07-fault-responses-blanket (AspNetCore README#every-fault-set-at-once): "builder.Services.AddFaultResponses(); // every fault set an +// endpoint answers with, by its kind"; "The mapping can be your own. It reads each member's kind and description through IFault"; "A set +// mapped on its own keeps its mapping."; "An enum still needs a mapping of its own ... the host doesn't start while an endpoint answers with +// one that's unmapped."; "A member with no kind stops the start, and so does one the mapping throws for or gives a status that isn't an +// error. That's the same as AddFaultResponses<TSet>() failing." +// mapping: AddFaultResponses() by kind, or AddFaultResponses(fault => FaultResponse.For(fault.Kind).WithType(...)) (README's example), which +// throws for FxThrows and answers FxStatus with 302. set: what the row's endpoints answer with beside HFault (and GFault/KFault, by http.response): +// nothing more (fault-set); HFault mapped on its own (own-mapped-set: own statuses and types); or one more endpoint answering an unmapped +// enum, a set with a member of no kind, or a set the mapping refuses. Under by-kind no mapping for every set throws or gives a non-error +// status, so there the refusing set's own AddFaultResponses<TSet>() does, which the README names as the same failure. +// Oracle: FaultClaim (wire and document per member, as mapped), and the refusals as the README states them. BaseProbe runs the HTTP +// payload calibration over the same rows. A check per obligation: by-kind or own-mapping (the row's mapping) for what every member answers +// and every operation lists; own-set-keeps-mapping (set own-mapped-set) for HFault's answers, mapped on their own, and the operations; +// enum-needs-own-mapping, member-without-kind-stops-start and mapping-refusal-stops-start (their sets) for the host's refusal, the row's +// one check, against the refusal. +using Probes.Faults; +using Probes.Shapes; +using CodoMetis.TypeKit.AspNetCore; +using Microsoft.Extensions.DependencyInjection; +namespace Probes; +public sealed class C07BlanketMappingProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C07BlanketMappingProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run("C07", idx, r => FaultClaim.Run(r, Spec(r)), probe); + + /// <summary>HFault mapped on its own (set=own-mapped-set): a status and a problem type of its own per member.</summary> + static readonly Dictionary<string, int> Own = new() { ["Missing"] = 410, ["Clash"] = 409, ["Bad"] = 422, ["Denied"] = 451, ["Down"] = 503, ["Stale"] = 428, ["Garbled"] = 400 }; + static string OwnType(string member) => "https://own.example/" + member; + + public static Spec Spec(Probes.Shapes.Row r) + { + var own = r["mapping"] == "own-IFault-mapping"; + var set = r["set"]; + var mapping = own ? "own-mapping" : "by-kind"; + var refusal = set switch + { + "enum-unmapped" => "enum-needs-own-mapping", "member-without-kind" => "member-without-kind-stops-start", + "mapping-throws" or "status-not-error" => "mapping-refusal-stops-start", _ => null, + }; + var s = new Spec + { + Minimal = new MGrp("/c7", [], [], [new MEp("k", [])]), + Controllers = ["C06K"], + Errors = [.. Enumerable.Range(1, FxErr.H.Length)], + Mapping = own ? "none" : "by-kind", + Typed = own, + Extra = b => FxSets.Map(b, set), + ExtraControllers = FxSets.Controllers(set), + Against = Baseline.Docs("src/CodoMetis.TypeKit.AspNetCore/README.md#every-fault-set-at-once"), + Refusals = refusal is null ? [] : [refusal], + Of = (aspect, m) => aspect == Aspect.Start ? [refusal ?? mapping] + : set != "own-mapped-set" ? [mapping] + : m is null ? [mapping, "own-set-keeps-mapping"] + : m.Type == "HFault" ? ["own-set-keeps-mapping"] : [mapping], + }; + s.Services = sv => + { + if (own) + sv.AddFaultResponses(fault => fault is FxThrows ? throw new FxMappingRefused() + : fault is FxStatus ? 302 + : FaultResponse.For(fault.Kind).WithType(new Uri(FaultClaim.TypeOf(fault.Kind.ToString())))); + if (set == "own-mapped-set") + sv.AddFaultResponses<HFault>(fault => FaultResponse.Status(Own[fault.ToString()]).WithType(new Uri(OwnType(fault.ToString())))); + if (!own && set == "mapping-throws") sv.AddFaultResponses<FxThrows>(_ => throw new FxMappingRefused()); + if (!own && set == "status-not-error") sv.AddFaultResponses<FxStatus>(_ => 302); + }; + if (set == "own-mapped-set") + s.Map = m => m.Type == "HFault" ? (Own[m.Name], OwnType(m.Name)) : (m.Status, own ? FaultClaim.TypeOf(m.Kind) : null); + s.RefusedAs = set switch + { + "enum-unmapped" => ex => ex is InvalidOperationException && ex.Message.Contains("FxEnum", StringComparison.Ordinal) + ? null : $"host refused to start, but not with an InvalidOperationException naming FxEnum: {ex.GetType().Name}: {ex.Message}", + "member-without-kind" => ex => ex is ArgumentException && (own || ex.Message.Contains("FxNoKind.Blank", StringComparison.Ordinal)) + ? null : $"host refused to start, but not with an ArgumentException{(own ? "" : " naming FxNoKind.Blank")}: {ex.GetType().Name}: {ex.Message}", + "mapping-throws" => ex => ex is FxMappingRefused || ex.InnerException is FxMappingRefused + ? null : $"host refused to start, but not with the mapping's own exception: {ex.GetType().Name}: {ex.Message}", + "status-not-error" => ex => ex is ArgumentException && ex.Message.Contains("302", StringComparison.Ordinal) + ? null : $"host refused to start, but not with an ArgumentException naming 302: {ex.GetType().Name}: {ex.Message}", + _ => null, + }; + return s; + } +} diff --git a/tools/probe/corpus/probes/C08SameNameWarningProbe.cs b/tools/probe/corpus/probes/C08SameNameWarningProbe.cs new file mode 100644 index 00000000..9f008d48 --- /dev/null +++ b/tools/probe/corpus/probes/C08SameNameWarningProbe.cs @@ -0,0 +1,10 @@ +// Claim C08-same-name-warning (src/CodoMetis.TypeKit.AspNetCore/README.md#faults-of-several-types, Keep names distinct). Its checks, one per obligation, are SchemaChecks.C08 in harness/SchemaClaim.cs. +using Probes.Schema; +using Probes.Shapes; +namespace Probes; +public sealed class C08SameNameWarningProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C08SameNameWarningProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, SchemaChecks.C08); +} diff --git a/tools/probe/corpus/probes/C09BaseRecordEqualityProbe.cs b/tools/probe/corpus/probes/C09BaseRecordEqualityProbe.cs new file mode 100644 index 00000000..b1e977c2 --- /dev/null +++ b/tools/probe/corpus/probes/C09BaseRecordEqualityProbe.cs @@ -0,0 +1,119 @@ +// Claim C09-base-record-equality (CHANGELOG.md 2.1.0, Fixed): "Over a base record, a value object's equality is its wrapped value's, +// in a collection of the base too, whatever equality the base declares." With Generators README#equality: "the generated Equals(TSelf) +// and GetHashCode() compare the underlying value alone, and never call the base's ... The value object implements each base's +// IEquatable<TBase> again (IReimplementedEquatable<TBase>), so a collection of the base compares it the way the value object does." +// Oracle: claim, differential against the wrapped type's EqualityComparer. Each row's value object (harness/C09Shapes.g.cs, generated +// from this claim's rows) is of shape.kind over shape.wrapped at shape.site, over a chain of base records (shape.inheritance) whose +// topmost declares base-equality by reference. Two value objects over equal wrapped values (distinct instances where the wrapped type is +// a class) are equal, ==, of one hash code and IEquatable-equal, one over another value is not; and the collection (HashSet, +// Dictionary key, Distinct) of the value object and of every base in its chain holds two of the three. +using System.Reflection; +using Probes.Build; +using NoteKeys = Probes.Shapes.NoteKeys; +using Probes.Build.C09; +namespace Probes; + +public sealed class C09BaseRecordEqualityProbe +{ + const string RowsFile = "C09BaseRecordEqualityProbe"; // the probe's own rows + + public static TheoryData<string, int> Data() => Probes.Shapes.Rows.Data(RowsFile); + + [Theory, MemberData(nameof(Data))] + public Task Probe(string probe, int idx) => Probes.Shapes.Rec.Run(probe, idx, r => Task.FromResult(Check(r))); + + /// <summary>A sample, an equal one that is another instance where the type is a class, and another value.</summary> + static (object A, object Same, object Other) Values(string wrapped) => wrapped switch + { + "string" => ("alpha", new string(['a', 'l', 'p', 'h', 'a']), "beta"), + "Guid" => (Guid.Parse("11111111-1111-1111-1111-111111111111"), Guid.Parse("11111111-1111-1111-1111-111111111111"), Guid.Parse("22222222-2222-2222-2222-222222222222")), + "int" => (42, 42, 43), "long" => (4200000000L, 4200000000L, 7L), "byte" => ((byte)7, (byte)7, (byte)8), "ulong" => (4200000000UL, 4200000000UL, 1UL), + "decimal" => (7.80m, 7.80m, 7.81m), "double" => (1.5, 1.5, 2.5), "bool" => (true, true, false), "char" => ('a', 'a', 'b'), + "DateTime" => (new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc), new DateTime(2026, 10, 16, 8, 30, 0, DateTimeKind.Utc)), + "DateTimeOffset" => (new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero), new DateTimeOffset(2026, 10, 16, 8, 30, 0, TimeSpan.Zero)), + "DateOnly" => (new DateOnly(2026, 10, 15), new DateOnly(2026, 10, 15), new DateOnly(2026, 10, 16)), + "TimeOnly" => (new TimeOnly(8, 30), new TimeOnly(8, 30), new TimeOnly(9, 30)), + "TimeSpan" => (TimeSpan.FromMinutes(90), TimeSpan.FromMinutes(90), TimeSpan.FromMinutes(91)), + "enum" => (Probes.Shapes.PEnum.A, Probes.Shapes.PEnum.A, Probes.Shapes.PEnum.B), + "flags-enum" => (Probes.Shapes.PFlags.P, Probes.Shapes.PFlags.P, Probes.Shapes.PFlags.Q), + "Uri" => (new Uri("https://example.org/a"), new Uri("https://example.org/a"), new Uri("https://example.org/b")), + "NodaTime.Instant" => (NodaTime.Instant.FromUnixTimeSeconds(1_700_000_000), NodaTime.Instant.FromUnixTimeSeconds(1_700_000_000), NodaTime.Instant.FromUnixTimeSeconds(1_700_000_001)), + "own-record-class" => (new Probes.Shapes.PRec { A = "a", B = 1 }, new Probes.Shapes.PRec { A = "a", B = 1 }, new Probes.Shapes.PRec { A = "b", B = 2 }), + _ => throw new ArgumentException(wrapped), + }; + + static string? Check(Probes.Shapes.Row r) + { + var c = C09Table.Cases.SingleOrDefault(x => x.Index == r.Index); + if (c is null || c.Key != Decls.Key(RowsFile, r.Index)) + { r.Outcome("harness-error", "C09Shapes.g.cs was generated from other rows: regenerate it with the rows"); return null; } + foreach (var (axis, value) in c.Substituted) r.Substituted(axis, value, "the declaration cannot exist as the row asks (excluded in claims.json)"); + _ = r["shape.kind"]; _ = r["shape.site"]; _ = r["shape.inheritance"]; _ = r["base-equality"]; + var wrapped = r["shape.wrapped"]; var collection = r["collection"]; + + var (a, same, other) = Values(wrapped); + if (!a.GetType().IsValueType && ReferenceEquals(a, same)) { r.Outcome("harness-error", "the equal sample is the same instance"); return null; } + var comparer = typeof(EqualityComparer<>).MakeGenericType(a.GetType()).GetProperty("Default")!.GetValue(null)!; + var eq = comparer.GetType().GetMethod("Equals", [a.GetType(), a.GetType()])!; + if (!(bool)eq.Invoke(comparer, [a, same])! || (bool)eq.Invoke(comparer, [a, other])!) { r.Outcome("harness-error", "the samples do not compare as intended under the wrapped type's equality"); return null; } + + var va = c.Make(a); var vb = c.Make(same); var vc = c.Make(other); + var baseline = Probes.Shapes.Baseline.Twin; // the wrapped type's EqualityComparer, which the samples were checked against + var f1 = r.Check("equal-by-the-wrapped-value", baseline, k => + { + var fails = new List<string>(); + if (!va.Equals(vb)) fails.Add("Equals(object) is false for equal wrapped values"); + if (va.Equals(vc)) fails.Add("Equals(object) is true for different wrapped values"); + if (va.GetHashCode() != vb.GetHashCode()) fails.Add("equal value objects have different hash codes"); + var op = c.Vo.GetMethod("op_Equality", BindingFlags.Public | BindingFlags.Static, [c.Vo, c.Vo]); + if (op is null) fails.Add("no operator =="); + else if (!(bool)op.Invoke(null, [va, vb])!) fails.Add("== is false for equal wrapped values"); + if (Held(c.Vo, collection, va, vb, vc) is { } f) fails.Add($"the value object: {f}"); + if (fails.Count > 0) return string.Join("; ", fails); + k.Note(NoteKeys.Compared, $"the value object, against EqualityComparer<{a.GetType().Name}>"); + return null; + }); + var f2 = r.Check("in-a-collection-of-the-base", baseline, k => + { + if (c.Bases.Length == 0) return k.Inconclusive("no base record in the chain: claims.json excludes the row's pairs"); + var fails = new List<string>(); + foreach (var t in c.Bases) + if (Held(t, collection, va, vb, vc) is { } f) fails.Add($"the base {t.Name}: {f}"); + if (fails.Count > 0) return string.Join("; ", fails); + k.Note(NoteKeys.Compared, $"{c.Bases.Length} base type(s), against EqualityComparer<{a.GetType().Name}>"); + return null; + }); + return f1 ?? f2; + } + + static string? Held(Type t, string collection, object a, object b, object c) => + (string?)typeof(C09BaseRecordEqualityProbe).GetMethod(nameof(Collect), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(t).Invoke(null, [collection, a, b, c]); + + /// <summary>Three values held as <typeparamref name="T"/>, two of them equal: the collection holds two, and IEquatable agrees.</summary> + static string? Collect<T>(string collection, object a, object b, object c) where T : notnull + { + T x = (T)a, y = (T)b, z = (T)c; + if (x is IEquatable<T> e && (!e.Equals(y) || e.Equals(z))) return $"IEquatable<{typeof(T).Name}> answers {e.Equals(y)} for equal and {e.Equals(z)} for different values"; + switch (collection) + { + case "HashSet": + { + var set = new HashSet<T> { x, y, z }; + return set.Count == 2 ? null : $"a HashSet<{typeof(T).Name}> holds {set.Count} of two equal and one other"; + } + case "Dictionary-key": + { + var d = new Dictionary<T, int>(); + var first = d.TryAdd(x, 1); var second = d.TryAdd(y, 2); var third = d.TryAdd(z, 3); + if (!first || second || !third) return $"a Dictionary<{typeof(T).Name}, int> took the keys {first}, {second}, {third}"; + return d.TryGetValue(y, out var found) && found == 1 ? null : $"a Dictionary<{typeof(T).Name}, int> does not find the equal key"; + } + case "Distinct": + { + var n = new[] { x, y, z }.Distinct().Count(); + return n == 2 ? null : $"Distinct over {typeof(T).Name} keeps {n} of two equal and one other"; + } + default: throw new ArgumentException("collection " + collection); + } + } +} diff --git a/tools/probe/corpus/probes/C10PreserveGuardProbe.cs b/tools/probe/corpus/probes/C10PreserveGuardProbe.cs new file mode 100644 index 00000000..35f5ffc9 --- /dev/null +++ b/tools/probe/corpus/probes/C10PreserveGuardProbe.cs @@ -0,0 +1,12 @@ +// Claim C10-preserve-guard (src/CodoMetis.TypeKit.Generators/README.md#json): under ReferenceHandler.Preserve a value object over a type the +// serializer walks as an object throws NotSupportedException in both directions, never writing an $id twice; everything else is unaffected. +// The check is Probes.Json.PreserveGuard. +using Probes.Json; +using Probes.Shapes; +namespace Probes; +public sealed class C10PreserveGuardProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C10PreserveGuardProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(PreserveGuard.Check(r))); +} diff --git a/tools/probe/corpus/probes/C11ParseShapesBuildProbe.cs b/tools/probe/corpus/probes/C11ParseShapesBuildProbe.cs new file mode 100644 index 00000000..7a05c281 --- /dev/null +++ b/tools/probe/corpus/probes/C11ParseShapesBuildProbe.cs @@ -0,0 +1,197 @@ +// Claim C11-parse-shapes-build (CHANGELOG.md 2.1.0, Fixed): "A wrapped type's `Parse` whose result can't be assigned to it, or that +// takes its text by `ref`, or an abstract class's string constructor, no longer breaks the build; the value object has no parsing." +// With Generators README "What you can wrap" ("Parsing where it can be parsed") and "What is generated" (TryFrom for IValidatedValue, +// the [TypeConverter] when parsing is generated, ToString() the underlying type's own text under the invariant culture). +// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104), the row's parse-shape +// selecting one: +// implicit-result: "A wrapped type's `Parse` whose result can't be assigned to it" +// ref-text: "or that takes its text by `ref`" +// abstract-string-constructor: "or an abstract class's string constructor, no longer breaks the build; the value object has no parsing." +// parsing-where-it-can-be-parsed (ordinary): "`IParsable`/`ISpanParsable`/`IUtf8SpanParsable`, formatting in the invariant culture" +// (the 2.0.0 entry of the same file: what the fix must leave to a type that parses) +// Each row's value object (harness/C11Shapes.g.cs, generated from this claim's rows) is of shape.kind at shape.site with +// shape.inheritance, over the parse shape's own class (implicit-result: Parse returns an int; ref-text: Parse or the string constructor +// takes the text by ref; abstract-string-ctor) or, for ordinary, over shape.wrapped. That it is in this assembly is the build half. +// Under text.culture and process.converter-lookup, the row's text.entry is then checked: +// a parse shape's obligation, against the CHANGELOG's sentence (Baseline.Docs): no Parse, TryParse, IParsable, ISpanParsable or +// string type converter, while TryFrom (validated) and formatting work, and the same declaration over the nearest type that parses +// (its control) parses; +// parsing-where-it-can-be-parsed, against the catalogue's top-level value object of the same kind over the same wrapped type +// (Probes.Shapes.Registry, Baseline.Twin): the entry exists exactly where the catalogue's does and wherever the wrapped type parses, +// ISpanParsable included, and gives the catalogue's result. +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using Probes.Build; +using Probes.Build.C11; +using Probes.Shapes; +namespace Probes; + +public sealed class C11ParseShapesBuildProbe +{ + const string RowsFile = "C11ParseShapesBuildProbe"; // the probe's own rows + + public static TheoryData<string, int> Data() => Rows.Data(RowsFile); + + [Theory, MemberData(nameof(Data))] + public Task Probe(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(Check(r))); + + static readonly string[] ParseEntries = ["Parse", "TryParse", "IParsable", "ISpanParsable", "type-converter"]; + + static string? Check(Probes.Shapes.Row r) + { + var c = C11Table.Cases.SingleOrDefault(x => x.Index == r.Index); + if (c is null || c.Key != Decls.Key(RowsFile, r.Index)) + { r.Outcome("harness-error", "C11Shapes.g.cs was generated from other rows: regenerate it with the rows"); return null; } + foreach (var (axis, value) in c.Substituted) r.Substituted(axis, value, "the declaration cannot exist as the row asks (excluded in claims.json)"); + _ = r["shape.kind"]; _ = r["shape.site"]; _ = r["shape.inheritance"]; + var shape = r["parse-shape"]; var entry = r["text.entry"]; + var ordinary = shape == "ordinary"; + var wrapped = ordinary ? r["shape.wrapped"] : null; + object sample = shape switch + { + "ordinary" => Registry.Get("record-struct", wrapped!).Valid(), + "implicit-result" => new C11ParsesToInt(), + "ref-text" => c.Wrapped == typeof(C11ParsesByRef) ? new C11ParsesByRef() : C11ConstructedByRef.Sample(), + "abstract-string-ctor" => new C11ConcreteText("abstract-text"), + _ => throw new ArgumentException(shape), + }; + if (!c.Wrapped.IsInstanceOfType(sample)) { r.Outcome("harness-error", $"the sample is no {c.Wrapped.Name}"); return null; } + var e = new Entry(c.Kind, wrapped ?? shape, c.Vo, c.Wrapped, () => sample, () => null, typeof(object), typeof(object), typeof(object), typeof(object)); + TextOps.Prepare(r, e); + string? Holds(object vo) => Equals(Make.Unwrap(e, vo), sample) ? null : "the value object does not hold the value it was made from"; + if (ordinary) + return r.Check("parsing-where-it-can-be-parsed", Baseline.Twin, _ => { var vo = Make.Vo(e, sample); return Holds(vo) ?? Ordinary(r, e, entry, wrapped!, sample, vo); }); + var obligation = shape switch + { + "implicit-result" => "implicit-result", + "ref-text" => "ref-text", + "abstract-string-ctor" => "abstract-string-constructor", + _ => throw new ArgumentException(shape), + }; + return r.Check(obligation, Baseline.Docs("CHANGELOG.md#2.1.0-fixed"), _ => { var vo = Make.Vo(e, sample); return Holds(vo) ?? NoParsing(r, e, entry, sample, vo) ?? Control(c); }); + } + + /// <summary> + /// The parse shape's control, the same declaration over the nearest type that parses: it has IParsable, and Parse gives back the text + /// it was given. Without it a generator that took parsing from every class would pass the shapes. + /// </summary> + static string? Control(C11Case c) + { + if (c.Control is null) throw new InvalidOperationException($"row {c.Index} has no control"); + if (!TextOps.Implements(typeof(IParsable<>), c.Control)) return $"the control over {c.ControlWrapped!.Name}, which can be parsed, has no IParsable"; + var parse = c.Control.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); + if (parse is null) return $"the control over {c.ControlWrapped!.Name} has no Parse(string, IFormatProvider)"; + object? parsed; + try { parsed = parse.Invoke(null, ["control-text", null]); } + catch (TargetInvocationException t) { return $"the control's Parse threw {t.InnerException!.GetType().Name}: {t.InnerException.Message}"; } + var value = c.Control.GetProperty("Value")!.GetValue(parsed); + return value?.ToString() == "control-text" ? null : $"the control's Parse gave back '{value}', not 'control-text'"; + } + + static bool Has(Type t, string name) => t.GetMethods(BindingFlags.Public | BindingFlags.Static).Any(m => m.Name == name); + + static string? NoParsing(Probes.Shapes.Row r, Entry e, string entry, object sample, object vo) + { + var t = e.Vo; + // The baseline every row checks: no member of parsing at all. + var parsing = new List<string>(); + if (Has(t, "Parse")) parsing.Add("Parse"); + if (Has(t, "TryParse")) parsing.Add("TryParse"); + if (TextOps.Implements(typeof(IParsable<>), t)) parsing.Add("IParsable"); + if (TextOps.Implements(typeof(ISpanParsable<>), t)) parsing.Add("ISpanParsable"); + if (TextOps.Implements(typeof(IUtf8SpanParsable<>), t)) parsing.Add("IUtf8SpanParsable"); + if (t.GetCustomAttribute<TypeConverterAttribute>() is not null || TypeDescriptor.GetConverter(t).CanConvertFrom(typeof(string))) parsing.Add("a type converter from string"); + if (parsing.Count > 0) return $"the value object over a {r["parse-shape"]} type has parsing: {string.Join(", ", parsing)}"; + var text = sample.ToString()!; + switch (entry) + { + case "Parse" or "TryParse" or "IParsable" or "ISpanParsable" or "type-converter": + { + var o = TextOps.Read(e, entry, text); + if (!o.NotApplicable) return $"{entry} reads text on a value object documented to have no parsing"; + break; + } + case "TryFrom": + { + var o = TextOps.TryFrom(e, sample); + if (TextOps.ValidatedKind(e)) + { + if (o.NotApplicable) return "a validated value object has no TryFrom"; + if (o.Ex is not null || !o.Ok || !Equals(Make.Unwrap(e, o.Value!), sample)) return $"TryFrom(valid) gave ok={o.Ok} {o.Ex?.GetType().Name}"; + } + else if (Has(e.Vo, "TryFrom")) return "a plain value object has a TryFrom"; + break; + } + case "ToString-format": + { + if (vo.ToString() != text) return $"ToString() is '{vo}', the wrapped value's own text is '{text}'"; + if (vo is IFormattable f && f.ToString(null, CultureInfo.InvariantCulture) != text) return $"IFormattable.ToString is '{f.ToString(null, CultureInfo.InvariantCulture)}', expected '{text}'"; + break; + } + case "TryFormat-short-span": + { + if (vo.ToString() != text) return $"ToString() is '{vo}', the wrapped value's own text is '{text}'"; + if (vo is ISpanFormattable) + { + var full = new object?[] { vo, 4096, null, null }; + var written = (string)typeof(TextOps).GetMethod(nameof(TextOps.FS))!.MakeGenericMethod(e.Vo).Invoke(null, full)!; + if (!(bool)full[2]! || written != text) return $"TryFormat wrote '{written}' (ok={full[2]}), the wrapped value's text is '{text}'"; + var shortArgs = new object?[] { vo, Math.Max(0, text.Length - 1), null, null }; + typeof(TextOps).GetMethod(nameof(TextOps.FS))!.MakeGenericMethod(e.Vo).Invoke(null, shortArgs); + if ((bool)shortArgs[2]!) return $"TryFormat into a span of {text.Length - 1} said '{text}' fit"; + } + break; + } + default: throw new ArgumentException("text.entry " + entry); + } + r.Note(NoteKeys.Compared, $"no parsing; {entry}; the control parses"); + return null; + } + + static string? Ordinary(Probes.Shapes.Row r, Entry e, string entry, string wrapped, object sample, object vo) + { + var reference = Registry.Get(e.Kind, wrapped); + var refVo = Make.Vo(reference, sample); + string? Same(Out mine, Out theirs, Entry me, Entry them, string what) + { + if (mine.NotApplicable != theirs.NotApplicable) return $"{what}: {(mine.NotApplicable ? "absent" : "present")} here, {(theirs.NotApplicable ? "absent" : "present")} on the catalogue's top-level value object"; + if (mine.NotApplicable) return null; + if ((mine.Ex is null) != (theirs.Ex is null) || mine.Ex?.GetType() != theirs.Ex?.GetType()) + return $"{what}: {(mine.Ex is null ? "no exception" : mine.Ex.GetType().Name + ": " + mine.Ex.Message)} here, {(theirs.Ex is null ? "no exception" : theirs.Ex.GetType().Name)} on the catalogue's"; + if (mine.Ok != theirs.Ok) return $"{what}: ok={mine.Ok} here, {theirs.Ok} on the catalogue's"; + if (mine.Text != theirs.Text) return $"{what}: '{mine.Text}' here, '{theirs.Text}' on the catalogue's"; + if (mine.Value is not null && theirs.Value is not null && !Equals(Make.Unwrap(me, mine.Value), Make.Unwrap(them, theirs.Value))) + return $"{what}: {Make.Unwrap(me, mine.Value)} here, {Make.Unwrap(them, theirs.Value)} on the catalogue's"; + return null; + } + string? fail; + if (ParseEntries.Contains(entry)) + { + var text = TextOps.PlainText(reference, sample); + var mine = TextOps.Read(e, entry, text); + if (mine.NotApplicable && wrapped != "own-record-class") + return $"{entry}: the value object over {wrapped}, which parses, has no {entry}"; + fail = Same(mine, TextOps.Read(reference, entry, text), e, reference, entry); + } + else if (entry == "TryFrom") + { + var mine = TextOps.TryFrom(e, sample); + if (TextOps.ValidatedKind(e) && (mine.NotApplicable || !mine.Ok)) return "TryFrom(valid) on a validated value object gave no value"; + fail = Same(mine, TextOps.TryFrom(reference, sample), e, reference, "TryFrom"); + } + else + { + fail = Same(TextOps.Write(e, entry, vo), TextOps.Write(reference, entry, refVo), e, reference, entry); + if (fail is null && entry == "TryFormat-short-span" && vo is ISpanFormattable) + { + var text = TextOps.Write(e, entry, vo).Text ?? ""; + var shortArgs = new object?[] { vo, Math.Max(0, text.Length - 1), null, null }; + typeof(TextOps).GetMethod(nameof(TextOps.FS))!.MakeGenericMethod(e.Vo).Invoke(null, shortArgs); + if (text.Length > 0 && (bool)shortArgs[2]!) fail = $"TryFormat into a span of {text.Length - 1} said '{text}' fit"; + } + } + if (fail is null) r.Note(NoteKeys.Compared, $"{entry} against the catalogue's {e.Kind} over {wrapped}"); + return fail; + } +} diff --git a/tools/probe/corpus/probes/C12DescribedConverterProbe.cs b/tools/probe/corpus/probes/C12DescribedConverterProbe.cs new file mode 100644 index 00000000..6e25d05f --- /dev/null +++ b/tools/probe/corpus/probes/C12DescribedConverterProbe.cs @@ -0,0 +1,10 @@ +// Claim C12-described-converter (docs/json.md#describing-what-a-converter-writes and the AspNetCore README's A described type). Its checks, one per obligation, are SchemaChecks.C12 in harness/SchemaClaim.cs. +using Probes.Schema; +using Probes.Shapes; +namespace Probes; +public sealed class C12DescribedConverterProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C12DescribedConverterProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, SchemaChecks.C12); +} diff --git a/tools/probe/corpus/probes/C13PatchRequiredProbe.cs b/tools/probe/corpus/probes/C13PatchRequiredProbe.cs new file mode 100644 index 00000000..26197cae --- /dev/null +++ b/tools/probe/corpus/probes/C13PatchRequiredProbe.cs @@ -0,0 +1,10 @@ +// Claim C13-patch-required (docs/merge-patches.md#required-constructor-parameters). Its checks, one per obligation, are SchemaChecks.C13 in harness/SchemaClaim.cs. +using Probes.Schema; +using Probes.Shapes; +namespace Probes; +public sealed class C13PatchRequiredProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C13PatchRequiredProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, SchemaChecks.C13); +} diff --git a/tools/probe/corpus/probes/C14DeclaredProblemProbe.cs b/tools/probe/corpus/probes/C14DeclaredProblemProbe.cs new file mode 100644 index 00000000..e8a53d2a --- /dev/null +++ b/tools/probe/corpus/probes/C14DeclaredProblemProbe.cs @@ -0,0 +1,42 @@ +// Claim C14-declared-problem (CHANGELOG 2.1.0 Fixed): "OpenAPI: a problem response an endpoint declares itself (ProducesProblem, +// [ProducesResponseType]) describes fault, and names its problem types." and "an operation with fault responses lists every status ascending, +// the ones its endpoint declares itself among them". declared: the endpoint declares a 404 problem itself, a status HFault.Missing answers: +// minimal .ProducesProblem(404) / MVC [ProducesResponseType<ProblemDetails>(404, "application/problem+json")] (what ProducesProblem declares), +// [ProducesResponseType<ProblemDetails>(404)] on the lambda or the action, or nothing (the control). The mapping gives a problem type per kind. +// Oracle: FaultClaim: the 404's schema describes fault and rule, its description names https://p.example/NotFound, the statuses ascending. +// A check per obligation: declared-describes-fault (the problem schemas' fault and rule), declared-names-types (the problem types the +// descriptions name, and what each member answers), ascending-with-declared (which statuses each operation lists, and their order), and in +// the rows of [ProducesResponseType<ProblemDetails>(404)] application-json-declared (its schema and its problem types). declared=none is the +// control: the same checks, where the endpoint declares nothing. Not checked here: that application/json keeps its media type. +using Probes.Faults; +using Probes.Shapes; +namespace Probes; +public sealed class C14DeclaredProblemProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C14DeclaredProblemProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run("C14", idx, r => FaultClaim.Run(r, Spec(r)), probe); + + public static Spec Spec(Probes.Shapes.Row r) + { + var (ep, ctl) = r["declared"] switch + { + "ProducesProblem" => (new MEp("d", [], [404], "pp"), "C14Pp"), + "ProducesResponseType" => (new MEp("d", [], [404], "prt"), "C14Prt"), + _ => (new MEp("d", []), "C14None"), + }; + var json = r["declared"] == "ProducesResponseType"; + return new Spec + { + Minimal = new MGrp("/c14", [], [], [ep]), Controllers = [ctl], Errors = [FxErr.E("Missing"), FxErr.E("Clash")], + Against = Baseline.Docs("CHANGELOG.md#2.1.0-fixed"), + Of = (aspect, _) => aspect switch + { + Aspect.DocDescribes => json ? ["declared-describes-fault", "application-json-declared"] : ["declared-describes-fault"], + Aspect.DocTypes => json ? ["declared-names-types", "application-json-declared"] : ["declared-names-types"], + Aspect.DocListed or Aspect.DocOrder => ["ascending-with-declared"], + _ => ["declared-names-types"], + }, + }; + } +} diff --git a/tools/probe/corpus/probes/C15MvcFaultStatusDocProbe.cs b/tools/probe/corpus/probes/C15MvcFaultStatusDocProbe.cs new file mode 100644 index 00000000..d4e3b0aa --- /dev/null +++ b/tools/probe/corpus/probes/C15MvcFaultStatusDocProbe.cs @@ -0,0 +1,51 @@ +// Claim C15-mvc-fault-status-doc (CHANGELOG 2.1.0 Fixed): "OpenAPI: a WithFaultStatus on the builder MapControllers() returns reaches the +// document, as it reached the response." Each MVC setter, with a status (HFault.Denied 451) or a member left out (HFault.Down): what +// MapControllers()'s builder, an action's attribute and a controller's attribute declare reaches the document as it reaches the response. +// Precedence (README "A status per endpoint"): "ASP.NET puts the metadata of MapControllers()'s builder after every attribute, so a status +// set there wins over an action's own"; "an action's over its controller's": an action with its own Denied 452 sits beside each scope. +// Minimal hosts take the equivalent of each setter (README: "Any convention builder that covers several endpoints works the same"): +// MapControllers()'s builder = a group over every endpoint, an action = the endpoint, a controller = a group beside a sibling endpoint; +// there "An endpoint's own wins over its group's". Oracle: FaultClaim. A check per obligation: status-per-scope (kind status-only) or +// without-faults-leaves-doc (kind WithoutFaults) for what every member answers, the member left out throwing, and every operation; and, +// where MapControllers()'s builder sets it, builder-reaches-doc for the operations too. +using Probes.Faults; +using Probes.Shapes; +namespace Probes; +public sealed class C15MvcFaultStatusDocProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C15MvcFaultStatusDocProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run("C15", idx, r => FaultClaim.Run(r, Spec(r)), probe); + + static readonly Decl Own = new("HFault", "Denied", 452); + public static Spec Spec(Probes.Shapes.Row r) + { + var statusOnly = r["kind"] == "status-only"; + var set = statusOnly ? new Decl("HFault", "Denied", 451) : new Decl("HFault", "Down", 0); + var kind = statusOnly ? "status-per-scope" : "without-faults-leaves-doc"; + var builder = r["setter"] == "MapControllers().WithFaultStatus"; + var s = new Spec + { + Errors = [FxErr.E("Missing"), FxErr.E("Denied"), FxErr.E("Down")], + Against = Baseline.Docs("CHANGELOG.md#2.1.0-fixed"), + Of = (aspect, _) => builder && Aspect.Doc.Contains(aspect) ? [kind, "builder-reaches-doc"] : [kind], + }; + switch (r["setter"]) + { + case "MapControllers().WithFaultStatus": + s.Minimal = new MGrp("/c15", [set], [], [new MEp("s/a", []), new MEp("s/p", [Own])]); + s.Controllers = ["C15M"]; + s.Builder = [set]; + break; + case "action-attribute": + s.Minimal = new MGrp("/c15", [], [], [new MEp("s/a", [set]), new MEp("s/k", [])]); + s.Controllers = [statusOnly ? "C15AS" : "C15AW"]; + break; + default: // controller-attribute + s.Minimal = new MGrp("/c15", [], [new MGrp("/g", [set], [], [new MEp("s/a", []), new MEp("s/p", [Own])])], [new MEp("s/k", [])]); + s.Controllers = [statusOnly ? "C15CS" : "C15CW", "C15K"]; + break; + } + return s; + } +} diff --git a/tools/probe/corpus/probes/C16OptionsAgreementProbe.cs b/tools/probe/corpus/probes/C16OptionsAgreementProbe.cs new file mode 100644 index 00000000..dbe824c1 --- /dev/null +++ b/tools/probe/corpus/probes/C16OptionsAgreementProbe.cs @@ -0,0 +1,10 @@ +// Claim C16-options-agreement (src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says, Register the same on both option sets). Its checks, one per obligation, are SchemaChecks.C16 in harness/SchemaClaim.cs. +using Probes.Schema; +using Probes.Shapes; +namespace Probes; +public sealed class C16OptionsAgreementProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C16OptionsAgreementProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, SchemaChecks.C16); +} diff --git a/tools/probe/corpus/probes/C17Cmtk0014Probe.cs b/tools/probe/corpus/probes/C17Cmtk0014Probe.cs new file mode 100644 index 00000000..629484f6 --- /dev/null +++ b/tools/probe/corpus/probes/C17Cmtk0014Probe.cs @@ -0,0 +1,261 @@ +// Claim C17-cmtk0014 (src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0014): "A FaultKind or a ConstructorAccessibility that names +// no member: a cast, default(FaultKind), a zero, or a folded constant" is reported (Warning), and "Both are reported where the value +// is written". The documented cases: `[RequireParameterlessConstructor((ConstructorAccessibility)7)]` and a fault set's member +// `new(default)`, beside `new(FaultKind.Conflict)`, a member, which is silent; "a default that is a member is silent" +// (ConstructorAccessibility.Public is 0). +// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104), the row's value +// and site selecting them: +// a-cast-naming-no-member (value (FaultKind)9): "A `FaultKind` or a `ConstructorAccessibility` that names no member: a cast" +// default-and-zero (value default): "`default(FaultKind)`, a zero" +// constructor-accessibility-too (value (FaultKind)9, site attribute-argument): "[RequireParameterlessConstructor((ConstructorAccessibility)7)] // CMTK0014: no member, read as public" +// reported-where-written (value (FaultKind)9 or default): "Both are reported where the value is written:" +// a-member-is-silent (value valid): "public static readonly ImportFault Duplicate = new(FaultKind.Conflict);" +// Oracle: the README's section (Baseline.Docs). Each row writes the value (value axis) in the written form (site axis) at every place +// its shape axes describe, each place marked: in the declaration of shape.kind over shape.wrapped at shape.site (a fault set's member +// for closed-set, a static member of a value object otherwise), in the topmost base of its shape.inheritance chain and in an explicit +// interface member, in a member of a shape.containing type at shape.position, and in a declaration of shape.refused-kind (which +// compiles here, with no generators; the two kinds that do not are excluded). An attribute-argument row also writes the documented +// RequireParameterlessConstructor case. One compilation per row, and every check judges its share of it: +// a-cast-naming-no-member, default-and-zero: each FaultKind place of an undefined value is reported once, at or around the value, +// naming FaultKind (default-and-zero also judges RequireParameterlessConstructor's default, Public, silent); +// constructor-accessibility-too: RequireParameterlessConstructor's (ConstructorAccessibility)9 is reported, naming its enum; +// reported-where-written: every report sits at the value's own span, and none outside every place; +// a-member-is-silent: no place of a value that names a member is reported, and nothing is reported anywhere. +using Probes.Build; +using Probes.Shapes; +namespace Probes; + +public sealed class C17Cmtk0014Probe +{ + public static TheoryData<string, int> Data() => Rows.Data("C17Cmtk0014Probe"); + + [Theory, MemberData(nameof(Data))] + public Task Probe(string probe, int idx) => Rec.Run(probe, idx, Check); + + sealed class Places(string value, string site) + { + int n; + public readonly HashSet<string> Expected = new(); + public readonly List<string> Labels = new(); + public bool Undefined => value != "valid"; + + string Label() { var l = "p" + n++; Labels.Add(l); if (Undefined) Expected.Add(l); return l; } + + string Value(bool targetTyped) => (site, value) switch + { + ("cast", "(FaultKind)9") => "(FaultKind)9", + ("cast", "default") => "(FaultKind)0", + ("cast", "valid") => "(FaultKind)2", + ("default-literal", _) => targetTyped ? "default" : "default(FaultKind)", + ("attribute-argument", "(FaultKind)9") => "(FaultKind)9", + ("attribute-argument", "default") => "default", + ("attribute-argument", "valid") => "FaultKind.Conflict", + _ => throw new ArgumentException($"{site} {value}"), + }; + + /// <summary>The expression for a place (marked unless the value goes into the attribute), and the attribute to put on its member.</summary> + public (string E, string Attr) Place(bool targetTyped = true) + { + var l = Label(); + return site == "attribute-argument" + ? ("FaultKind.NotFound", $"[ProbeKind({{|#{l}:{Value(true)}|}})] ") + : ($"{{|#{l}:{Value(targetTyped)}|}}", ""); + } + + /// <summary>RequireParameterlessConstructor's argument: (ConstructorAccessibility)9 is reported, default (Public) and a member are not.</summary> + public string Accessibility() + { + var l = "ca" + n++; Labels.Add(l); + if (value == "(FaultKind)9") Expected.Add(l); + var v = value switch { "(FaultKind)9" => "(ConstructorAccessibility)9", "default" => "default", _ => "ConstructorAccessibility.Internal" }; + return $"{{|#{l}:{v}|}}"; + } + } + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var value = r["value"]; var site = r["site"]; + if (site == "default-literal" && value != "default") + { + r.Substituted("site", "cast", "a default literal is the value 0, so it cannot spell this value (excluded pair)"); + site = "cast"; + } + var kind = r["shape.kind"]; var inheritance = r["shape.inheritance"]; + if (Src.IsStructKind(kind) && inheritance is not ("none" or "explicit-interface-member")) + { + r.Substituted("shape.inheritance", "none", "a record struct has no base type (excluded pair)"); + inheritance = "none"; + } + var p = new Places(value, site); + var id = r.Index.ToString(); + var body = new System.Text.StringBuilder(); + + // The declaration of shape.kind over shape.wrapped, with its base chain and explicit member. + var depth = inheritance switch { "base-depth-1" or "base-explicit-IEquatable" => 1, "base-depth-2" => 2, "base-depth-3" => 3, _ => 0 }; + var record = kind != "closed-set"; + for (var d = depth; d >= 1; d--) + { + var name = $"DB{id}_{d}"; var parent = d == depth ? "" : $" : DB{id}_{d + 1}"; + var equatable = d == 1 && inheritance == "base-explicit-IEquatable"; + if (equatable) parent = parent.Length == 0 ? $" : IEquatable<{name}>" : parent + $", IEquatable<{name}>"; + var members = d == depth ? Member(p.Place(), "BK") : ""; + if (equatable) members += $"\n bool IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"; + body.AppendLine($"public abstract {(record ? "record" : "class")} {name}{parent}\n{{\n {members}\n}}"); + } + var explicitMember = inheritance == "explicit-interface-member"; + var mark = explicitMember ? $", IMark{id}" : ""; + if (explicitMember) body.AppendLine($"public interface IMark{id} {{ FaultKind Mark(); }}"); + var baseList = depth > 0 ? $"DB{id}_1, " : ""; + string Explicit() { if (!explicitMember) return ""; var (e, a) = p.Place(); return $"\n {a}FaultKind IMark{id}.Mark() => {e};"; } + if (kind == "closed-set") + { + var (e, a) = p.Place(); + body.AppendLine($$""" + public sealed partial class D{{id}} : {{baseList}}IFaultSet<D{{id}}>{{mark}} + { + public static readonly D{{id}} A = new(FaultKind.NotFound); + {{a}}public static readonly D{{id}} M = new({{e}}); + private D{{id}}(FaultKind kind) => Kind = kind; + public FaultKind Kind { get; } + public string Name => ""; + public string QualifiedName => ""; + public static IReadOnlyList<D{{id}}> All => [A, M]; + public static bool TryParse(string? name, out D{{id}} member) { member = A; return true; }{{Explicit()}} + } + """); + } + else + { + var w = Src.Wrapped(r["shape.wrapped"], id); + var head = kind switch + { + "record-struct" => $"public readonly partial record struct D{id} : IValue<{w}>{mark}", + "sealed-record-class" => $"public sealed partial record D{id} : {baseList}IValue<{w}>{mark}", + "validated-struct" => $"public readonly partial record struct D{id} : IValidatedValue<D{id}, {w}, DF{id}>{mark}", + "validated-class" => $"public sealed partial record D{id} : {baseList}IValidatedValue<D{id}, {w}, DF{id}>{mark}", + _ => throw new ArgumentException(kind), + }; + var create = kind.StartsWith("validated") ? $"\n public static Result<D{id}, DF{id}> Create({w} value) => Result.Error(DF{id}.No);" : ""; + body.AppendLine($"public enum DF{id} {{ No }}"); + body.AppendLine($"{head}\n{{\n {Member(p.Place(), "K")}{create}{Explicit()}\n}}"); + } + + // shape.refused-kind: refused by the generators, an ordinary declaration to the compiler. The two kinds that are not (a closed + // set named as a generated member, CS0535; a hand-written operator ==, CS0111) are excluded in claims.json, and a row the + // covering array fills with one runs over object instead. + var refused = r["shape.refused-kind"]; + if (refused is "closed-set-named-as-generated-member (CMTK1021)" or "equality-operator (CMTK1011)") + { + r.Substituted("shape.refused-kind", "over-object (CMTK1005)", "it does not compile without the generators (excluded pair)"); + refused = "over-object (CMTK1005)"; + } + var rm = Member(p.Place(), "RK"); + body.AppendLine(refused switch + { + "unsealed-record-class (CMTK1006)" => $"public partial record R{id} : IValue<string> {{ {rm} }}", + "over-value-object (CMTK1005)" => $"public readonly partial record struct RIn{id} : IValue<int>;\npublic sealed partial record R{id} : IValue<RIn{id}> {{ {rm} }}", + "generic-wrapped (CMTK1005)" => $"public readonly partial record struct R{id} : IValue<List<int>> {{ {rm} }}", + "abstract (CMTK1005)" => $"public abstract partial record R{id} : IValue<string> {{ {rm} }}", + "over-object (CMTK1005)" => $"public sealed partial record R{id} : IValue<object> {{ {rm} }}", + "over-System.Enum (CMTK1005)" => $"public readonly partial record struct R{id} : IValue<System.Enum> {{ {rm} }}", + "named-as-generated-member (CMTK1005)" => $"public readonly partial record struct {Refusals.ValueObjectNames[r.Index % Refusals.ValueObjectNames.Length]} : IValue<int> {{ {rm} }}", + _ => throw new ArgumentException(refused), + }); + + // shape.containing at shape.position. + body.AppendLine(Container(r["shape.containing"], r["shape.position"], $"C{id}", p)); + + if (site == "attribute-argument") + body.AppendLine($"[RequireParameterlessConstructor({p.Accessibility()})] public interface IPlug{id} {{ }}"); + + var source = Src.Usings + Src.WrappedDeclarations(id) + + "[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public sealed class ProbeKindAttribute(FaultKind kind) : Attribute { public FaultKind Kind { get; } = kind; }\n" + + Src.AtSite(r["shape.site"], id, body.ToString()); + var run = await Rz.Analyze("CMTK0014", [("Row" + id + ".cs", source)]); + if (run.Errors.Count > 0) { r.Outcome("harness-error", "the probe's source does not compile: " + run.ErrorText()); return null; } + if (run.AnalyzerFailures.Count > 0) return "the analyzer failed: " + string.Join("; ", run.AnalyzerFailures.Select(f => f.GetMessage())); + + // Every check the row's value selects runs, so a seed that breaks one obligation is seen by its own check whatever the others find. + var readme = Baseline.Docs("src/CodoMetis.TypeKit.Analyzers/README.md#cmtk0014"); + var kinds = p.Labels.Where(l => l.StartsWith('p')).ToList(); + var accessibilities = p.Labels.Where(l => l.StartsWith("ca", StringComparison.Ordinal)).ToList(); + var failures = new List<string?>(); + switch (value) + { + case "(FaultKind)9": + failures.Add(r.Check("a-cast-naming-no-member", readme, c => Judged(c, Rz.Reported(run, kinds, p.Expected, Names), $"{kinds.Count} FaultKind places reported"))); + if (site == "attribute-argument") + failures.Add(r.Check("constructor-accessibility-too", readme, c => Judged(c, Rz.Reported(run, accessibilities, p.Expected, Names), "RequireParameterlessConstructor's (ConstructorAccessibility)9 reported"))); + failures.Add(r.Check("reported-where-written", readme, c => Judged(c, Rz.WhereWritten(run, p.Expected), $"{p.Expected.Count} reports at the value"))); + break; + case "default": + failures.Add(r.Check("default-and-zero", readme, c => Judged(c, Rz.Reported(run, p.Labels, p.Expected, Names), $"{kinds.Count} FaultKind places reported, {accessibilities.Count} default ConstructorAccessibility silent"))); + failures.Add(r.Check("reported-where-written", readme, c => Judged(c, Rz.WhereWritten(run, p.Expected), $"{p.Expected.Count} reports at the value"))); + break; + case "valid": + failures.Add(r.Check("a-member-is-silent", readme, c => Judged(c, Rz.Reported(run, p.Labels, p.Expected) ?? Rz.WhereWritten(run, p.Expected), $"{p.Labels.Count} places silent"))); + break; + default: throw new ArgumentException("value " + value); + } + return failures.FirstOrDefault(f => f is not null); + } + + static string? Judged(Probes.Shapes.Check c, string? failure, string compared) + { + if (failure is null) c.Note(NoteKeys.Compared, compared); + return failure; + } + + /// <summary>A report names the enum of the place it is at: FaultKind, or ConstructorAccessibility for RequireParameterlessConstructor's.</summary> + static string? Names(Microsoft.CodeAnalysis.Diagnostic d, Mark m) + { + var type = m.Label.StartsWith("ca", StringComparison.Ordinal) ? "ConstructorAccessibility" : "FaultKind"; + return d.GetMessage().Contains($"names no member of {type}") ? null : $"the report does not name {type}: {d.GetMessage()}"; + } + + static string Member((string E, string Attr) place, string name) => $"{place.Attr}public static FaultKind {name} => {place.E};"; + + /// <summary>shape.containing holding the value at shape.position; static members, so a struct needs no constructor.</summary> + static string Container(string containing, string position, string name, Places p) + { + var self = Src.ContainerSelf(containing, name); + if (position == "record-ctor-parameter") + { + var (e, a) = p.Place(); + var record = containing is "record-class" or "record-struct"; + var parameter = record ? $"({a}FaultKind K = {e})" : $"({a}FaultKind k = {e})"; + return Src.ContainerHead(containing, name, parameter) + (record ? ";" : " { public FaultKind K => k; }"); + } + string member; + if (position == "required-init") + { + var (e, a) = p.Place(); + member = $"{a}public required FaultKind K {{ get; init; }}\n public static {self} Make() => new() {{ K = {e} }};"; + } + else if (position == "polymorphic-derived-property") + { + var (e, a) = p.Place(); + member = $"public abstract class PB {{ public abstract FaultKind K {{ get; }} }}\n public sealed class PD : PB {{ {a}public override FaultKind K => {e}; }}"; + } + else + { + var (e, a) = p.Place(targetTyped: position != "nullable-property"); + member = a + position switch + { + "root" => $"public static FaultKind Root() => {e};", + "property" => $"public static FaultKind P {{ get; }} = {e};", + "nullable-property" => $"public static FaultKind? N {{ get; }} = {e};", + "field" => $"public static readonly FaultKind F = {e};", + "list-element" => $"public static readonly List<FaultKind> L = [{e}];", + "array-element" => $"public static readonly FaultKind[] A = [{e}];", + "immutable-array-element" => $"public static readonly ImmutableArray<FaultKind> I = [{e}];", + "read-only-list-element" => $"public static readonly IReadOnlyList<FaultKind> R = [{e}];", + "dictionary-key" => $"public static readonly Dictionary<FaultKind, int> D = new() {{ [{e}] = 1 }};", + "dictionary-value" => $"public static readonly Dictionary<int, FaultKind> D = new() {{ [1] = {e} }};", + "in-patch" => $"public static Patch<FaultKind> Pa => Patch.Value<FaultKind>({e});", + _ => throw new ArgumentException("shape.position " + position), + }; + } + return $"{Src.ContainerHead(containing, name)}\n{{\n {member}\n}}"; + } +} diff --git a/tools/probe/corpus/probes/C18Cmtk1023Probe.cs b/tools/probe/corpus/probes/C18Cmtk1023Probe.cs new file mode 100644 index 00000000..6be2d049 --- /dev/null +++ b/tools/probe/corpus/probes/C18Cmtk1023Probe.cs @@ -0,0 +1,245 @@ +// Claim C18-cmtk1023 (src/CodoMetis.TypeKit.Generators/README.md#build-errors): "CMTK1023 | A warning, and the value object is +// generated. A base record declares `Equals(TBase)`, `GetHashCode()` or `IEquatable<TBase>.Equals`, which the generated equality, the +// underlying value's, doesn't call." With README#equality: "a hand-written `Equals(TBase)`, `GetHashCode()` or `IEquatable<TBase>.Equals` +// on a base record isn't used, and a warning names it (CMTK1023)". +// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104); every row runs all three: +// names-each-base-member: "So a hand-written `Equals(TBase)`, `GetHashCode()` or `IEquatable<TBase>.Equals` on a base record isn't used, and a warning names it (CMTK1023)." +// a-warning-and-generated: "A warning, and the value object is generated." +// generated-equality-never-calls-the-base: "Over a base record, the generated `Equals(TSelf)` and `GetHashCode()` compare the underlying value alone, and never call the base's." +// Oracle: the README's sentences (Baseline.Docs) for what the build reports, the wrapped values (Baseline.Twin) for equality. One +// consumer build (Probes.Build.Consumer) holds every row's declaration in a file of its own: a value object of shape.kind over +// shape.wrapped at shape.site, over a chain of base records (shape.inheritance: the base-member on the topmost of 1, 2 or 3 bases, or on +// the direct base beside an explicit IEquatable of its own or an explicit member of another interface), used at shape.position of a +// shape.containing type. +// names-each-base-member: the file reports CMTK1023 once, for that value object, naming each equality member its bases declare and +// nothing else; +// a-warning-and-generated: the file reports no error, CMTK1023 is a warning, and the value object is generated (its From or TryFrom +// is in the built assembly); +// generated-equality-never-calls-the-base: in this process, the half of the sentence a run can show: over a base that declares the +// base-member by reference, the generated equality is the underlying value's (one value object per base-member, built by this +// project's own weaving). It reads base-member alone, since the row's shape is not what it runs. +// The row's settings are read inside the checks that run them (the consumer's two), from the rows file's copy of the row. +using Probes.Build; +using Baseline = Probes.Shapes.Baseline; +using NoteKeys = Probes.Shapes.NoteKeys; +namespace Probes; + +#pragma warning disable CMTK1023, CS8851 // the bases' own equality is the point; CS8851: the Equals base has no GetHashCode, on purpose +public enum C18TwinFault { No } +public abstract record C18EqualsBase { public virtual bool Equals(C18EqualsBase? other) => ReferenceEquals(this, other); } +public abstract record C18HashBase { public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this); } +public abstract record C18EquatableBase : IEquatable<C18EquatableBase> { bool IEquatable<C18EquatableBase>.Equals(C18EquatableBase? other) => ReferenceEquals(this, other); } +public sealed partial record C18EqualsTwin : C18EqualsBase, CodoMetis.TypeKit.ValueObjects.IValidatedValue<C18EqualsTwin, int, C18TwinFault> +{ public static CodoMetis.TypeKit.Result<C18EqualsTwin, C18TwinFault> Create(int value) => new C18EqualsTwin(value); } +public sealed partial record C18HashTwin : C18HashBase, CodoMetis.TypeKit.ValueObjects.IValidatedValue<C18HashTwin, int, C18TwinFault> +{ public static CodoMetis.TypeKit.Result<C18HashTwin, C18TwinFault> Create(int value) => new C18HashTwin(value); } +public sealed partial record C18EquatableTwin : C18EquatableBase, CodoMetis.TypeKit.ValueObjects.IValidatedValue<C18EquatableTwin, int, C18TwinFault> +{ public static CodoMetis.TypeKit.Result<C18EquatableTwin, C18TwinFault> Create(int value) => new C18EquatableTwin(value); } +#pragma warning restore CMTK1023, CS8851 + +public sealed class C18Cmtk1023Probe +{ + const string RowsFile = "C18Cmtk1023Probe"; // the probe's own rows + + public static TheoryData<string, int> Data() => Probes.Shapes.Rows.Data(RowsFile); + + [Theory, MemberData(nameof(Data))] + public Task Probe(string probe, int idx) => Probes.Shapes.Rec.Run(probe, idx, Check); + + /// <summary>A row as it is declared: the excluded values it carries replaced by the nearest that exists, each named.</summary> + sealed record Shape(string Kind, string Site, string Inheritance, string Wrapped, string Member, string Containing, string Position, List<(string Axis, string Value, string Why)> Substituted); + + static Shape Of(Probes.Shapes.Row r) + { + var subs = new List<(string, string, string)>(); + var kind = r["shape.kind"]; + if (kind is not ("sealed-record-class" or "validated-class")) { subs.Add(("shape.kind", "sealed-record-class", "a record struct or a closed set has no base record (excluded)")); kind = "sealed-record-class"; } + var site = r["shape.site"]; + if (site == "in-generic-type") { subs.Add(("shape.site", "nested-in-class", "a value object in a generic type is CMTK1005 (excluded)")); site = "nested-in-class"; } + var inheritance = r["shape.inheritance"]; + if (inheritance == "none") { subs.Add(("shape.inheritance", "base-depth-1", "with no base there is no base member (excluded)")); inheritance = "base-depth-1"; } + return new(kind, site, inheritance, r["shape.wrapped"], r["base-member"], r["shape.containing"], r["shape.position"], subs); + } + + static int Depth(string inheritance) => inheritance switch { "base-depth-2" => 2, "base-depth-3" => 3, _ => 1 }; + + /// <summary>The members CMTK1023 must name, as fragments of its message: the base-member, and the direct base's explicit IEquatable.</summary> + static List<string> Named(Shape s, string id) + { + var q = Nesting(s, id); + var top = $"{q}B{id}_{Depth(s.Inheritance)}"; + var named = new List<string> + { + s.Member switch + { + "Equals" => $"{top}.Equals({top}?)", + "GetHashCode" => $"{top}.GetHashCode()", + "IEquatable" => $"IEquatable<{top}>.Equals({top}?)", + _ => throw new ArgumentException(s.Member), + }, + }; + if (s.Inheritance == "base-explicit-IEquatable" && s.Member != "IEquatable") named.Add($"IEquatable<{q}B{id}_1>.Equals({q}B{id}_1?)"); + return named; + } + + static string Source(Shape s, string id) + { + var depth = Depth(s.Inheritance); + var body = new System.Text.StringBuilder(); + if (s.Inheritance == "explicit-interface-member") body.AppendLine($"public interface IMark{id} {{ int Mark(); }}"); + for (var d = depth; d >= 1; d--) + { + var name = $"B{id}_{d}"; + var interfaces = new List<string>(); + var members = new List<string>(); + if (d == depth) + switch (s.Member) + { + case "Equals": members.Add($"public virtual bool Equals({name}? other) => ReferenceEquals(this, other);"); break; + case "GetHashCode": members.Add("public override int GetHashCode() => 0;"); break; + case "IEquatable": interfaces.Add($"IEquatable<{name}>"); members.Add($"bool IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"); break; + } + if (d == 1 && s.Inheritance == "base-explicit-IEquatable" && s.Member != "IEquatable") + { interfaces.Add($"IEquatable<{name}>"); members.Add($"bool IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"); } + if (d == 1 && s.Inheritance == "explicit-interface-member") { interfaces.Add($"IMark{id}"); members.Add($"int IMark{id}.Mark() => 0;"); } + var parents = (d == depth ? new List<string>() : [$"B{id}_{d + 1}"]).Concat(interfaces).ToList(); + body.AppendLine($"public abstract record {name}{(parents.Count > 0 ? " : " + string.Join(", ", parents) : "")} {{ {string.Join(" ", members)} }}"); + } + var w = Src.Wrapped(s.Wrapped, id); + body.AppendLine(s.Kind == "validated-class" + ? $"public enum VF{id} {{ No }}\npublic sealed partial record V{id} : B{id}_1, IValidatedValue<V{id}, {w}, VF{id}>\n{{\n public static Result<V{id}, VF{id}> Create({w} value) => new V{id}(value);\n}}" + : $"public sealed partial record V{id} : B{id}_1, IValue<{w}>;"); + body.AppendLine(Container(s.Containing, s.Position, $"C{id}", $"V{id}")); + return Src.Usings + $"namespace C18R{id};\n" + Src.WrappedDeclarations(id) + Src.AtSite(s.Site, id, body.ToString()) + "\n"; + } + + /// <summary>shape.containing using the value object at shape.position; no initializers, so a struct needs no constructor.</summary> + static string Container(string containing, string position, string name, string v) + { + if (position == "record-ctor-parameter") + { + var record = containing is "record-class" or "record-struct"; + return Src.ContainerHead(containing, name, record ? $"({v} K)" : $"({v} k)") + (record ? ";" : $" {{ public {v} K => k; }}"); + } + var member = position switch + { + "root" => $"public static {v}? Root() => null;", + "property" => $"public {v}? P {{ get; set; }}", + "nullable-property" => $"public {v}? N {{ get; set; }}", + "field" => $"public {v}? F;", + "list-element" => $"public List<{v}>? L {{ get; set; }}", + "array-element" => $"public {v}[]? A {{ get; set; }}", + "immutable-array-element" => $"public ImmutableArray<{v}> I {{ get; set; }}", + "read-only-list-element" => $"public IReadOnlyList<{v}>? R {{ get; set; }}", + "dictionary-key" => $"public Dictionary<{v}, int>? D {{ get; set; }}", + "dictionary-value" => $"public Dictionary<int, {v}>? D {{ get; set; }}", + "required-init" => $"public required {v} K {{ get; init; }}", + "in-patch" => $"public Patch<{v}> Pa {{ get; set; }}", + "polymorphic-derived-property" => $"public abstract class PB {{ }}\n public sealed class PD : PB {{ public {v}? K {{ get; set; }} }}", + _ => throw new ArgumentException("shape.position " + position), + }; + return $"{Src.ContainerHead(containing, name)}\n{{\n {member}\n}}"; + } + + /// <summary>The site's nesting as the diagnostic writes a type: its containing types, dotted.</summary> + static string Nesting(Shape s, string id) => s.Site switch + { + "top-level" => "", "nested-in-class" or "nested-in-struct" => $"O{id}.", "nested-two-levels" => $"O{id}.P{id}.", _ => throw new ArgumentException(s.Site), + }; + + /// <summary>The value object's metadata name: the row's namespace, then the site's nesting.</summary> + static string MetadataName(Shape s, string id) => $"C18R{id}." + s.Site switch + { + "top-level" => "", "nested-in-class" or "nested-in-struct" => $"O{id}+", "nested-two-levels" => $"O{id}+P{id}+", _ => throw new ArgumentException(s.Site), + } + $"V{id}"; + + /// <summary> + /// Two value objects holding 42 over a base declaring the member by reference, and one holding 43: equal, of one hash, and unequal, + /// as the wrapped values 42, 42 and 43 are. + /// </summary> + static string? Twin(string member) + { + (object A, object B, object C, object BaseA, object BaseB) v = member switch + { + "Equals" => (C18EqualsTwin.FromKnownGood(42), C18EqualsTwin.FromKnownGood(42), C18EqualsTwin.FromKnownGood(43), (C18EqualsBase)C18EqualsTwin.FromKnownGood(42), (C18EqualsBase)C18EqualsTwin.FromKnownGood(42)), + "GetHashCode" => (C18HashTwin.FromKnownGood(42), C18HashTwin.FromKnownGood(42), C18HashTwin.FromKnownGood(43), C18HashTwin.FromKnownGood(42), C18HashTwin.FromKnownGood(42)), + "IEquatable" => (C18EquatableTwin.FromKnownGood(42), C18EquatableTwin.FromKnownGood(42), C18EquatableTwin.FromKnownGood(43), C18EquatableTwin.FromKnownGood(42), C18EquatableTwin.FromKnownGood(42)), + _ => throw new ArgumentException(member), + }; + int a = 42, b = 42, c = 43; + if (v.A.Equals(v.B) != a.Equals(b) || (v.A.GetHashCode() == v.B.GetHashCode()) != (a.GetHashCode() == b.GetHashCode())) + return $"over a base declaring {member} by reference, two value objects holding 42 are unequal or hash apart: the generated equality called the base's"; + if (v.A.Equals(v.C) != a.Equals(c)) return $"over a base declaring {member}, value objects holding 42 and 43 are equal"; + if (member == "IEquatable" && ((IEquatable<C18EquatableBase>)v.BaseA).Equals((C18EquatableBase)v.BaseB) != a.Equals(b)) return "the base's explicit IEquatable<TBase> compares two value objects holding 42 by reference"; + return null; + } + + static Task<ConsumerOutcome> Built() => Consumer.Build(RowsFile, () => + Probes.Shapes.Rows.Load(RowsFile).ToDictionary(row => $"R{row.Index}.cs", row => Source(Of(row), row.Index.ToString()))); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + // The row as the rows file holds it, read without marking the run's row: each check marks what it ran. + var s = Of(Probes.Shapes.Rows.Get(RowsFile, r.Index)); + var id = r.Index.ToString(); + var build = await Built(); + var file = $"R{id}.cs"; + var mine = build.In(file).ToList(); + var errors = mine.Where(d => d.Severity == "error").ToList(); + if (errors.Any(e => e.Id.StartsWith("CS"))) + { r.Outcome("harness-error", "the probe's declaration does not compile: " + string.Join("; ", errors.Select(e => $"{e.Id} {e.Message}"))); return null; } + var reported = mine.Where(d => d.Id == "CMTK1023").ToList(); + + // Every check runs, so a seed that breaks one obligation is seen by its own check whatever the others find. + var named = r.Check("names-each-base-member", Baseline.Docs("src/CodoMetis.TypeKit.Generators/README.md#equality"), c => { Ran(c, s); return NamesEach(c, s, id, file, reported); }); + var generated = r.Check("a-warning-and-generated", Baseline.Docs("src/CodoMetis.TypeKit.Generators/README.md#build-errors"), c => { Ran(c, s); return WarningAndGenerated(c, s, id, build, errors, reported); }); + var equality = r.Check("generated-equality-never-calls-the-base", Baseline.Twin, c => Equality(c)); + return named ?? generated ?? equality; + } + + /// <summary>Marks the axes the consumer's declaration was written from read for <paramref name="c"/>, at the value that ran.</summary> + static void Ran(Probes.Shapes.Check c, Shape s) + { + foreach (var axis in (string[])["shape.kind", "shape.site", "shape.inheritance", "shape.wrapped", "base-member", "shape.containing", "shape.position"]) _ = c[axis]; + foreach (var (axis, value, why) in s.Substituted) c.Substituted(axis, value, why); + } + + static string? NamesEach(Probes.Shapes.Check c, Shape s, string id, string file, List<BuildDiagnostic> reported) + { + if (reported.Count != 1) return $"CMTK1023 reported {reported.Count} times in {file}, expected once"; + var d = reported[0]; + if (!d.Message.StartsWith($"'{Nesting(s, id)}V{id}' inherits ", StringComparison.Ordinal)) + return "CMTK1023 does not name the value object: " + d.Message; + var named = Named(s, id); + foreach (var name in named) + if (!d.Message.Contains(name, StringComparison.Ordinal)) return $"CMTK1023 does not name {name}: {d.Message}"; + var count = System.Text.RegularExpressions.Regex.Matches(d.Message, " in its base type ").Count; + if (count != named.Count) return $"CMTK1023 names {count} members, the bases declare {named.Count} ({string.Join(", ", named)}): {d.Message}"; + if (d.Message.Contains("Mark", StringComparison.Ordinal)) return "CMTK1023 names the explicit member of another interface: " + d.Message; + c.Note(NoteKeys.Compared, $"{named.Count} members named"); + return null; + } + + static string? WarningAndGenerated(Probes.Shapes.Check c, Shape s, string id, ConsumerOutcome build, List<BuildDiagnostic> errors, List<BuildDiagnostic> reported) + { + if (errors.Count > 0) return "the value object is not generated: " + string.Join("; ", errors.Select(e => $"{e.Id} {e.Message}")); + if (reported.Count == 0) return "no CMTK1023 warning: the build reports none for the value object"; + if (reported.FirstOrDefault(d => d.Severity != "warning") is { } other) return $"CMTK1023 is reported as {other.Severity}, documented a warning"; + if (build.Methods.Count == 0) + { c.Outcome("harness-error", $"the consumer built no assembly (exit {build.ExitCode}): " + string.Join("; ", build.Diagnostics.Where(x => x.Severity == "error").Take(3).Select(x => $"{Path.GetFileName(x.File)} {x.Id} {x.Message}"))); return null; } + var type = MetadataName(s, id); + if (!build.Methods.TryGetValue(type, out var methods)) { c.Outcome("harness-error", $"no type {type} in the consumer"); return null; } + var entry = s.Kind == "validated-class" ? "TryFrom" : "From"; + if (!methods.Contains(entry)) return $"the value object is not generated: {type} has no {entry} ({string.Join(", ", methods.Order())})"; + c.Note(NoteKeys.Compared, $"a warning, and {type} has {entry}"); + return null; + } + + static string? Equality(Probes.Shapes.Check c) + { + var member = c["base-member"]; + if (Twin(member) is { } twin) return twin; + c.Note(NoteKeys.Compared, $"over a base declaring {member}, equality is the wrapped value's"); + return null; + } +} diff --git a/tools/probe/corpus/probes/C19Cmtk0010KindsProbe.cs b/tools/probe/corpus/probes/C19Cmtk0010KindsProbe.cs new file mode 100644 index 00000000..349e1ea1 --- /dev/null +++ b/tools/probe/corpus/probes/C19Cmtk0010KindsProbe.cs @@ -0,0 +1,504 @@ +// Claim C19-cmtk0010-kinds (docs/type-constraints.md#the-kind-of-a-type-argument, with its "Two parameters apart" and the list of +// what the rule checks): [Interface], [Unsealed], [ValueObject], [ClosedSet], [FlagsEnum], [ReadOnlyStruct], [Distinct] and [Unrelated] +// reject the kinds the page's table names, CMTK0010 (Error), "at the methods the compiler calls for you: a foreach's GetEnumerator, an +// await's GetAwaiter, a Deconstruct (in var (a, b) = x, a foreach, or a pattern like x is (1, 2)), and a collection expression's Add or +// builder method ... every other attribute is [checked]", and at a call; "An inferred one is reported at the call ... the foreach's +// collection, the await, the deconstruction, the pattern or the collection expression"; a pair's breach "once, at the later argument". +// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104), the row's attribute +// selecting one of the first four and a site other than a call the fifth: +// interface-and-unsealed, value-object-and-closed-set, flags-enum-and-read-only-struct (attribute): the page's sentences on the pairs +// of kinds; a-pair-once-at-the-later-argument (Distinct, Unrelated): "A breach is reported once, at the later argument."; +// methods-the-compiler-calls (site foreach, await, Deconstruct, collection-expression): "**Methods the compiler calls for you:** ..." +// Oracle: the page (Baseline.Docs): its table ("Accepts") over what each argument is, and its list of what the rule checks. Each row +// compiles, with the CMTK0010 analyzer: +// - the catalogue (test/CodoMetis.TypeKit.Analyzers.Tests/Variants: TypeArgumentSites' subjects, its calls with written and inferred +// arguments and its uses of the row's site, TypeArgumentKinds' candidates), with one argument that breaks the attribute and one that +// meets it (a pair attribute: subjects of two parameters at the same sites), plus a collection expression's builder method, which +// the catalogue has no use of; +// - the row's shapes as arguments at the same calls and sites: the value object or closed set of shape.kind over shape.wrapped at +// shape.site, its base and its explicit interface (shape.inheritance), the wrapped type itself, and a declaration of +// shape.refused-kind (one that compiles without the generators; the two that do not are excluded); each use hosted in a member of +// shape.containing at shape.position. +// The attribute's check judges the calls: every breaking use reported exactly once at its span, no meeting one, nothing anywhere else. +// The methods' check judges the uses at the row's site against each argument's call with inferred arguments in the same compilation: +// reported once at its span where that call is reported, silent where it is silent; so a kind the rule misjudges fails the attribute's +// check alone, and a site the rule does not check fails the methods' check alone. +using Probes.Build; +using Baseline = Probes.Shapes.Baseline; +using NoteKeys = Probes.Shapes.NoteKeys; +using CodoMetis.TypeKit.Analyzers.Tests.Variants; +namespace Probes; + +public sealed class C19Cmtk0010KindsProbe +{ + const string RowsFile = "C19Cmtk0010KindsProbe"; // the probe's own rows + + public static TheoryData<string, int> Data() => Probes.Shapes.Rows.Data(RowsFile); + + [Theory, MemberData(nameof(Data))] + public Task Probe(string probe, int idx) => Probes.Shapes.Rec.Run(probe, idx, Check); + + /// <summary>What a type is, as the page's table reads it.</summary> + sealed record Facts(bool Interface = false, bool Class = false, bool Sealed = false, bool Struct = false, bool ReadOnly = false, bool Enum = false, bool Flags = false, bool ValueObject = false, bool ClosedSet = false); + + /// <summary>The table's "Accepts" column.</summary> + static bool Accepts(string attribute, Facts f) => attribute switch + { + "Interface" => f.Interface, // an interface + "Unsealed" => f.Class && !f.Sealed, // a class or record class that can be derived from, abstract included + "ValueObject" => f.ValueObject && !f.Interface, // a struct or class that implements IValue<T> for any T + "ClosedSet" => f.ClosedSet, // a type that implements IClosedSet<TSelf> + "FlagsEnum" => f.Enum && f.Flags, // an enum marked [Flags] + "ReadOnlyStruct" => f.Struct && f.ReadOnly && !f.Enum, // a readonly struct or readonly record struct, the primitives included + _ => throw new ArgumentException(attribute), + }; + + static Facts OfKind(string kind) => kind switch + { + "record-struct" or "validated-struct" => new(Struct: true, ReadOnly: true, Sealed: true, ValueObject: true), + "sealed-record-class" or "validated-class" => new(Class: true, Sealed: true, ValueObject: true), + "closed-set" => new(Class: true, Sealed: true, ClosedSet: true), + _ => throw new ArgumentException(kind), + }; + + static Facts OfRefused(string refused) => refused switch + { + "unsealed-record-class (CMTK1006)" => new(Class: true, ValueObject: true), + "over-value-object (CMTK1005)" => new(Class: true, Sealed: true, ValueObject: true), + "generic-wrapped (CMTK1005)" => new(Struct: true, ReadOnly: true, Sealed: true, ValueObject: true), + "abstract (CMTK1005)" => new(Class: true, ValueObject: true), + "over-object (CMTK1005)" => new(Class: true, Sealed: true, ValueObject: true), + "over-System.Enum (CMTK1005)" => new(Struct: true, ReadOnly: true, Sealed: true, ValueObject: true), + "named-as-generated-member (CMTK1005)" => new(Struct: true, ReadOnly: true, Sealed: true, ValueObject: true), + _ => throw new ArgumentException(refused), + }; + + static readonly Dictionary<string, Type> Runtime = new() + { + ["string"] = typeof(string), ["Guid"] = typeof(Guid), ["int"] = typeof(int), ["long"] = typeof(long), ["byte"] = typeof(byte), ["ulong"] = typeof(ulong), + ["decimal"] = typeof(decimal), ["double"] = typeof(double), ["bool"] = typeof(bool), ["char"] = typeof(char), ["DateTime"] = typeof(DateTime), + ["DateTimeOffset"] = typeof(DateTimeOffset), ["DateOnly"] = typeof(DateOnly), ["TimeOnly"] = typeof(TimeOnly), ["TimeSpan"] = typeof(TimeSpan), + ["Uri"] = typeof(Uri), ["NodaTime.Instant"] = typeof(NodaTime.Instant), + }; + + /// <summary>The wrapped type: the ones declared in the row's source by what they are declared as, the framework's by reflection.</summary> + static Facts OfWrapped(string wrapped) + { + switch (wrapped) + { + case "enum": return new(Enum: true, Struct: true, Sealed: true); + case "flags-enum": return new(Enum: true, Flags: true, Struct: true, Sealed: true); + case "own-record-class": return new(Class: true, Sealed: true); + } + var t = Runtime[wrapped]; + // A library for netstandard2.0 (NodaTime) carries its own IsReadOnlyAttribute, so it is read by its full name. + var readOnly = t.IsValueType && t.GetCustomAttributesData().Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute"); + return new(Class: t.IsClass, Sealed: t.IsSealed, Struct: t.IsValueType, ReadOnly: readOnly || t.IsPrimitive || t == typeof(decimal)); + } + + static readonly string[] Pair = ["Distinct", "Unrelated"]; + + /// <summary>The catalogue's uses of the claim's site value.</summary> + static string[] CatalogueSites(string site) => site switch + { + "call" => ["a call with written arguments", "a call with inferred arguments"], + "foreach" => ["a foreach's GetEnumerator"], + "await" => ["an await's GetAwaiter"], + "Deconstruct" => ["a deconstruction", "a positional pattern"], + "collection-expression" => ["a collection expression's Add"], + _ => throw new ArgumentException(site), + }; + + /// <summary>A builder for a collection expression, the "or builder" the page names; one parameter and two.</summary> + const string Builders = + """ + public static class ProbeBuilders + { + public static Built<T> Create<{C} T>(ReadOnlySpan<T> items) => new(); + } + [System.Runtime.CompilerServices.CollectionBuilder(typeof(ProbeBuilders), nameof(ProbeBuilders.Create))] + public sealed class Built<T> : IEnumerable<T> + { + public IEnumerator<T> GetEnumerator() => throw new NotSupportedException(); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + + """; + + /// <summary>The catalogue's sites with two constrained parameters, for [Distinct] and [Unrelated].</summary> + const string PairSubjects = + """ + public static class PairRunner { public static void Run<{C} T1, {C} T2>(T1 first, T2 second) { } } + public sealed class Bag2<T1, T2> { } + public sealed class Heap2<T1, T2> : IEnumerable<T1> + { + public IEnumerator<T1> GetEnumerator() => throw new NotSupportedException(); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + public static class PairPatterns + { + public static Cursor<T1> GetEnumerator<{C} T1, {C} T2>(this Bag2<T1, T2> bag) => default; + public static Waiter<T1> GetAwaiter<{C} T1, {C} T2>(this Bag2<T1, T2> bag) => default; + public static void Deconstruct<{C} T1, {C} T2>(this Bag2<T1, T2> bag, out int first, out int second) => (first, second) = (0, 0); + public static void Add<{C} T1, {C} T2>(this Heap2<T1, T2> heap, T1 item) { } + } + public static class PairBuilders + { + public static Built2<T1, T2> Create<{C} T1, {C} T2>(ReadOnlySpan<T1> items) => new(); + } + [System.Runtime.CompilerServices.CollectionBuilder(typeof(PairBuilders), nameof(PairBuilders.Create))] + public sealed class Built2<T1, T2> : IEnumerable<T1> + { + public IEnumerator<T1> GetEnumerator() => throw new NotSupportedException(); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } + public delegate System.Threading.Tasks.Task GoP<TF, TS>(TF x, TS y, Bag2<TF, TS> bag2); + public delegate System.Threading.Tasks.Task GoD<TG>(TG a, Holder? holder, Bag<TG> bag, Crate<TG> crate, object o); + + """; + + /// <summary>The statements of a pair site: x and y of the two arguments, bag2 of both. {F} and {S} are the arguments.</summary> + static string[] PairStatements(string site) => site switch + { + "call" => ["PairRunner.Run<{F}, {|#0:{S}|}>(x, y);", "{|#0:PairRunner.Run(x, y)|};"], + "foreach" => ["foreach (var item in {|#0:bag2|}) { }"], + "await" => ["_ = {|#0:await bag2|};"], + "Deconstruct" => ["{|#0:var (p, q) = bag2|};", "_ = bag2 is {|#0:(1, 2)|};"], + "collection-expression" => ["Heap2<{F}, {S}> heap = {|#0:[x]|};", "Built2<{F}, {S}> built = {|#0:[x]|};"], + _ => throw new ArgumentException(site), + }; + + sealed class Uses + { + int n; + public readonly HashSet<string> Expected = new(); + public readonly List<string> All = new(); + /// <summary>The uses at a call, written or inferred: what the attribute's check judges.</summary> + public readonly HashSet<string> Calls = new(); + /// <summary>Each argument's call with inferred arguments, the baseline of its uses at the row's site.</summary> + public readonly Dictionary<string, string> InferredCall = new(); + /// <summary>The uses at the row's site when it is not a call, each with its argument: what the methods' check judges.</summary> + public readonly Dictionary<string, string> AtSite = new(); + public readonly System.Text.StringBuilder Core = new(); + /// <summary>Relabels a statement's {|#0:…|} with a label of its own; its breaking or meeting is recorded.</summary> + public string Label(string statements, bool breaking) => Label(statements, breaking, out _); + string Label(string statements, bool breaking, out string l) + { + l = "u" + n++; All.Add(l); if (breaking) Expected.Add(l); + return statements.Replace("{|#0:", "{|#" + l + ":"); + } + /// <summary>A use at a call of <paramref name="argument"/>, the inferred one its uses at the site are compared with.</summary> + public string Call(string statements, bool breaking, string argument, bool inferred) + { + var s = Label(statements, breaking, out var l); + Calls.Add(l); + if (inferred) InferredCall[argument] = l; + return s; + } + /// <summary>A use of <paramref name="argument"/> at the row's site, a method the compiler calls.</summary> + public string Site(string statements, bool breaking, string argument) + { + var s = Label(statements, breaking, out var l); + AtSite[l] = argument; + return s; + } + public int Next => n++; + } + + static (string Prefix, string Suffix) BodyFrame() + { + var t = TypeArgumentSites.Body("\u0001"); + var at = t.IndexOf('\u0001'); + return (t[..at], t[(at + 1)..]); + } + + static string StatementsOf(string siteName) + { + var use = TypeArgumentSites.All.Single(s => s.Name == siteName).Use; + var (prefix, suffix) = BodyFrame(); + if (!use.StartsWith(prefix, StringComparison.Ordinal) || !use.EndsWith(suffix, StringComparison.Ordinal)) + throw new InvalidOperationException($"the catalogue's use '{siteName}' is not written through TypeArgumentSites.Body"); + return use[prefix.Length..^suffix.Length]; + } + + const string Inferred = "a call with inferred arguments"; + + /// <summary>The obligation of each attribute's row (claims.json).</summary> + static readonly Dictionary<string, string> ObligationOf = new() + { + ["Interface"] = "interface-and-unsealed", ["Unsealed"] = "interface-and-unsealed", + ["ValueObject"] = "value-object-and-closed-set", ["ClosedSet"] = "value-object-and-closed-set", + ["FlagsEnum"] = "flags-enum-and-read-only-struct", ["ReadOnlyStruct"] = "flags-enum-and-read-only-struct", + ["Distinct"] = "a-pair-once-at-the-later-argument", ["Unrelated"] = "a-pair-once-at-the-later-argument", + }; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + // The row as the rows file holds it, read without marking the run's row: each check marks the axes it ran, so the attribute's + // check, judged at the calls, is credited with the row's site only where the site is a call. + var row = Probes.Shapes.Rows.Get(RowsFile, r.Index); + var read = new List<string>(); + var substituted = new List<(string Axis, string Value, string Why)>(); + string Read(string axis) { read.Add(axis); return row[axis]; } + + var attribute = Read("attribute"); var site = Read("site"); + var pair = Pair.Contains(attribute); + var constraint = $"[{attribute}]"; + var uses = new Uses(); + var id = r.Index.ToString(); + var atCall = site == "call"; + + // The catalogue, with an argument that breaks the attribute and one that meets it, each used at a call, written and inferred, + // and, where the row's site is not a call, at the site. + if (!pair) + { + var (breaking, meeting) = attribute switch + { + "Interface" => ("OrderHandler", "IHandler"), + "Unsealed" => ("OrderHandler", "Handler"), + "ValueObject" => ("Guid", "Id"), + "ClosedSet" => ("Id", "Palette"), + "FlagsEnum" => ("Shade", "Rights"), + "ReadOnlyStruct" => ("Mutable", "Frozen"), + _ => throw new ArgumentException(attribute), + }; + var k = 0; + foreach (var (arg, breaks) in new[] { (breaking, true), (meeting, false) }) + { + var argument = "catalogue:" + arg; + string Use(string use) => use.Replace("{A}", arg).Replace("public static class Use", $"public static class Use{k++}"); + foreach (var name in CatalogueSites("call")) + uses.Core.AppendLine(uses.Call(Use(TypeArgumentSites.All.Single(s => s.Name == name).Use), breaks, argument, name == Inferred)); + if (atCall) continue; + foreach (var name in CatalogueSites(site)) + uses.Core.AppendLine(uses.Site(Use(TypeArgumentSites.All.Single(s => s.Name == name).Use), breaks, argument)); + if (site == "collection-expression") + uses.Core.AppendLine(uses.Site(Use(TypeArgumentSites.Body("Built<{A}> built = {|#0:[a]|};")), breaks, argument)); + } + } + else + { + // [Unrelated] also keeps a type and its base apart; [Distinct] only one type twice. + var cases = new List<(string F, string S, bool Breaks)> { ("int", "string", false), ("Placed", "Placed", true), ("Handler", "OrderHandler", attribute == "Unrelated") }; + var k = 0; + foreach (var (f, s, breaks) in cases) + { + var argument = $"catalogue:{f},{s}"; + var calls = PairStatements("call"); + for (var i = 0; i < calls.Length; i++) + uses.Core.AppendLine(uses.Call(PairHost("root", "class", $"Pair{k++}", f, s, calls[i]), breaks, argument, inferred: i == 1)); + if (atCall) continue; + foreach (var statement in PairStatements(site)) + uses.Core.AppendLine(uses.Site(PairHost("root", "class", $"Pair{k++}", f, s, statement), breaks, argument)); + } + } + + // The row's shapes. + var kind = Read("shape.kind"); var inheritance = Read("shape.inheritance"); + if (Src.IsStructKind(kind) && inheritance is not ("none" or "explicit-interface-member")) + { + substituted.Add(("shape.inheritance", "none", "a record struct has no base type (excluded pair)")); + inheritance = "none"; + } + var depth = inheritance switch { "base-depth-1" or "base-explicit-IEquatable" => 1, "base-depth-2" => 2, "base-depth-3" => 3, _ => 0 }; + var decl = new System.Text.StringBuilder(); + var record = kind != "closed-set"; + for (var d = depth; d >= 1; d--) + { + var name = $"DB{id}_{d}"; var parent = d == depth ? "" : $" : DB{id}_{d + 1}"; + var equatable = d == 1 && inheritance == "base-explicit-IEquatable"; + if (equatable) parent = parent.Length == 0 ? $" : IEquatable<{name}>" : parent + $", IEquatable<{name}>"; + decl.AppendLine($"public abstract {(record ? "record" : "class")} {name}{parent} {{ {(equatable ? $"bool IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);" : "")} }}"); + } + var explicitMember = inheritance == "explicit-interface-member"; + var mark = explicitMember ? $", IMark{id}" : ""; + var markMember = explicitMember ? $" int IMark{id}.Mark() => 0;" : ""; + if (explicitMember) decl.AppendLine($"public interface IMark{id} {{ int Mark(); }}"); + var baseList = depth > 0 ? $"DB{id}_1, " : ""; + string? wrapped = null; + if (kind == "closed-set") + decl.AppendLine($$""" + public sealed partial class D{{id}} : {{baseList}}IClosedSet<D{{id}}>{{mark}} + { + public static readonly D{{id}} A = new(); + public string Name => ""; + public string QualifiedName => ""; + public static IReadOnlyList<D{{id}}> All => [A]; + public static bool TryParse(string? name, out D{{id}} member) { member = A; return true; }{{markMember}} + } + """); + else + { + wrapped = Read("shape.wrapped"); + var w = Src.Wrapped(wrapped, id); + decl.AppendLine($"public enum DF{id} {{ No }}"); + decl.AppendLine(kind switch + { + "record-struct" => $"public readonly partial record struct D{id} : IValue<{w}>{mark} {{{markMember} }}", + "sealed-record-class" => $"public sealed partial record D{id} : {baseList}IValue<{w}>{mark} {{{markMember} }}", + "validated-struct" => $"public readonly partial record struct D{id} : IValidatedValue<D{id}, {w}, DF{id}>{mark} {{ public static Result<D{id}, DF{id}> Create({w} value) => Result.Error(DF{id}.No);{markMember} }}", + "validated-class" => $"public sealed partial record D{id} : {baseList}IValidatedValue<D{id}, {w}, DF{id}>{mark} {{ public static Result<D{id}, DF{id}> Create({w} value) => Result.Error(DF{id}.No);{markMember} }}", + _ => throw new ArgumentException(kind), + }); + } + // shape.refused-kind: refused by the generators, an ordinary declaration to the compiler. The two kinds that are not (a closed + // set named as a generated member, CS0535; a hand-written operator ==, CS0111) are excluded in claims.json, and a row the + // covering array fills with one runs over object instead. + var refused = Read("shape.refused-kind"); + if (refused is "closed-set-named-as-generated-member (CMTK1021)" or "equality-operator (CMTK1011)") + { + substituted.Add(("shape.refused-kind", "over-object (CMTK1005)", "it does not compile without the generators (excluded pair)")); + refused = "over-object (CMTK1005)"; + } + var rName = refused == "named-as-generated-member (CMTK1005)" ? Refusals.ValueObjectNames[r.Index % Refusals.ValueObjectNames.Length] : $"R{id}"; + decl.AppendLine(refused switch + { + "unsealed-record-class (CMTK1006)" => $"public partial record R{id} : IValue<string>;", + "over-value-object (CMTK1005)" => $"public readonly partial record struct RIn{id} : IValue<int>;\npublic sealed partial record R{id} : IValue<RIn{id}>;", + "generic-wrapped (CMTK1005)" => $"public readonly partial record struct R{id} : IValue<List<int>>;", + "abstract (CMTK1005)" => $"public abstract partial record R{id} : IValue<string>;", + "over-object (CMTK1005)" => $"public sealed partial record R{id} : IValue<object>;", + "over-System.Enum (CMTK1005)" => $"public readonly partial record struct R{id} : IValue<System.Enum>;", + "named-as-generated-member (CMTK1005)" => $"public readonly partial record struct {rName} : IValue<int>;", + _ => throw new ArgumentException(refused), + }); + + var containing = Read("shape.containing"); var position = Read("shape.position"); + var dFacts = OfKind(kind); + if (!pair) + { + var arguments = new List<(string Type, bool Breaks)> { ($"D{id}", !Accepts(attribute, dFacts)), (rName, !Accepts(attribute, OfRefused(refused))) }; + if (depth > 0) arguments.Add(($"DB{id}_{depth}", !Accepts(attribute, new Facts(Class: true)))); + if (explicitMember) arguments.Add(($"IMark{id}", !Accepts(attribute, new Facts(Interface: true)))); + if (wrapped is not null) arguments.Add((Src.Wrapped(wrapped, id), !Accepts(attribute, OfWrapped(wrapped)))); + foreach (var (type, breaks) in arguments) + { + var argument = "shape:" + type; + foreach (var name in CatalogueSites("call")) + decl.AppendLine(uses.Call(Host(position, containing, $"C{id}_{uses.Next}", type, StatementsOf(name)), breaks, argument, name == Inferred)); + if (atCall) continue; + foreach (var name in CatalogueSites(site)) + decl.AppendLine(uses.Site(Host(position, containing, $"C{id}_{uses.Next}", type, StatementsOf(name)), breaks, argument)); + if (site == "collection-expression") + decl.AppendLine(uses.Site(Host(position, containing, $"C{id}_{uses.Next}", type, "Built<{A}> built = {|#0:[a]|};"), breaks, argument)); + } + } + else + { + var other = depth > 0 ? $"DB{id}_{depth}" : explicitMember ? $"IMark{id}" : wrapped is not null ? Src.Wrapped(wrapped, id) : "int"; + var related = depth > 0 || explicitMember; // a base and a derived type, an interface and an implementer + var cases = new List<(string F, string S, bool Breaks)> + { + ($"D{id}", $"D{id}", true), // one type twice + (other, $"D{id}", related && attribute == "Unrelated"), + (rName, rName, true), + (rName, $"D{id}", false), + }; + foreach (var (f, s, breaks) in cases) + { + var argument = $"shape:{f},{s}"; + var calls = PairStatements("call"); + for (var i = 0; i < calls.Length; i++) + decl.AppendLine(uses.Call(PairHost(position, containing, $"C{id}_{uses.Next}", f, s, calls[i]), breaks, argument, inferred: i == 1)); + if (atCall) continue; + foreach (var statement in PairStatements(site)) + decl.AppendLine(uses.Site(PairHost(position, containing, $"C{id}_{uses.Next}", f, s, statement), breaks, argument)); + } + } + + var shapes = Src.Usings + Src.WrappedDeclarations(id) + Src.AtSite(Read("shape.site"), id, decl.ToString()); + var subjects = TypeArgumentSites.Subjects.Replace("{C}", pair ? "" : constraint); + var run = await Rz.Analyze("CMTK0010", + [ + ("Candidates.cs", TypeArgumentKinds.Candidates), ("Subjects.cs", subjects), + ("Builders.cs", Src.Usings + Builders.Replace("{C}", pair ? "" : constraint) + PairSubjects.Replace("{C}", pair ? constraint : "")), + ("Core.cs", Src.Usings + uses.Core), ("Shapes.cs", shapes), + ]); + if (run.Errors.Count > 0) { r.Outcome("harness-error", "the probe's source does not compile: " + run.ErrorText()); return null; } + if (run.AnalyzerFailures.Count > 0) return "the analyzer failed: " + string.Join("; ", run.AnalyzerFailures.Select(f => f.GetMessage())); + + void Ran(Probes.Shapes.Check c, bool withSite) + { + foreach (var axis in read) if (withSite || axis != "site") _ = c[axis]; + foreach (var (axis, value, why) in substituted) c.Substituted(axis, value, why); + } + + // The attribute's obligation, judged at the calls: each breaking argument reported once at its span, each meeting one silent, and + // nothing reported outside every use. Where the row's site is a call, that is every use of the row. + var page = pair ? "docs/type-constraints.md#two-parameters-apart" : "docs/type-constraints.md#the-kind-of-a-type-argument"; + var kinds = r.Check(ObligationOf[attribute], Baseline.Docs(page), c => + { + Ran(c, withSite: atCall); + var fail = Rz.Agree(run, uses.Expected, m => uses.Calls.Contains(m.Label), outside: true); + if (fail is null) c.Note(NoteKeys.Compared, $"{uses.Calls.Count} uses at a call, {uses.Calls.Count(uses.Expected.Contains)} expected reported"); + return fail; + }); + if (atCall) return kinds; + + // The methods the compiler calls, judged against each argument's own call with inferred arguments in the same compilation: a use + // at the row's site is reported once at its span where that call is reported, and is silent where it is silent. + var methods = r.Check("methods-the-compiler-calls", Baseline.Docs("docs/type-constraints.md#constrain-a-type-parameter"), c => + { + Ran(c, withSite: true); + var marks = run.Marks.ToDictionary(m => m.Label); + var expected = uses.AtSite.Where(u => Rz.At(run, marks[uses.InferredCall[u.Value]]).Count > 0).Select(u => u.Key).ToHashSet(); + var fail = Rz.Agree(run, expected, m => uses.AtSite.ContainsKey(m.Label), outside: false); + if (fail is null) c.Note(NoteKeys.Compared, $"{uses.AtSite.Count} uses at a {site}, {expected.Count} reported as their inferred calls are"); + return fail; + }); + return kinds ?? methods; + } + /// <summary> + /// A member of shape.containing at shape.position holding <paramref name="statements"/> in the catalogue's frame: a, holder, + /// bag, crate and o of the argument {A}. A struct is given no instance initializer and no lambda over this. + /// </summary> + static string Host(string position, string containing, string name, string argument, string statements) + { + statements = statements.Replace("{A}", argument); + var type = $"GoD<{argument}>"; + string Lambda(string head = "(a, holder, bag, crate, o)", string prelude = "") => $"async {head} => {{ {prelude}{statements} await Task.CompletedTask; }}"; + return Member(position, containing, name, type, + $"public static async Task Root({argument} a, Holder? holder, Bag<{argument}> bag, Crate<{argument}> crate, object o) {{ {statements} await Task.CompletedTask; }}", + Lambda(), $"Bag<{argument}> bag0", Lambda("(a, holder, bagX, crate, o)", "var bag = b0; ")); + } + + /// <summary>The pair sites' statements, with x, y and bag2 of the two arguments, as <see cref="Host"/> holds them.</summary> + static string PairHost(string position, string containing, string name, string first, string second, string statement) + { + var statements = statement.Replace("{F}", first).Replace("{S}", second); + var type = $"GoP<{first}, {second}>"; + string Lambda(string head = "(x, y, bag2)", string prelude = "") => $"async {head} => {{ {prelude}{statements} await Task.CompletedTask; }}"; + return Member(position, containing, name, type, + $"public static async Task Root({first} x, {second} y, Bag2<{first}, {second}> bag2) {{ {statements} await Task.CompletedTask; }}", + Lambda(), $"Bag2<{first}, {second}> bag0", Lambda("(x, y, bag2X)", "var bag2 = b0; ")); + } + + static string Member(string position, string containing, string name, string type, string root, string lambda, string primaryParameter, string primaryLambda) + { + var self = Src.ContainerSelf(containing, name); + if (position == "record-ctor-parameter") + { + var record = containing is "record-class" or "record-struct"; + return Src.ContainerHead(containing, name, $"({primaryParameter})") + + $"\n{{\n public {type} G {{ get {{ var b0 = {primaryParameter.Split(' ')[^1]}; return {primaryLambda}; }} }}\n}}"; + } + var member = position switch + { + "root" => root, + "property" => $"public static {type} P => {lambda};", + "nullable-property" => $"public static {type}? N => {lambda};", + "field" => $"public static readonly {type} F = {lambda};", + "list-element" => $"public static readonly List<{type}> L = [{lambda}];", + "array-element" => $"public static readonly {type}[] A = [{lambda}];", + "immutable-array-element" => $"public static readonly ImmutableArray<{type}> I = [{lambda}];", + "read-only-list-element" => $"public static readonly IReadOnlyList<{type}> R = [{lambda}];", + "dictionary-key" => $"public static readonly Dictionary<{type}, int> D = new() {{ [{lambda}] = 1 }};", + "dictionary-value" => $"public static readonly Dictionary<int, {type}> D = new() {{ [1] = {lambda} }};", + "required-init" => $"public required {type} K {{ get; init; }}\n public static {self} Make() => new() {{ K = {lambda} }};", + "in-patch" => $"public static Patch<{type}> Pa => Patch.Value<{type}>({lambda});", + "polymorphic-derived-property" => $"public abstract class PB {{ public abstract {type} K {{ get; }} }}\n public sealed class PD : PB {{ public override {type} K => {lambda}; }}", + _ => throw new ArgumentException("shape.position " + position), + }; + return $"{Src.ContainerHead(containing, name)}\n{{\n {member}\n}}"; + } +} diff --git a/tools/probe/corpus/probes/C20ShouldHoldProbe.cs b/tools/probe/corpus/probes/C20ShouldHoldProbe.cs new file mode 100644 index 00000000..f84da364 --- /dev/null +++ b/tools/probe/corpus/probes/C20ShouldHoldProbe.cs @@ -0,0 +1,182 @@ +// Claim C20-testing-shouldhold (src/CodoMetis.TypeKit.Testing/README.md): "ShouldHold(reason) fails unless the fault holds that reason, whichever of its types the reason belongs to. It hands back the Fault<TFault> with its detail and rule intact." +// "An uninitialized result fails every assertion." "A failure says what the outcome was" and "never prints a value". +// Oracle: claim. Held reason (with a detail and a rule) -> returns the Fault<TW> with Reason/Detail/Rule intact; wrong reason (same type other value, or a fault of another type) -> TypeKitAssertionException naming the fault it holds; default composed fault -> TypeKitAssertionException "uninitialized". +#pragma warning disable xUnit1051, CMTK0014, CMTK0010 // CMTK0010: TW is a run-time-chosen type, distinct from the fixed Alt enums by construction +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.Testing; +using Probes.Shapes; +namespace Probes; +public enum C20Alt1 { A, B } +public enum C20Alt2 { A, B } +public enum C20RuleKind { Strict } +public enum C20WEnum { One, Two } +[Flags] public enum C20WFlags { None = 0, X = 1, Y = 2 } +public sealed record C20Own(int N); +public sealed class C20ShouldHoldProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C20ShouldHoldProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static Task<string?> Check(Row r) => r["shape.wrapped"] switch + { + "string" => Go(r, "a", "b"), "Guid" => Go(r, Guid.Empty, Guid.NewGuid()), "int" => Go(r, 1, 2), "long" => Go(r, 1L, 2L), "byte" => Go<byte>(r, 1, 2), + "ulong" => Go<ulong>(r, 1, 2), "decimal" => Go(r, 1.5m, 2.5m), "double" => Go(r, 1.5, 2.5), "bool" => Go(r, true, false), "char" => Go(r, 'a', 'b'), + "DateTime" => Go(r, new DateTime(2026, 1, 1), new DateTime(2026, 1, 2)), "DateTimeOffset" => Go(r, new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2026, 1, 2, 0, 0, 0, TimeSpan.Zero)), + "DateOnly" => Go(r, new DateOnly(2026, 1, 1), new DateOnly(2026, 1, 2)), "TimeOnly" => Go(r, new TimeOnly(1, 0), new TimeOnly(2, 0)), "TimeSpan" => Go(r, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)), + "enum" => Go(r, C20WEnum.One, C20WEnum.Two), "flags-enum" => Go(r, C20WFlags.X, C20WFlags.Y), "Uri" => Go(r, new Uri("http://a/"), new Uri("http://b/")), + "NodaTime.Instant" => Go(r, NodaTime.Instant.FromUnixTimeSeconds(1), NodaTime.Instant.FromUnixTimeSeconds(2)), "own-record-class" => Go(r, new C20Own(1), new C20Own(2)), + var w => throw new ArgumentException(w), + }; + + static async Task<string?> Go<TW>(Row r, TW a, TW b) where TW : notnull + { + var arity = r["arity"]; var first = r["set"] == "first"; var wrong = r["wrong-reason"] == "yes"; + if (arity == "2") + return first ? await Run<TW, Fault<TW, C20Alt1>>(r, a, b, x => Fault<TW, C20Alt1>.Of(Held(x)), f => f.ShouldHold(a), f => f.ShouldHold(b), Fault<TW, C20Alt1>.Of(C20Alt1.A), f => f.ShouldHold(C20Alt1.A)) + : await Run<TW, Fault<C20Alt1, TW>>(r, a, b, x => Fault<C20Alt1, TW>.Of(Held(x)), f => f.ShouldHold(a), f => f.ShouldHold(b), Fault<C20Alt1, TW>.Of(C20Alt1.A), f => f.ShouldHold(C20Alt1.A)); + return first ? await Run<TW, Fault<TW, C20Alt1, C20Alt2>>(r, a, b, x => Fault<TW, C20Alt1, C20Alt2>.Of(Held(x)), f => f.ShouldHold(a), f => f.ShouldHold(b), Fault<TW, C20Alt1, C20Alt2>.Of(C20Alt2.A), f => f.ShouldHold(C20Alt2.A)) + : await Run<TW, Fault<C20Alt1, C20Alt2, TW>>(r, a, b, x => Fault<C20Alt1, C20Alt2, TW>.Of(Held(x)), f => f.ShouldHold(a), f => f.ShouldHold(b), Fault<C20Alt1, C20Alt2, TW>.Of(C20Alt1.A), f => f.ShouldHold(C20Alt1.A)); + } + + const string Detail = "probe-detail"; + + /// <summary>The fault a row holds: its reason with a detail and a rule, which ShouldHold hands back intact.</summary> + static Fault<TW> Held<TW>(TW reason) where TW : notnull => Fault.Of(reason, Detail).WithRule(C20RuleKind.Strict); + + static async Task<string?> Run<TW, TC>(Row r, TW a, TW b, Func<TW, TC> make, Func<TC, Fault<TW>> holdA, Func<TC, Fault<TW>> holdB, TC foreign, Func<TC, object> holdForeignSet) + where TW : notnull where TC : struct + { + var wrong = r["wrong-reason"] == "yes"; + // the held fault: with wrong-reason=yes the carrier holds a fault of ANOTHER type (foreign) and the test asks for TW's reason a + TC held = wrong ? foreign : make(a); + var op = r["or.operation"]; var shape = r["or.shape"]; + TC? got = await Carry(r, held, op, shape); + if (got is null) { r.Outcome("harness-error", $"the carrier gave no fault back ({shape}, {op})"); return null; } + var composed = got.Value; + Fault<TW>? hand = null; Exception? thrown = null; + try { hand = holdA(composed); } catch (Exception ex) { thrown = ex; } + var docs = Baseline.Docs("src/CodoMetis.TypeKit.Testing/README.md"); + if (op == "default-instance") + return r.Check("uninitialized-fails", docs, c => + { + if (thrown is not TypeKitAssertionException u || !u.Message.Contains("uninitialized")) return $"default composed fault: ShouldHold gave {thrown?.GetType().Name ?? "no exception"} {thrown?.Message}; expected TypeKitAssertionException 'uninitialized'"; + c.Note(NoteKeys.Compared, "uninitialized: TypeKitAssertionException"); + return null; + }); + if (wrong) + { + // the carrier holds a fault of another type, and the test asks for TW's reason a + var w1 = r.Check("fails-unless-it-holds-that-reason", docs, c => + { + if (thrown is not TypeKitAssertionException) return $"ShouldHold({a}) on a fault of another type gave {(thrown is null ? "a result: " + hand : thrown.GetType().Name)}; expected TypeKitAssertionException"; + // same type other value + try { holdB(composed); return "ShouldHold(other value) passed on a fault of another type"; } catch (TypeKitAssertionException) { } + c.Note(NoteKeys.Compared, "fails for a reason it does not hold"); + return null; + }); + var w2 = r.Check("a-failure-says-the-outcome", docs, c => + { + if (thrown is not TypeKitAssertionException e) return c.Inconclusive("no assertion failed to say anything: judged by fails-unless-it-holds-that-reason"); + if (!e.Message.Contains("Expected the fault") || e.Message.Contains("UNEXPECTED")) return "message: " + e.Message; + // the outcome: the fault the composed fault holds, as ShouldHold of its own reason hands it back + string outcome; + try { outcome = holdForeignSet(composed).ToString()!; } catch (Exception ex) { return c.Inconclusive("the held fault is not handed back by its own reason, judged by whichever-of-its-types: " + ex.GetType().Name); } + if (!e.Message.Contains(outcome)) return $"the failure does not say what the outcome was ({outcome}): {e.Message}"; + c.Note(NoteKeys.Compared, "the failure names " + outcome); + return null; + }); + var w3 = r.Check("whichever-of-its-types", docs, c => + { + // the held reason itself is still reachable by its own type + try { holdForeignSet(composed); } catch (Exception ex) { return "ShouldHold(own reason) threw " + ex.GetType().Name + ": " + ex.Message; } + c.Note(NoteKeys.Compared, "the held fault's own reason, whichever of its types"); + return null; + }); + return w1 ?? w2 ?? w3; + } + var f1 = r.Check("whichever-of-its-types", docs, c => + { + if (thrown is not null) return $"ShouldHold({a}) on a fault holding it threw {thrown.GetType().Name}: {thrown.Message}"; + if (!EqualityComparer<TW>.Default.Equals(hand!.Value.Reason, a)) return $"returned fault has reason {hand.Value.Reason}, expected {a}"; + c.Note(NoteKeys.Compared, $"the reason of the {r["set"]} of {r["arity"]} types"); + return null; + }); + var f2 = r.Check("hands-back-detail-and-rule", docs, c => + { + if (thrown is not null || hand is null) return c.Inconclusive("nothing handed back: judged by whichever-of-its-types"); + var want = Held(a); + if (hand.Value.Detail != want.Detail || hand.Value.Rule != want.Rule) return $"returned fault has detail '{hand.Value.Detail}' and rule '{hand.Value.Rule}', expected '{want.Detail}' and '{want.Rule}'"; + c.Note(NoteKeys.Compared, "detail and rule intact"); + return null; + }); + var f3 = r.Check("fails-unless-it-holds-that-reason", docs, c => + { + try { holdB(composed); return "ShouldHold(other value of the same type) passed"; } catch (TypeKitAssertionException) { } + c.Note(NoteKeys.Compared, "fails for another value of the reason's type"); + return null; + }); + return f1 ?? f2 ?? f3; + } + + // Carries the fault through the carrier and the operation, then extracts it with the Testing assertions. null = the combination has no such operation (noted inapplicable). + static async Task<TC?> Carry<TC>(Row r, TC fault, string op, string shape) where TC : struct + { + if (op == "default-instance") return default(TC); + // Operations the carrier has no member for run as Match, so the carrier's other pairs earn credit: Option has no Result.Catch (Catch returns a Result); Result<E> has no Select/SelectMany (query syntax) and no FirstOrNone (Option-only). + if ((shape == "Option" && op == "Result.Catch") || (shape == "Result-E" && op is "query-syntax" or "FirstOrNone")) + { + // One of the two pairs of the row cannot be earned; alternate which axis runs at a value that exists, so the carrier's and the operation's pairs both get credit across rows. + if (r.Index % 2 == 0) { r.Substituted("or.operation", "Match", $"{op} has no member on {shape}"); op = "Match"; } + else { r.Substituted("or.shape", "Result-T-E", $"{op} has no member on {shape}"); shape = "Result-T-E"; } + } + Result<int, TC> res = Result.Error(fault); + switch (shape) + { + case "Result-T-E" or "Task-of-Result" or "ValueTask-of-Result": + switch (op) + { + case "Match": res = res.Match<Result<int, TC>>(v => v, e => Result.Error(e)); break; + case "Bind-Map-chain": res = Result<int, TC>.Success(1).Bind(_ => res).Map(x => x + 1); break; + case "query-syntax": res = from x in res select x + 1; break; + case "Sequence-Transpose": { var seq = new[] { res }.Sequence(); var e = seq.ShouldBeError(); res = Result.Error(e); break; } + case "Result.Catch": res = Result.Catch<int>((Func<int>)(() => throw new InvalidOperationException())).MapError(_ => fault); break; + case "FirstOrNone": { var o = new[] { fault }.FirstOrNone(); res = Result.Error(o.ShouldBeSome()); break; } + case "after-await-elsewhere": await Task.Yield(); res = await Task.Run(() => res).ConfigureAwait(false); break; + default: throw new ArgumentException(op); + } + if (shape == "Task-of-Result") return await Task.FromResult(res).ConfigureAwait(false) is var t ? t.ShouldBeError() : null; + if (shape == "ValueTask-of-Result") return (await new ValueTask<Result<int, TC>>(res)).ShouldBeError(); + return res.ShouldBeError(); + case "Result-E": + { + Result<TC> re = Result.Error(fault); + switch (op) + { + case "Match": re = re.Match<Result<TC>>(() => Result<TC>.Success(), e => Result.Error(e)); break; + case "Bind-Map-chain": re = Result<TC>.Success().Bind(() => re); break; + case "Sequence-Transpose": re = new[] { re }.Sequence(); break; + case "Result.Catch": re = Result.Catch((Action)(() => throw new InvalidOperationException())).MapError(_ => fault); break; + case "after-await-elsewhere": await Task.Yield(); re = await Task.Run(() => re).ConfigureAwait(false); break; + default: throw new ArgumentException(op + " on Result-E: the row's operation is substituted above where the carrier has none"); + } + return re.ShouldBeError(); + } + default: // Option: the fault as the content of a Some + { + var opt = Option.Some(fault); + switch (op) + { + case "Match": opt = opt.Match(v => Option.Some(v), () => Option.None<TC>()); break; + case "Bind-Map-chain": opt = Option.Some(1).SelectMany(_ => opt); break; + case "query-syntax": opt = from x in opt select x; break; + case "FirstOrNone": opt = new[] { fault }.FirstOrNone(); break; + case "Sequence-Transpose": { var back = Option.Some<Result<int, TC>>(Result.Error(fault)).Transpose().ShouldBeError(); opt = Option.Some(back); break; } + case "after-await-elsewhere": await Task.Yield(); opt = await Task.Run(() => opt).ConfigureAwait(false); break; + default: throw new ArgumentException(op + " on Option: the row's operation is substituted above where the carrier has none"); + } + return opt.ShouldBeSome(); + } + } + } +} diff --git a/tools/probe/corpus/probes/C21OpenapiSchemaParityProbe.cs b/tools/probe/corpus/probes/C21OpenapiSchemaParityProbe.cs new file mode 100644 index 00000000..9a3f3e57 --- /dev/null +++ b/tools/probe/corpus/probes/C21OpenapiSchemaParityProbe.cs @@ -0,0 +1,41 @@ +// Claim C21-openapi-schema-parity (README.md#what-you-get), surfaces http, json, family http. +// Obligations (claims.json), each a check of its own (Row.Check) in the rows its axes select, and each with a seed of its own in the +// registry entry (decision 104); the-underlying-types-schema is checked where no other obligation's axes apply: +// the-underlying-types-schema: "The JSON, the database column and the OpenAPI schema are the same as the underlying type's." +// nullable-admits-null-as-the-wrapped-does (http.nullability=nullable): "Its OpenAPI schema is the one ASP.NET Core publishes for the underlying type." +// container-elements (a list's, an array's, a read-only list's, an immutable array's element, a dictionary's value): the same sentence. +// dictionary-key (shape.position=dictionary-key): the same sentence. +// every-document-version (openapi.OpenApiVersion OpenApi2_0, OpenApi3_0; the default is 3.1): the same sentence. +// Oracle: differential (HttpBaseline.Schema, the schema half BaseProbe used to run). On the shared host under the row's options, every +// operation that takes or returns the row's shape with the value object (the minimal schema endpoint, the minimal and the MVC binding +// endpoint of the row's http.binding) has the parameters, request body and 200 response of its twin with the wrapped type, every +// reference resolved, so a component compares by its content. Only the schemas are compared, never whether a parameter is required; +// a class value object is compared with the wrapped type's Nullable<T>, and where nullability is spelled two ways the markers go +// (schema:* notes). Not promised: a closed set (a string enum), excluded from the rows; and a converter of the application's own for +// the value object or the wrapped type, declared inapplicable on every obligation (claims.json), so no row is generated with one. +// Expected: no failure for any row; a document ASP.NET alone refuses the same way has nothing to compare. A difference a not-promised entry +// names ends its check as that citation (Cite.NotPromised), and a nullable class value object without a nullable twin as inconclusive. +using Probes.Shapes; +namespace Probes; +public sealed class C21OpenapiSchemaParityProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("C21OpenapiSchemaParityProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static readonly string[] Containers = ["list-element", "array-element", "immutable-array-element", "read-only-list-element", "dictionary-value"]; + + /// <summary>The obligations whose axes the row has, each checked by the same comparison against the wrapped twin.</summary> + static IEnumerable<string> Obligations(Row r) + { + var any = false; + if (r.Is("http.nullability", "nullable")) { any = true; yield return "nullable-admits-null-as-the-wrapped-does"; } + if (Containers.Contains(r["shape.position"])) { any = true; yield return "container-elements"; } + if (r.Is("shape.position", "dictionary-key")) { any = true; yield return "dictionary-key"; } + if (r["openapi.OpenApiVersion"] is "OpenApi2_0" or "OpenApi3_0") { any = true; yield return "every-document-version"; } + if (!any) yield return "the-underlying-types-schema"; + } + + /// <summary>Every obligation the row selects is checked, on one host, so a seed is seen by its own obligation's check whatever the others find.</summary> + static Task<string?> Check(Row r) => HttpBaseline.Schema(r, Registry.Of(r), Obligations(r).ToList()); +} diff --git a/tools/probe/corpus/probes/C22GeneratorRefusalsProbe.cs b/tools/probe/corpus/probes/C22GeneratorRefusalsProbe.cs new file mode 100644 index 00000000..749b1cfe --- /dev/null +++ b/tools/probe/corpus/probes/C22GeneratorRefusalsProbe.cs @@ -0,0 +1,103 @@ +// Claim C22-generator-refusals (src/CodoMetis.TypeKit.Generators/README.md#build-errors), surfaces generation, analyzers, family build. +// Obligations (claims.json), each with a check below and a seed of its own in the registry entry (decision 104): +// no-type-half-generated: "A declaration that can't be generated fails the build, so no type is left half-generated." +// every-error-links-the-table: "Every error ends with a link to this table." +// unsealed-record-class: "A record class not declared `sealed`." +// over-value-object: "or a value object (itself included). Wrap what the other value object wraps instead." +// generic-wrapped: "A generic underlying type, a tuple included, fails the build (CMTK1005)." +// abstract: "An abstract type with a value-object marker. It would get no member, and a value held as it, of a type derived from it, would have no `Value` and be written as `{}`." +// over-object: "or wrapping `object`, `System.Enum`, an array, a pointer, a nullable type, a generic type (a tuple included) or a value object (itself included)." +// over-system-enum: "or wrapping `object`, `System.Enum`, an array, a pointer, a nullable type, a generic type (a tuple included) or a value object (itself included)." +// named-as-generated-member: "named `Value` or as another member the generators add (`From`, `TryFrom`, `FromKnownGood`, `Revalidate`, `Parse`, `TryParse`, `TryFormat`, `MinValue`, `MaxValue`, `CompareTo`)" +// closed-set-named-as-generated-member: "a set itself named as one of them (a member can't have the name of its type)" +// equality-operator: "So a hand-written `Equals(TSelf)`, `GetHashCode()`, `IEquatable<TSelf>.Equals`, or `operator ==` or `!=` between two of the type, fails the build (CMTK1011)." +// Oracle: the refusal (Baseline.Refusal). Each row's file of a consumer build (harness/BuildRefusals.cs: the SDK build of a throwaway +// project in a child process, as C18 and BuildOutcomeTests build, since Metalama runs only there) declares the row's shape.refused-kind as +// a value object or a closed set of shape.kind at shape.site, over shape.inheritance and shape.wrapped, and beside it a control: the same +// declaration without what is refused. Three consumers, since a compiler error in one file hid the generators' errors in the others +// (e4b751c): the value objects, whose files hold no compiler error; the closed sets, whose refusal leaves IClosedSet's members +// unimplemented (CS0535, as BuildOutcomeTests allows); and the equality operators, which C# itself reports as CS0111. Per row: +// the refusal's own obligation: an error of the README's id that names the type, and beside it no Metalama error (an aspect that +// failed, or generated code that does not compile) and no compiler error but the documented one; +// no-type-half-generated: the build failed, the type's file reports an error, and in Metalama's transformed source the type is as it +// was written: no base type, no member and no {Name}Extensions companion added; +// every-error-links-the-table: every CMTK1xxx error of the file ends with ". See <the README's #build-errors>", and the README has +// the heading. +// A refused kind the harness does not declare, one the README's table no longer lists, and a control the generators refuse or do not +// generate are harness errors: the row would show nothing about a refusal the README states. +using Probes.Build; +using Probes.Shapes; +namespace Probes; +public sealed class C22GeneratorRefusalsProbe +{ + const string RowsFile = "C22GeneratorRefusalsProbe"; // the probe's own rows + + public static TheoryData<string, int> Data() => Rows.Data(RowsFile); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static Task<RefusalOutcome> Built(string group) => Refusals.Build($"{RowsFile}/{group}", () => + Rows.Load(RowsFile).Select(Refusals.Declare).Where(d => d.Refusal.Group == group).ToDictionary(d => d.File, d => d.Source)); + + static async Task<string?> Check(Row r) + { + var d = Refusals.Declare(r); + var build = await Built(d.Refusal.Group); + var mine = build.In(d.File).ToList(); + + // What would make the row show nothing: no transformed source, a compiler error elsewhere that hides this file's, a control + // that is refused or not generated. + if (build.Transformed.Count == 0) + { r.Outcome("harness-error", $"the consumer wrote no transformed source (exit {build.ExitCode}): " + Errors(build.Diagnostics.Where(x => x.Severity == "error").Take(3))); return null; } + var hiding = build.Diagnostics.Where(x => x.Severity == "error" && x.Id.StartsWith("CS", StringComparison.Ordinal) + && !d.Refusal.Companions.Contains(x.Id) && Path.GetFileName(x.File) != d.File).ToList(); + if (hiding.Count > 0) + { r.Outcome("harness-error", "a compiler error in another file hides the generators' errors in this one (e4b751c): " + Errors(hiding.Take(3))); return null; } + var control = d.Display[..^d.Name.Length] + d.Control; + if (mine.Where(x => x.Severity == "error" && x.Message.Contains($"'{control}'", StringComparison.Ordinal)).ToList() is { Count: > 0 } refusedControl) + { r.Outcome("harness-error", $"the control {control} is refused, so the row shows nothing about {d.Refusal.Kind}: " + Errors(refusedControl)); return null; } + if (Refusals.Added(build, d, d.Control) is null) + { r.Outcome("harness-error", $"the control {control} is not generated, so the row shows nothing about {d.Refusal.Kind}"); return null; } + + // Every check runs, so a seed that breaks one obligation is seen by its own check whatever the others find. + var named = r.Check(d.Refusal.Obligation, Baseline.Refusal, c => RefusedByName(c, d, mine)); + var whole = r.Check("no-type-half-generated", Baseline.Refusal, c => NotHalfGenerated(c, d, build, mine)); + var linked = r.Check("every-error-links-the-table", Baseline.Docs("src/CodoMetis.TypeKit.Generators/README.md#build-errors"), c => Linked(c, mine)); + return named ?? whole ?? linked; + } + + static string Errors(IEnumerable<BuildDiagnostic> errors) => string.Join("; ", errors.Select(e => $"{Path.GetFileName(e.File)} {e.Id} {e.Message}")); + + static string? RefusedByName(Probes.Shapes.Check c, RefusedDeclaration d, List<BuildDiagnostic> mine) + { + var errors = mine.Where(e => e.Severity == "error").ToList(); + if (!errors.Any(e => e.Id == d.Refusal.Id && e.Message.StartsWith($"'{d.Display}'", StringComparison.Ordinal))) + return $"{d.Display} ({d.Refusal.Kind}) is not refused with {d.Refusal.Id} by name: " + (errors.Count == 0 ? "its file reports no error" : Errors(errors)); + if (errors.Where(e => e.Id.StartsWith("LAMA", StringComparison.Ordinal)).ToList() is { Count: > 0 } aspect) + return $"beside {d.Refusal.Id}, an aspect failed on the declaration or generated code that does not compile: " + Errors(aspect); + if (errors.Where(e => e.Id.StartsWith("CS", StringComparison.Ordinal) && !d.Refusal.Companions.Contains(e.Id)).ToList() is { Count: > 0 } compiler) + return $"beside {d.Refusal.Id}, compiler errors the README does not name: " + Errors(compiler); + c.Note(NoteKeys.Compared, $"{d.Refusal.Id} names {d.Display}"); + return null; + } + + static string? NotHalfGenerated(Probes.Shapes.Check c, RefusedDeclaration d, RefusalOutcome build, List<BuildDiagnostic> mine) + { + if (build.ExitCode == 0) return $"the consumer with {d.Display} ({d.Refusal.Kind}) in it built"; + if (!mine.Any(e => e.Severity == "error")) return $"{d.Display} ({d.Refusal.Kind}) compiles: its file reports no error"; + if (Refusals.Added(build, d, d.Name) is { } added) return $"{d.Display} ({d.Refusal.Kind}) is half-generated: the generators added {added}"; + c.Note(NoteKeys.Compared, $"the build failed and {d.Display} is as written"); + return null; + } + + static string? Linked(Probes.Shapes.Check c, List<BuildDiagnostic> mine) + { + if (!Refusals.ReadmeHasBuildErrorsHeading()) return "the Generators README has no '## Build errors' heading, which the errors link to"; + var generator = mine.Where(e => e.Severity == "error" && e.Id.StartsWith("CMTK1", StringComparison.Ordinal)).ToList(); + if (generator.Count == 0) return c.Inconclusive("no generator error in the row's file to read"); + if (generator.Where(e => !e.Message.EndsWith($". See {Refusals.TableLink}", StringComparison.Ordinal)).ToList() is { Count: > 0 } bare) + return "these errors do not end with the link to the Build errors table: " + Errors(bare); + c.Note(NoteKeys.Compared, $"{generator.Count} error(s) end with the link"); + return null; + } +} diff --git a/tools/probe/corpus/probes/E01ColumnParityProbe.cs b/tools/probe/corpus/probes/E01ColumnParityProbe.cs new file mode 100644 index 00000000..c7720746 --- /dev/null +++ b/tools/probe/corpus/probes/E01ColumnParityProbe.cs @@ -0,0 +1,93 @@ +// Claim E01-column-parity (docs/ef-core.md#the-model): "Every property maps as a column of the underlying type, nullable where the property is. A facet you configure on it is kept: a precision, a length, a column type." +// Oracle: differential. The EF model and the CREATE script of an entity declared with the value object (UseTypeKit) equal those of the same entity declared with the wrapped type (plain EF), in every mapping, shape and facet. +// Obligations (claims.json), one check each where the row's axes select it, each against the wrapped twin (Baseline.Twin): +// column-type (ef.mapping property, key, owned, complex): "Every property maps as a column of the underlying type, nullable where the property is." +// nullable (ef.mapping nullable): "nullable where the property is" +// facet-kept (ef.facet HasMaxLength, HavePrecision, HasColumnType): "A facet you configure on it is kept: a precision, a length, a column type." +// Expected: no failure for any row; where a side refuses the model, EfSides says how the check ends. +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E01ColumnParityProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E01ColumnParityProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var e = Probes.Ef3.E4.At(r, r["shape.kind"], r["shape.wrapped"], r["shape.site"]); + var mapping = r["ef.mapping"]; + var facet = r["ef.facet"]; + // The facet as configured, on any wrapped type: EF keeps a length or a precision on a property of any type, and the + // wrapped twin shows what it keeps for that type. + var columnType = EfModel.ColumnTypes[e.Wrapped]; + Action<Microsoft.EntityFrameworkCore.Metadata.IMutableProperty>? apply = facet switch + { + "HasMaxLength" => p => p.SetMaxLength(5), + "HavePrecision" => p => { p.SetPrecision(18); p.SetScale(2); }, + "HasColumnType" => p => p.SetColumnType(columnType), + _ => null, + }; + var integerKey = mapping == "key" && e.Wrapped is "int" or "long" or "ulong" && facet != "HasColumnType"; + var voEntity = EfModel.Entity(mapping, e.Vo); + var plainEntity = EfModel.Entity(mapping, e.Plain); + string? voModel = null, voScript = null, plainModel = null, plainScript = null; + Exception? voEx = null, plainEx = null; + await using (var vo = new EfDb(EfModel.Configure(mapping, voEntity, apply, integerKey), typeKit: true)) + try { voModel = EfModel.Describe(vo.Context); voScript = vo.Context.Database.GenerateCreateScript(); } catch (Exception ex) { voEx = ex; } + await using (var plain = new EfDb(EfModel.Configure(mapping, plainEntity, apply, false), typeKit: false)) + try { plainModel = EfModel.Describe(plain.Context); plainScript = plain.Context.Database.GenerateCreateScript(); } catch (Exception ex) { plainEx = ex; } + + // A key over char or Guid is never generated where EF generates the twin's (README#keys, below): the script's AUTOINCREMENT + // is that generation, compared apart by column-type, so neither check compares it in the script. + var ownKey = mapping == "key" && e.Wrapped is "char" or "Guid"; + if (ownKey) { voScript = voScript?.Replace(" AUTOINCREMENT", ""); plainScript = plainScript?.Replace(" AUTOINCREMENT", ""); } + + // The column the facet is configured on, as EfModel.Describe names it, and the fields the facet sets there. + var column = mapping switch { "key" => "t.Id: ", "complex" => "t.C.P: ", _ => "t.P: " }; + string[] facetFields = facet switch { "HasMaxLength" => ["max"], "HavePrecision" => ["prec", "scale"], "HasColumnType" => ["type"], _ => [] }; + + // column-type and nullable: every column of the model and the script, as the wrapped twin's, with the facet's own fields + // left to facet-kept (and the script too, where the facet is the column type the script spells). + var mapped = r.Check(mapping == "nullable" ? "nullable" : "column-type", Baseline.Twin, c => + { + if (EfSides.Threw(c, voEx, plainEx, "the model or the create script", out var refused)) return refused; + string vm = Mask(voModel!, column, facetFields), pm = Mask(plainModel!, column, facetFields), vs = voScript!, ps = plainScript!; + if (ownKey) + { + // README#keys: "A key over `char` is yours to assign. It's never generated, on any provider" and "A key over a `Guid` is yours to assign" (EF's own plain char and Guid keys are generated on add). + if (!voModel!.Contains("pk=True gen=Never")) return "a key over char or Guid is not ValueGeneratedNever: " + voModel; + vm = vm.Replace("gen=Never", "gen=*"); pm = pm.Replace("gen=OnAdd", "gen=*").Replace("gen=Never", "gen=*"); + c.Note(NoteKeys.DocumentedCharGuidKeyNeverGeneratedChecked, "generation compared apart: not-promised char-key-not-generated, guid-key-not-generated"); + } + if (vm != pm) return "model differs: " + EfModel.FirstDiff(vm, pm); + if (facet != "HasColumnType" && vs != ps) return "create script differs: " + EfModel.FirstDiff(vs, ps); + c.Note(NoteKeys.Compared, facet == "HasColumnType" ? "the model" : "the model and the create script"); + return null; + }); + if (facet == "none") return mapped; + + // facet-kept: the facet's own fields of the configured column, and for a column type the script, as the wrapped twin keeps them. + var kept = r.Check("facet-kept", Baseline.Twin, c => + { + if (EfSides.Threw(c, voEx, plainEx, "the model or the create script", out var refused)) return refused; + var vl = Line(voModel!, column); var pl = Line(plainModel!, column); + foreach (var f in facetFields) + if (Field(vl, f) != Field(pl, f)) return $"{facet} not kept: the value object's column has {f}={Field(vl, f)}, the wrapped type's {f}={Field(pl, f)} ({vl})"; + if (facet == "HasColumnType" && voScript != plainScript) return "create script differs: " + EfModel.FirstDiff(voScript!, plainScript!); + c.Note(NoteKeys.Compared, string.Join(",", facetFields.Select(f => $"{f}={Field(pl, f)}"))); + return null; + }); + return mapped ?? kept; + } + + /// <summary>The line EfModel.Describe writes for <paramref name="column"/>; a model without it is the harness's mistake.</summary> + static string Line(string model, string column) => + model.Split('\n').FirstOrDefault(l => l.StartsWith(column, StringComparison.Ordinal)) ?? throw new InvalidOperationException($"no column {column.TrimEnd(' ', ':')} in the model: {model}"); + static string Field(string line, string field) => System.Text.RegularExpressions.Regex.Match(line, $@"(?:^|\s){field}=(\S*)").Groups[1].Value; + /// <summary>The model with the given fields of <paramref name="column"/>'s line masked.</summary> + static string Mask(string model, string column, string[] fields) => string.Join("\n", model.Split('\n').Select(l => + !l.StartsWith(column, StringComparison.Ordinal) ? l : fields.Aggregate(l, (x, f) => System.Text.RegularExpressions.Regex.Replace(x, $@"(^|\s){f}=\S*", $"$1{f}=*")))); +} diff --git a/tools/probe/corpus/probes/E02ValueTranslatesProbe.cs b/tools/probe/corpus/probes/E02ValueTranslatesProbe.cs new file mode 100644 index 00000000..372ea708 --- /dev/null +++ b/tools/probe/corpus/probes/E02ValueTranslatesProbe.cs @@ -0,0 +1,142 @@ +// Claim E02-value-translates-to-column (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries): ".Value, GetValue() and ValueOrNull() translate to the column itself" (column parity: same SQL as the query over the wrapped property). +// Oracle: differential. For each query shape the SQL (ToQueryString) of the query over the value object's accessor equals the SQL of the same query over the wrapped-type property, and both return the same rows. +// Obligations (claims.json), each against the wrapped twin (Baseline.Twin): +// value, get-value, value-or-null (by ef.accessor): "`.Value`, `GetValue()` and `ValueOrNull()` translate to the column itself.": the rows the query answers +// column-not-cast (every row): "A table's column is re-typed, not cast, so an index on it still serves the query.": the SQL +// Expected: no failure for any row; where a side refuses the query, EfSides says how the check ends. Where SQLite cannot compare or +// order the wrapped type, every obligation declares itself inapplicable (claims.json, fact KB-039). where-value-compare is >= as C# writes +// it, over the underlying type for an enum; a string, a bool and a Uri have none, and claims.json excludes the pair. +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E02ValueTranslatesProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E02ValueTranslatesProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static MethodInfo? Unwrap(Type vo, string name) => + vo.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m => m.Name == name && m.GetParameters().Length == 1); + + /// <summary>The type C# compares an enum (or a nullable enum) over: its underlying type, nullable where it is; null for any other type.</summary> + static Type? Underlying(Type t) => Nullable.GetUnderlyingType(t) is { IsEnum: true } e ? typeof(Nullable<>).MakeGenericType(Enum.GetUnderlyingType(e)) + : t.IsEnum ? Enum.GetUnderlyingType(t) : null; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var e = Probes.Ef3.E4.At(r, r["shape.kind"], r["shape.wrapped"], r["shape.site"]); + var acc = r["ef.accessor"]; + var q = r["ef.query"]; + var nullable = acc == "ValueOrNull"; + var mapping = nullable ? "nullable" : "property"; + MethodInfo? unwrap = acc == "Value" ? null : e.Companion(acc); + if (acc != "Value" && unwrap is null) return $"no static {acc}(...) on {e.Vo}"; + var voProp = nullable && e.Vo.IsValueType ? typeof(Nullable<>).MakeGenericType(e.Vo) : e.Vo; + var plProp = nullable && e.Plain.IsValueType ? typeof(Nullable<>).MakeGenericType(e.Plain) : e.Plain; + var voEntity = EfModel.Entity(mapping, e.Vo); + var plainEntity = EfModel.Entity(mapping, e.Plain); + // the type the accessor yields; the plain side is the property itself + Type accessType = acc == "Value" ? e.Vo.GetProperty("Value")!.PropertyType : unwrap!.ReturnType; + object constant = nullable && !e.Plain.IsValueType ? e.Valid() : e.Valid(); + + IQueryable Build(DbContext db, Type entity, bool vo) + { + var p = Expression.Parameter(entity, "e"); + Expression prop = Expression.Property(p, "P"); + Expression access = !vo ? prop : acc == "Value" ? Expression.Property(prop, e.Vo.GetProperty("Value")!) : Expression.Call(unwrap!, prop); + var at = access.Type; + var src = Q.SetOf(db, entity); + var box = Expression.Constant(Q.NewBox(at, constant)); + Expression param = Expression.Field(box, "Value"); + LambdaExpression Lam(Expression body) => Expression.Lambda(body, p); + switch (q) + { + case "where-equals": return Q.Call("Where", [entity], src, Lam(Expression.Equal(access, param))); + case "where-value-compare": + BinaryExpression cmp; + try { cmp = Expression.GreaterThanOrEqual(access, param); } + // C# compares two values of an enum with >= over its underlying type, and an expression tree it compiles says so + // with a conversion on each side; Expression.GreaterThanOrEqual defines no >= for the enum itself. + catch (InvalidOperationException) when (Underlying(at) is { } u) { cmp = Expression.GreaterThanOrEqual(Expression.Convert(access, u), Expression.Convert(param, u)); } + // string, bool and Uri have no >= in C# (excluded in claims.json): no row asks for one + catch (InvalidOperationException) { r.Substituted("ef.query", "where-equals", "the wrapped type has no >= operator"); cmp = Expression.Equal(access, param); } + return Q.Call("Where", [entity], src, Lam(cmp)); + case "order-by": return Q.Call("OrderBy", [entity, at], src, Lam(access)); + case "select": return Q.Call("Select", [entity, at], src, Lam(access)); + case "contains": + var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(at))!; + list.GetType().GetMethod("Add")!.Invoke(list, [constant]); + var lbox = Expression.Constant(Q.NewBox(list.GetType(), list)); + var contains = typeof(Enumerable).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 2).MakeGenericMethod(at); + return Q.Call("Where", [entity], src, Lam(Expression.Call(contains, Expression.Field(lbox, "Value"), access))); + case "group-by": + var grouped = Q.Call("GroupBy", [entity, at], src, Lam(access)); + var gt = typeof(IGrouping<,>).MakeGenericType(at, entity); + var g = Expression.Parameter(gt, "g"); + return Q.Call("Select", [gt, at], grouped, Expression.Lambda(Expression.Property(g, "Key"), g)); + default: throw new ArgumentException(q); + } + } + + // One side's query: its SQL, or the exception translating it; the rows it answers, or the exception running it. + async Task<(string? Sql, Exception? Translating, List<string>? Rows, Exception? Running)> Side(Type entity, bool vo) + { + await using var db = new EfDb(EfModel.Configure(mapping, entity, null, false), typeKit: vo); + IQueryable query; + try + { + await db.Context.Database.EnsureCreatedAsync(); + var row = Activator.CreateInstance(entity)!; + var value = vo ? e.MakeVo(e.Valid()) : e.Valid(); + entity.GetProperty("P")!.SetValue(row, value); + db.Context.Add(row); + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + query = Build(db.Context, entity, vo); + } + catch (Exception ex) { return (null, ex, null, ex); } + string sql; + try { sql = query.ToQueryString(); } + catch (Exception ex) { return (null, ex, null, ex); } + try + { + var list = new List<string>(); + foreach (var x in (System.Collections.IEnumerable)query) list.Add(Norm(x)); + return (sql, null, list, null); + } + catch (Exception ex) { return (sql, null, null, ex); } + } + var v = await Side(voEntity, true); + var pl = await Side(plainEntity, false); + + // The accessor's obligation: the query over it answers what the same query over the wrapped property answers. + var answered = r.Check(acc switch { "Value" => "value", "GetValue" => "get-value", _ => "value-or-null" }, Baseline.Twin, c => + { + if (EfSides.Threw(c, v.Running, pl.Running, "the query", out var refused)) return refused; + if (!v.Rows!.SequenceEqual(pl.Rows!)) return $"rows differ: VO [{string.Join(",", v.Rows!)}] plain [{string.Join(",", pl.Rows!)}]"; + c.Note(NoteKeys.Compared, $"{v.Rows!.Count} row(s)"); + return null; + }); + // column-not-cast: the SQL is the wrapped property's, so the column is re-typed and not cast. + var sqlOf = r.Check("column-not-cast", Baseline.Twin, c => + { + if (EfSides.Threw(c, v.Translating, pl.Translating, "translating the query", out var refused)) return refused; + if (v.Sql != pl.Sql) return $"SQL differs.\nVO:\n{v.Sql}\nplain:\n{pl.Sql}"; + c.Note(NoteKeys.Compared, "the SQL"); + return null; + }); + return answered ?? sqlOf; + } + /// <summary>A result as comparable text: an entity by its P, a value object by the value it wraps.</summary> + static string Norm(object? x) + { + if (x is null) return "<null>"; + var t = x.GetType(); + if (t.IsGenericType && t.GetGenericTypeDefinition() is var d && (d == typeof(EntP<>) || d == typeof(EntNC<>))) return Norm(t.GetProperty("P")!.GetValue(x)); + if (t.GetProperty("Value") is { } v && v.DeclaringType == t && t.Namespace == "Probes.Ef" || t.Name == "V" || t.Name.StartsWith("E_")) return Norm(t.GetProperty("Value")!.GetValue(x)); + return Convert.ToString(x, System.Globalization.CultureInfo.InvariantCulture) ?? "<null>"; + } +} diff --git a/tools/probe/corpus/probes/E03ColumnRoundTripProbe.cs b/tools/probe/corpus/probes/E03ColumnRoundTripProbe.cs new file mode 100644 index 00000000..7dc53d48 --- /dev/null +++ b/tools/probe/corpus/probes/E03ColumnRoundTripProbe.cs @@ -0,0 +1,82 @@ +// Claim E03-column-round-trip (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#what-maps): "Keys, foreign keys, nullable properties, primitive collections and query parameters all map as plain columns of the underlying type." +// Oracle: metamorphic plus differential. A value object saved in a property / nullable property / key / owned property / complex property and read back by a fresh query equals what was saved (and its Value is the wrapped value); +// the stored column (SQLite type and quoted content) equals what the wrapped type stored by plain EF holds. A nullable property also round-trips null. +// Obligations (claims.json), one per row by ef.mapping, against the wrapped twin (Baseline.Twin): stored-as-underlying (property, owned, +// complex): "Every value object is stored in a plain column of its underlying type"; keys (key) and nullable-properties (nullable): +// "Keys, foreign keys, nullable properties, primitive collections and query parameters all map as plain columns of the underlying type." +// Expected: no failure for any row; where a side refuses, EfSides says how the check ends. +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E03ColumnRoundTripProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E03ColumnRoundTripProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + sealed record Side(string? Raw, List<object?> Read, Exception? Ex); + + static async Task<Side> Run(string mapping, Type entity, Func<object?> value, bool vo) + { + var col = mapping switch { "key" => "Id", "owned" => "O_P", "complex" => "C_P", _ => "P" }; + await using var db = new EfDb(EfModel.Configure(mapping, entity, p => { if (mapping == "key") p.ValueGenerated = ValueGenerated.Never; }, false), typeKit: vo); + try + { + await db.Context.Database.EnsureCreatedAsync(); + var n = mapping == "nullable" ? 2 : 1; + for (var i = 1; i <= n; i++) + { + var row = Activator.CreateInstance(entity)!; + var v = i == 2 ? null : value(); + if (mapping == "key") { EfX.Set(row, "Id", v); EfX.Set(row, "Tag", "t"); } + else + { + EfX.Set(row, "Id", i); + object holder = row; + if (mapping is "owned" or "complex") holder = EfX.Get(row, mapping == "owned" ? "O" : "C")!; + EfX.Set(holder, "P", v); + } + db.Context.Add(row); + } + await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var read = new List<object?>(); + foreach (var x in EfX.Enumerate(Q.SetOf(db.Context, entity))) + { + object? o = mapping switch { "key" => EfX.Get(x, "Id"), "owned" => EfX.Get(EfX.Get(x, "O")!, "P"), "complex" => EfX.Get(EfX.Get(x, "C")!, "P"), _ => EfX.Get(x, "P") }; + read.Add(o); + } + var raw = await EfX.RawScalar(db, $"SELECT group_concat(k, '|') FROM (SELECT typeof({col}) || ':' || quote({col}) AS k FROM t ORDER BY rowid)"); + return new Side(raw, read, null); + } + catch (Exception ex) { return new Side(null, [], ex); } + } + + static object? Plainify(object? o) => o is not null && o.GetType().Namespace is "Probes.Ef3" or "Probes.Ef" or "Probes.Ef4" ? o.GetType().GetProperty("Value")!.GetValue(o) : o; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var w = r["shape.wrapped"]; var kind = r["shape.kind"]; var mapping = r["ef.mapping"]; + var e = Probes.Ef3.E4.At(r, kind, w, r["shape.site"]); + var original = e.Valid(); + var voSide = await Run(mapping, EfModel.Entity(mapping, e.Vo), () => e.MakeVo(original), vo: true); + var plainSide = await Run(mapping, EfModel.Entity(mapping, e.Plain), () => original, vo: false); + return r.Check(mapping switch { "key" => "keys", "nullable" => "nullable-properties", _ => "stored-as-underlying" }, Baseline.Twin, c => + { + if (EfSides.Threw(c, voSide.Ex, plainSide.Ex, $"saving and reading a {mapping}", out var refused)) return refused; + if (voSide.Read.Count != plainSide.Read.Count) return $"read {voSide.Read.Count} rows, the wrapped type read {plainSide.Read.Count}"; + for (var i = 0; i < voSide.Read.Count; i++) + { + var v = voSide.Read[i]; var p = plainSide.Read[i]; + if (p is null != v is null) return $"row {i}: the value object read back {(v is null ? "null" : "a value")}, the wrapped type {(p is null ? "null" : "a value")}"; + if (v is null) continue; + if (!Equals(Plainify(v), p)) return $"row {i}: read back Value {Plainify(v)}, the wrapped type reads {p}"; + if (!Equals(v, e.MakeVo(p!))) return $"row {i}: the read-back value object does not equal the one built from the stored value"; + } + if (voSide.Raw != plainSide.Raw) return $"stored column differs: value object [{voSide.Raw}] vs wrapped type [{plainSide.Raw}]"; + c.Note(NoteKeys.Compared, $"{voSide.Read.Count} row(s) read back, the stored column {voSide.Raw}"); + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E04IntegerKeyProbe.cs b/tools/probe/corpus/probes/E04IntegerKeyProbe.cs new file mode 100644 index 00000000..db254607 --- /dev/null +++ b/tools/probe/corpus/probes/E04IntegerKeyProbe.cs @@ -0,0 +1,75 @@ +// Claim E04-integer-key-generated (README#keys): "A single-column key over an integer is generated on add, like a key of that integer type ... not byte. A key over char is yours to assign: never generated. A key over a Guid is yours to assign ... To have EF generate it instead, configure ValueGeneratedOnAdd()." +// Oracle: differential against the same key declared with the wrapped type (two entities added with default keys, SaveChanges, the keys read back), except the documented char and Guid keys, which must not be generated by default. +// Obligations (claims.json), one per row by shape.wrapped and ef.key-config: +// generated-on-add (int, long, ulong), byte-not-generated (byte), not-on-efs-list (string, default): Baseline.Twin +// char-key-yours, guid-key-yours (char, Guid, default): the README's "yours to assign" (Baseline.Docs) +// own-configuration-wins (char, Guid, string, ValueGeneratedOnAdd or ValueGeneratedNever): Baseline.Twin +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E04IntegerKeyProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E04IntegerKeyProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var e = KeyRegistry.Of(r); + var config = r["ef.key-config"]; + string voGenerated = "?"; + async Task<string> Side(Type keyType, bool vo) + { + var entity = typeof(EntK<>).MakeGenericType(keyType); + Action<ModelBuilder> cfg = mb => + { + var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); + var pb = eb.Property("Id"); + if (config == "ValueGeneratedOnAdd") pb.ValueGeneratedOnAdd(); + if (config == "ValueGeneratedNever") pb.ValueGeneratedNever(); + }; + await using var db = new EfDb(cfg, typeKit: vo); + try + { + if (vo) voGenerated = db.Context.Model.FindEntityType(entity)!.FindProperty("Id")!.ValueGenerated.ToString(); + await db.Context.Database.EnsureCreatedAsync(); + var rows = new[] { Activator.CreateInstance(entity)!, Activator.CreateInstance(entity)! }; + foreach (var row in rows) db.Context.Add(row); + await db.Context.SaveChangesAsync(); + return "keys=[" + string.Join(",", rows.Select(x => Norm(entity.GetProperty("Id")!.GetValue(x)))) + "]"; + } + catch (Exception) { return "threw"; } // where it throws (creating the table, or the insert) differs by provider rule; that it cannot generate is the outcome + } + string Norm(object? k) + { + if (k is not null && k.GetType().Namespace == "Probes.Ef") k = k.GetType().GetProperty("Value")!.GetValue(k); + return k switch { null => "null", Guid g => g == Guid.Empty ? "empty" : "guid", string str when Guid.TryParse(str, out _) => "guid", _ => Convert.ToString(k, System.Globalization.CultureInfo.InvariantCulture)! }; + } + var voOut = await Side(e.Vo, true); + var plainOut = await Side(e.Plain, false); + if (config == "default" && e.Wrapped is "char" or "Guid") + return r.Check(e.Wrapped == "char" ? "char-key-yours" : "guid-key-yours", Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys"), c => + { + // documented: yours to assign, never generated: the model declares the key never generated, and the second default + // key collides, or both stay default (not-promised meaningful-char-key-unchecked) + c.Note(NoteKeys.DocumentedCharGuidNotGeneratedChecked, "the model declares it " + voGenerated); + if (voGenerated != "Never") return $"{e.Wrapped} key is declared {voGenerated}, not never generated (plain: {plainOut})"; + return voOut.StartsWith("threw") || voOut is "keys=[empty,empty]" or "keys=[\0,\0]" ? null : $"{e.Wrapped} key was generated by default: {voOut} (plain: {plainOut})"; + }); + // the rest: as the same key declared with the wrapped type, under the same configuration + var obligation = (e.Wrapped, config) switch + { + ("int" or "long" or "ulong", _) => "generated-on-add", + ("byte", _) => "byte-not-generated", + ("string", "default") => "not-on-efs-list", + _ => "own-configuration-wins", + }; + return r.Check(obligation, Baseline.Twin, c => + { + if (voOut != plainOut) return $"VO key: {voOut}; plain key: {plainOut}"; + c.Note(NoteKeys.Compared, voOut); + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E05OwnCompareToProbe.cs b/tools/probe/corpus/probes/E05OwnCompareToProbe.cs new file mode 100644 index 00000000..80c90646 --- /dev/null +++ b/tools/probe/corpus/probes/E05OwnCompareToProbe.cs @@ -0,0 +1,127 @@ +// Claim E05-own-compareto-rejected (README#queries): "Your own CompareTo(TSelf) can't run in the database ... these queries throw an InvalidOperationException naming the value object: a comparison (<, >, <=, >=, CompareTo); an ordering (OrderBy, ThenBy, their descending forms, Order); Min or Max. ... Equality isn't ordering, and stays allowed. A query filter (HasQueryFilter, named or not) that does the same fails when the model is built." +// Oracle: exception. A value object with a descending CompareTo, in each such query, must throw InvalidOperationException naming it (never return rows); equality and ordering by .Value return what memory returns. +// Obligations (claims.json), one per row by ef.op: comparison, ordering, min-max and query-filter against the documented refusal +// (Baseline.Refusal); equality-allowed and value-order-allowed against memory (Baseline.Model). +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E05OwnCompareToProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E05OwnCompareToProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static readonly string[] Refused = ["lt", "gt", "le", "ge", "CompareTo", "OrderBy", "OrderByDescending", "ThenBy", "Order", "Min", "Max"]; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var op = r["ef.op"]; + var obligation = op switch + { + "lt" or "gt" or "le" or "ge" or "CompareTo" => "comparison", + "OrderBy" or "OrderByDescending" or "ThenBy" or "Order" => "ordering", + "Min" or "Max" => "min-max", + "equals" => "equality-allowed", + "value-order" => "value-order-allowed", + _ => "query-filter", + }; + // a refused query is held to the documented refusal; an allowed one to what memory answers + return await r.Check(obligation, Refused.Contains(op) || obligation == "query-filter" ? Baseline.Refusal : Baseline.Model, async c => + { + var h = HandOrdered.Of(r["ef.vo"]); + var entity = typeof(EntP<>).MakeGenericType(h.Vo); + var filter = op is "HasQueryFilter" or "named-query-filter"; + var box = Expression.Constant(Q.NewBox(h.Vo, HandOrdered.Make(h, h.Values[1]))); + Expression Const() => Expression.Field(box, "Value"); + var p = Expression.Parameter(entity, "e"); + var prop = Expression.Property(p, "P"); + LambdaExpression Lam(Expression body) => Expression.Lambda(body, p); + Expression? predicate = null; + try + { + predicate = op switch + { + "lt" => Expression.LessThan(prop, Const()), + "gt" => Expression.GreaterThan(prop, Const()), + "le" => Expression.LessThanOrEqual(prop, Const()), + "ge" => Expression.GreaterThanOrEqual(prop, Const()), + "CompareTo" => Expression.GreaterThan(Expression.Call(prop, h.Vo.GetMethod("CompareTo", [h.Vo])!, Const()), Expression.Constant(0)), + "equals" => Expression.Equal(prop, Const()), + "HasQueryFilter" or "named-query-filter" => Expression.GreaterThan(prop, Const()), + _ => null, + }; + } + catch (InvalidOperationException ex) { return c.Inconclusive($"{op} cannot be written over {h.Vo.Name}: {ex.Message}"); } + + Action<ModelBuilder> cfg = mb => + { + var eb = mb.Entity(entity); + eb.ToTable("t"); eb.HasKey("Id"); + if (op == "HasQueryFilter") eb.HasQueryFilter(Lam(predicate!)); + if (op == "named-query-filter") eb.HasQueryFilter("AboveTwo", Lam(predicate!)); + }; + await using var db = new EfDb(cfg, typeKit: true); + object[] Seed() => h.Values.Select(v => HandOrdered.Make(h, v)).ToArray(); + Exception? thrown = null; + List<string> result = []; + try + { + await db.Context.Database.EnsureCreatedAsync(); + foreach (var v in Seed()) { var row = Activator.CreateInstance(entity)!; entity.GetProperty("P")!.SetValue(row, v); db.Context.Add(row); } + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + var src = Q.SetOf(db.Context, entity); + object? scalar = null; + IQueryable? query = null; + switch (op) + { + case "lt" or "gt" or "le" or "ge" or "CompareTo" or "equals": query = Q.Call("Where", [entity], src, Lam(predicate!)); break; + case "OrderBy": query = Q.Call("OrderBy", [entity, h.Vo], src, Lam(prop)); break; + case "OrderByDescending": query = Q.Call("OrderByDescending", [entity, h.Vo], src, Lam(prop)); break; + case "ThenBy": query = Q.Call("ThenBy", [entity, h.Vo], Q.Call("OrderBy", [entity, typeof(int)], src, Lam(Expression.Property(p, "Id"))), Lam(prop)); break; + case "value-order": + var value = h.Vo.GetProperty("Value")!; + query = Q.Call("OrderBy", [entity, h.Plain], src, Lam(Expression.Property(prop, value))); break; + case "Order" or "Min" or "Max": + var sel = Q.Call("Select", [entity, h.Vo], src, Lam(prop)); + var m = typeof(Queryable).GetMethods().First(x => x.Name == op && x.IsGenericMethodDefinition && x.GetParameters().Length == 1 && x.GetGenericArguments().Length == 1).MakeGenericMethod(h.Vo); + try { var res = m.Invoke(null, [sel]); if (op == "Order") query = (IQueryable)res!; else scalar = res; } + catch (TargetInvocationException tie) { throw tie.InnerException!; } + break; + case "HasQueryFilter" or "named-query-filter": + query = src; break; + default: throw new ArgumentException(op); + } + if (query is not null) foreach (var x in (System.Collections.IEnumerable)query) result.Add(Plain(x)); + else result.Add(Plain(scalar)); + } + catch (Exception ex) { thrown = ex; } + + if (Refused.Contains(op) || filter) + { + if (thrown is null) return $"{op} on {h.Vo.Name} returned [{string.Join(",", result)}] instead of throwing"; + if (thrown is not InvalidOperationException) return $"{op}: expected InvalidOperationException, got {thrown.GetType().Name}: {Trim(thrown.Message)}"; + if (!thrown.Message.Contains(h.Vo.Name)) return $"{op}: the exception does not name {h.Vo.Name}: {Trim(thrown.Message)}"; + if (filter && !thrown.Message.Contains("query filter")) return $"{op}: the exception does not say it is the query filter: {Trim(thrown.Message)}"; + if (!filter && !thrown.Message.Contains(".Value")) return $"{op}: the exception does not point to .Value: {Trim(thrown.Message)}"; + return null; + } + if (thrown is not null) return $"{op} (allowed) threw {thrown.GetType().Name}: {Trim(thrown.Message)}"; + // allowed: equals keeps the one equal row; value-order sorts by the wrapped value, ascending + var expected = op == "equals" ? new[] { Plain(h.Values[1]) } : h.Values.OrderBy(v => v, Comparer<object>.Create((a, b) => ((IComparable)a).CompareTo(b))).Select(Plain).ToArray(); + return result.SequenceEqual(expected) ? null : $"{op}: got [{string.Join(",", result)}], memory gives [{string.Join(",", expected)}]"; + }); + } + static string Plain(object? x) + { + if (x is null) return "<null>"; + var t = x.GetType(); + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(EntP<>)) return Plain(t.GetProperty("P")!.GetValue(x)); + if (t.Namespace == "Probes.Ef" && t.GetProperty("Value") is { } v) return Plain(v.GetValue(x)); + return Convert.ToString(x, System.Globalization.CultureInfo.InvariantCulture) ?? "<null>"; + } + static string Trim(string s) => s.Length > 300 ? s[..300] : s; +} diff --git a/tools/probe/corpus/probes/E06OwnConverterNoValueProbe.cs b/tools/probe/corpus/probes/E06OwnConverterNoValueProbe.cs new file mode 100644 index 00000000..2b58f044 --- /dev/null +++ b/tools/probe/corpus/probes/E06OwnConverterNoValueProbe.cs @@ -0,0 +1,76 @@ +// Claim E06-own-converter-no-value (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries): ".Value translates only where the column holds the underlying value ... A property your application converts itself (HasConversion(c => "C:" + c.Value, ...), a converter type, a conversion in ConfigureConventions) doesn't hold .Value. So a query that reads .Value there fails with EF's 'could not be translated', rather than comparing against what the column really holds. Comparing the value object itself (o.Code == code) goes through that converter and still works." +// Obligations (claims.json), one per row by ef.op: value-not-translated (value-access), against the README (Baseline.Docs), and +// value-object-comparison (vo-equality), against the row memory matches (Baseline.Model). +// Oracle: claim. With the application's own converter (three ways to declare it) the column holds the converter's text; a query on .Value throws InvalidOperationException "could not be translated" (never returns rows or an empty result); a query comparing the value object returns exactly the matching row. +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E06OwnConverterNoValueProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E06OwnConverterNoValueProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static Action<ModelBuilder> Model<T>(string form) where T : notnull => mb => mb.Entity<EntP<T>>(eb => + { + eb.ToTable("t"); eb.HasKey(x => x.Id); + switch (form) + { + case "HasConversion-lambda": eb.Property(x => x.P).HasConversion(v => OwnConv<T>.To(v), s => OwnConv<T>.From(s)); break; + case "converter-type": eb.Property(x => x.P).HasConversion<OwnConverter<T>>(); break; + } + }); + static Action<ModelConfigurationBuilder>? Conventions<T>(string form) where T : notnull => + form == "ConfigureConventions" ? cb => cb.Properties<T>().HaveConversion<OwnConverter<T>>() : null; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var form = r["ef.converter"]; var op = r["ef.op"]; var w = r["shape.wrapped"]; + // value-access: the README's "could not be translated" (no twin can show it); vo-equality: the one row memory matches + var valueAccess = op == "value-access"; + return await r.Check(valueAccess ? "value-not-translated" : "value-object-comparison", + valueAccess ? Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#queries") : Baseline.Model, async c => + { + var e = EfX.Top(EfX.Kinds[r.Index % EfX.Kinds.Length], w); + OwnConvs.Setup(e, vo: true); + var entity = typeof(EntP<>).MakeGenericType(e.Vo); + var cfg = (Action<ModelBuilder>)typeof(E06OwnConverterNoValueProbe).GetMethod(nameof(Model), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!.MakeGenericMethod(e.Vo).Invoke(null, [form])!; + var conv = (Action<ModelConfigurationBuilder>?)typeof(E06OwnConverterNoValueProbe).GetMethod(nameof(Conventions), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!.MakeGenericMethod(e.Vo).Invoke(null, [form]); + await using var db = new EfDb2(cfg, "after-provider", conv); + await db.Context.Database.EnsureCreatedAsync(); + foreach (var i in new[] { 0, 1 }) { var x = EfX.New(entity); EfX.Set(x, "P", e.MakeVo(EfX.Sample(w, i))); db.Context.Add(x); } + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + + var stored = await EfX2.RawScalar(db.Connection, "SELECT group_concat(P, ',') FROM (SELECT P FROM t ORDER BY Id)"); + var expected = "C:" + OwnConv<int>.Fmt(EfX.Sample(w, 0)) + ",C:" + OwnConv<int>.Fmt(EfX.Sample(w, 1)); + if (stored != expected) return $"{form}: the column holds {stored}, expected the application's converter output {expected}"; + + var p = Expression.Parameter(entity, "e"); + var prop = Expression.Property(p, "P"); + var src = Q.SetOf(db.Context, entity); + if (op == "vo-equality") + { + var box = Q.NewBox(e.Vo, e.MakeVo(EfX.Sample(w, 1))); + var pred = Expression.Lambda(Expression.Equal(prop, Expression.Field(Expression.Constant(box), "Value")), p); + List<object> got; + // a refusal here is the claim failing, not the harness: comparing the value object goes through the application's converter + try { got = EfX.Enumerate(Q.Call("Where", [entity], src, pred)).ToList(); } + catch (InvalidOperationException ex) { return $"{form}: comparing the value object failed with {ex.GetType().Name}: {ex.Message[..Math.Min(200, ex.Message.Length)]}"; } + if (got.Count != 1 || EfX.Plainify(EfX.Get(got[0], "P")) != EfX.Plainify(EfX.Sample(w, 1))) return $"{form}: comparing the value object returned {got.Count} row(s), expected the one that matches"; + return null; + } + var pbox = Q.NewBox(e.Plain, EfX.Sample(w, 1)); + var vpred = Expression.Lambda(Expression.Equal(Expression.Property(prop, "Value"), Expression.Field(Expression.Constant(pbox), "Value")), p); + try + { + var got = EfX.Enumerate(Q.Call("Where", [entity], src, vpred)).ToList(); + return $"{form}: a query on .Value of a property the application converts itself returned {got.Count} row(s) instead of failing as untranslatable"; + } + catch (InvalidOperationException ex) when (ex.Message.Contains("could not be translated")) { return null; } + catch (Exception ex) { return $"{form}: the .Value query failed with {ex.GetType().Name}: {ex.Message[..Math.Min(200, ex.Message.Length)]}, not EF's 'could not be translated'"; } + }); + } +} diff --git a/tools/probe/corpus/probes/E07ClosedSetNameProbe.cs b/tools/probe/corpus/probes/E07ClosedSetNameProbe.cs new file mode 100644 index 00000000..16bf3562 --- /dev/null +++ b/tools/probe/corpus/probes/E07ClosedSetNameProbe.cs @@ -0,0 +1,129 @@ +// Claim E07-closed-set-stored-as-name (README#closed-sets): "A closed set is stored as a text column of the member's name, and read back as the member itself. That holds in a table, in a primitive collection of names and in a JSON column alike ... A name the set no longer has throws when it's read, rather than reading as null. An instance that isn't a member throws when it's written." +// Obligations (claims.json), against the README's section (Baseline.Docs): stored-as-name (round-trip), in-collections-and-json +// (round-trip in a primitive collection or a JSON column), unknown-name-throws (unknown-name-on-read), non-member-throws (non-member-on-write). +// Oracle: claim. Round trip: the stored text is the member's name and the read value is the member itself (reference-equal); an unknown stored name throws on read (never null/default); a non-member instance throws on write. +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E07ClosedSetNameProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E07ClosedSetNameProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var site = r["ef.site"]; var ev = r["ef.event"]; + var set = typeof(PSet); + Type entity; string column; Action<Microsoft.EntityFrameworkCore.ModelBuilder> cfg; + switch (site) + { + case "table": + entity = typeof(EntP<PSet>); column = "P"; + cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); }; + break; + case "primitive-collection": + entity = typeof(EntL<PSet>); column = "Items"; + cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); }; + break; + case "json-column": + entity = typeof(EntO<PSet>); column = "O"; + cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); eb.OwnsOne(typeof(Own<PSet>), "O", ob => ob.ToJson()); }; + break; + default: throw new ArgumentException(site); + } + PSet NonMember() => (PSet)Activator.CreateInstance(set, nonPublic: true)!; + object Entity(params PSet[] members) + { + var e = EfX.New(entity); + switch (site) + { + case "table": EfX.Set(e, "P", members[0]); break; + case "primitive-collection": EfX.Set(e, "Items", members.ToList()); break; + default: var o = new Own<PSet> { P = members[0] }; EfX.Set(e, "O", o); break; + } + return e; + } + string Members(object e) => site switch + { + "table" => ((PSet)EfX.Get(e, "P")!).Name(), + "primitive-collection" => string.Join(",", ((List<PSet>)EfX.Get(e, "Items")!).Select(x => x.Name())), + _ => ((Own<PSet>)EfX.Get(e, "O")!).P.Name(), + }; + + var docs = Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#closed-sets"); + await using var db = new EfDb(cfg, typeKit: true); + await db.Context.Database.EnsureCreatedAsync(); + switch (ev) + { + case "round-trip": + { + var e = site == "primitive-collection" ? Entity(PSet.Beta, PSet.Alpha) : Entity(PSet.Beta); + db.Context.Add(e); await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var stored = await EfX.RawScalar(db, $"SELECT {column} FROM t"); + var expectStored = site switch { "table" => "Beta", "primitive-collection" => "[\"Beta\",\"Alpha\"]", _ => "{\"P\":\"Beta\"}" }; + // stored-as-name: a table's text is the member's name, and every site reads back the member itself + var named = r.Check("stored-as-name", docs, c => + { + if (site == "table" && stored != expectStored) return $"stored text is {stored}, expected {expectStored}"; + object back; + try { back = EfX.Enumerate(Q.SetOf(db.Context, entity)).Single(); } + catch (Exception ex) when (ex is not Xunit.Sdk.XunitException) { return $"reading the stored {stored} back threw {ex.GetType().Name}: {ex.Message.Split('\n')[0]}"; } + if (site == "primitive-collection") + { + var l = (List<PSet>)EfX.Get(back, "Items")!; + if (!(l.Count == 2 && ReferenceEquals(l[0], PSet.Beta) && ReferenceEquals(l[1], PSet.Alpha))) return "read back " + Members(back); + } + else + { + var got = site == "table" ? (PSet)EfX.Get(back, "P")! : ((Own<PSet>)EfX.Get(back, "O")!).P; + if (!ReferenceEquals(got, PSet.Beta)) return "read back is not the member itself: " + got; + } + c.Note(NoteKeys.Compared, site == "table" ? "the stored name and the member read back" : "the members read back"); + return null; + }); + if (site == "table") return named; + // in-collections-and-json: a primitive collection holds the names as a JSON array, a JSON column the name as the property's value + var inJson = r.Check("in-collections-and-json", docs, c => + { + if (stored != expectStored) return $"stored text is {stored}, expected {expectStored}"; + c.Note(NoteKeys.Compared, stored); + return null; + }); + return named ?? inJson; + } + case "unknown-name-on-read": + { + var e = site == "primitive-collection" ? Entity(PSet.Beta, PSet.Alpha) : Entity(PSet.Alpha); + db.Context.Add(e); await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var bad = site switch { "table" => "'Gamma'", "primitive-collection" => "'[\"Alpha\",\"Gamma\"]'", _ => "'{\"P\":\"Gamma\"}'" }; + await EfX.Raw(db, $"UPDATE t SET {column} = {bad}"); + return r.Check("unknown-name-throws", docs, c => + { + try + { + var back = EfX.Enumerate(Q.SetOf(db.Context, entity)).ToList(); + return "an unknown stored name read without throwing: " + (back.Count == 1 ? Safe(() => Members(back[0])) : back.Count + " rows"); + } + catch (Exception ex) when (ex is not Xunit.Sdk.XunitException) { c.Note(NoteKeys.Compared, ex.GetType().Name); return null; } + }); + } + case "non-member-on-write": + { + var e = Entity(NonMember()); + db.Context.Add(e); + return await r.Check("non-member-throws", docs, async c => + { + try { await db.Context.SaveChangesAsync(); } + catch (Exception ex) when (ex is not Xunit.Sdk.XunitException) { c.Note(NoteKeys.Compared, ex.GetType().Name); return null; } + var stored = await EfX.RawScalar(db, $"SELECT {column} FROM t"); + return "a non-member instance was written without throwing; stored: " + stored; + }); + } + default: throw new ArgumentException(ev); + } + } + static string Safe(Func<string> f) { try { return f(); } catch (Exception ex) { return "(" + ex.GetType().Name + ")"; } } +} +static class PSetNames { public static string Name(this PSet? s) => s is null ? "null" : s == PSet.Alpha ? "Alpha" : s == PSet.Beta ? "Beta" : "other"; } diff --git a/tools/probe/corpus/probes/E08OptionResultNotColumnsProbe.cs b/tools/probe/corpus/probes/E08OptionResultNotColumnsProbe.cs new file mode 100644 index 00000000..06908358 --- /dev/null +++ b/tools/probe/corpus/probes/E08OptionResultNotColumnsProbe.cs @@ -0,0 +1,76 @@ +// Claim E08-option-result-not-columns (docs/ef-core.md#option-and-result): "EF can't map an Option or a Result property, and building the model fails with an error that says so." +// Oracle: claim. Building the model (UseTypeKit on) of an entity with an Option/Result as property, key, list element or owned property throws, and the message names the Option/Result type (not a silent column). +// Obligations (claims.json), both on every row, against the docs page's section (Baseline.Docs): model-fails ("EF can't map an `Option` +// or a `Result` property": InvalidOperationException, never a column) and error-says-so ("building the model fails with an error that +// says so": the message names the type). A model that builds, or a message that does not name the type, is a violation. +using Microsoft.EntityFrameworkCore; +using CodoMetis.TypeKit; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E08OptionResultNotColumnsProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E08OptionResultNotColumnsProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static Type PlainOf(string w) => w switch { "string" => typeof(string), "int" => typeof(int), "Guid" => typeof(Guid), _ => throw new ArgumentException(w) }; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var w = r["shape.wrapped"]; var kind = r["ef.type"]; var pos = r["ef.position"]; + var plain = PlainOf(w); + var vo = EfX.Top("record-struct", w).Vo; + Type t = kind switch + { + "Option-int" => typeof(Option<>).MakeGenericType(plain), + "Option-VO" => typeof(Option<>).MakeGenericType(vo), + "Result-int-E" => typeof(Result<,>).MakeGenericType(plain, typeof(ShapeFault)), + "Result-VO-E" => typeof(Result<,>).MakeGenericType(vo, typeof(ShapeFault)), + "Result-E" => typeof(Result<>).MakeGenericType(plain), + _ => throw new ArgumentException(kind), + }; + var entity = pos switch + { + "property" => typeof(EntP<>).MakeGenericType(t), + "key" => typeof(EntK<>).MakeGenericType(t), + "collection" => typeof(EntL<>).MakeGenericType(t), + "owned-property" => typeof(EntO<>).MakeGenericType(t), + _ => throw new ArgumentException(pos), + }; + Action<Microsoft.EntityFrameworkCore.ModelBuilder> cfg = mb => + { + var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); + if (pos == "owned-property") eb.OwnsOne(typeof(Own<>).MakeGenericType(t), "O"); + }; + await using var db = new EfDb(cfg, typeKit: true); + Exception? thrown = null; + try + { + _ = db.Context.GetService<IDesignTimeModel>().Model; + await db.Context.Database.EnsureCreatedAsync(); + } + catch (Exception ex) { thrown = ex; } + var docs = Baseline.Docs("docs/ef-core.md#option-and-result"); + var built = $"the model with {t.Name} as {pos} built and created the table"; + // model-fails: building the model is refused (InvalidOperationException), never a silent column + var fails = r.Check("model-fails", docs, c => + { + if (thrown is null) return built; + if (thrown is not InvalidOperationException) return $"model build threw {thrown.GetType().Name}, not InvalidOperationException: {thrown.Message.Split('\n')[0]}"; + c.Note(NoteKeys.Compared, thrown.GetType().Name); + return null; + }); + // error-says-so: the refusal names the Option or Result type + var says = r.Check("error-says-so", docs, c => + { + if (thrown is null) return built + ", with no error to say so"; + c.Note(NoteKeys.RefusalNamesTheType, thrown.Message.Split('\n')[0]); + var friendly = t.Name.Split('`')[0] + "<"; + return thrown.Message.Contains(friendly) ? null : $"model build failed but the message does not name the type: {thrown.Message.Split('\n')[0]}"; + }); + return fails ?? says; + } +} diff --git a/tools/probe/corpus/probes/E09OrNoneInDbProbe.cs b/tools/probe/corpus/probes/E09OrNoneInDbProbe.cs new file mode 100644 index 00000000..9f18dd6b --- /dev/null +++ b/tools/probe/corpus/probes/E09OrNoneInDbProbe.cs @@ -0,0 +1,179 @@ +// Claim E09-or-none-in-database (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found): "These ask the database for one row and let it run the condition: FirstOrNoneAsync, FirstOrNone, FirstOrError read one row (WHERE ... LIMIT); SingleOrNone(Async) ask for two rows and throw for more than one; ElementAtOrNone skips to its row (OFFSET)." +// Oracle: claim. Over a table of value-object keys the operator returns what an in-memory evaluation of the same condition gives (none / the first match / the error), the executed SELECT carries the condition (WHERE) and LIMIT (OFFSET for ElementAtOrNone), and SingleOrNone throws InvalidOperationException for more than one match. +// Obligations (claims.json), one per row by ef.operator, against the README (Baseline.Docs): first-reads-one-row (FirstOrNoneAsync, +// FirstOrNone, FirstOrError), single-asks-for-two (SingleOrNoneAsync, SingleOrNone), element-at-skips (ElementAtOrNone). +// Expected: no failure for any row. The condition is written as a lambda at the call or held as an Expression<Func<T,bool>> variable. +#nullable enable +#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000 +using System.Data.Common; +using System.Linq.Expressions; +using CodoMetis.TypeKit; +using CodoMetis.TypeKit.ValueObjects; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; + +public sealed class SqlCap : DbCommandInterceptor +{ + public readonly List<string> Sql = []; + public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { Sql.Add(command.CommandText); return result; } + public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default) { Sql.Add(command.CommandText); return ValueTask.FromResult(result); } +} + +public sealed class E09OrNoneInDbProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E09OrNoneInDbProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + /// <summary>One check per row, the operator's obligation, held to the README's SQL and to the in-memory answer of the same condition.</summary> + static Task<string?> Check(Probes.Shapes.Row r) + { + var obligation = r["ef.operator"] switch + { + "FirstOrNoneAsync" or "FirstOrNone" or "FirstOrError" => "first-reads-one-row", + "SingleOrNoneAsync" or "SingleOrNone" => "single-asks-for-two", + _ => "element-at-skips", + }; + return r.Check(obligation, Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#an-option-for-nothing-found"), c => + c["shape.wrapped"] switch { "int" => Run_int(r), "Guid" => Run_Guid(r), "string" => Run_string(r), var w => throw new ArgumentException(w) }); + } + + static string Flat(string s) => s.Replace("\n", " "); + static string? Verify(string op, string data, string got, string? thrown, List<int> matching, SqlCap cap) + { + var expect = op switch + { + "FirstOrNoneAsync" or "FirstOrNone" => matching.Count == 0 ? "none" : "some:" + matching[0], + "FirstOrError" => matching.Count == 0 ? "err" : "ok:" + matching[0], + "SingleOrNoneAsync" or "SingleOrNone" => matching.Count > 1 ? "throws" : matching.Count == 0 ? "none" : "some:" + matching[0], + "ElementAtOrNone" => matching.Count > 1 ? "some:" + matching[1] : "none", + _ => throw new ArgumentException(op), + }; + var actual = thrown is not null ? (thrown == "InvalidOperationException" ? "throws" : "threw " + thrown) : got; + if (actual != expect) return $"{op} over {data}: got {actual}, expected {expect}"; + var selects = cap.Sql.Where(s => s.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase) && s.Contains("FROM \"t\"")).ToList(); + if (selects.Count == 0) return $"{op}: no SELECT was executed"; + var last = selects[^1]; + if (!last.Contains("WHERE")) return $"{op}: the condition did not reach the database: {Flat(last)}"; + if (!last.Contains("LIMIT")) return $"{op}: the SELECT has no LIMIT (reads more than the rows asked for): {Flat(last)}"; + if (op == "ElementAtOrNone" && !last.Contains("OFFSET")) return $"{op}: the SELECT has no OFFSET: {Flat(last)}"; + return null; + } + + public sealed class Ent_int { public int Id { get; set; } public PInt Key { get; set; } } + static async Task<string?> Run_int(Probes.Shapes.Row r) + { + var op = r["ef.operator"]; var data = r["ef.data"]; var cond = r["ef.condition"]; _ = r["shape.wrapped"]; + var cap = new SqlCap(); + await using var db = new EfDb(mb => { var eb = mb.Entity<Ent_int>(); eb.ToTable("t"); eb.HasKey(x => x.Id); }, typeKit: true, b => b.AddInterceptors(cap)); + await db.Context.Database.EnsureCreatedAsync(); + PInt[] keys = data == "empty" ? [] : data == "one-match" ? [PInt.From(10), PInt.From(11), PInt.From(12)] : [PInt.From(10), PInt.From(11), PInt.From(11), PInt.From(12)]; + for (var i = 0; i < keys.Length; i++) db.Context.Add(new Ent_int { Id = i + 1, Key = keys[i] }); + await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var target = PInt.From(11); + var matching = Enumerable.Range(0, keys.Length).Where(i => keys[i] == target).Select(i => i + 1).ToList(); + Expression<Func<Ent_int, bool>> held = o => o.Key == target; + IQueryable<Ent_int> q = db.Context.Set<Ent_int>().OrderBy(o => o.Id); + cap.Sql.Clear(); + var lam = cond == "lambda"; + string got = ""; string? thrown = null; + try + { + switch (op) + { + case "FirstOrNoneAsync": got = Show(lam ? await q.FirstOrNoneAsync(o => o.Key == target) : await q.FirstOrNoneAsync(held)); break; + case "FirstOrNone": got = Show(lam ? q.FirstOrNone(o => o.Key == target) : q.FirstOrNone(held)); break; + case "SingleOrNoneAsync": got = Show(lam ? await q.SingleOrNoneAsync(o => o.Key == target) : await q.SingleOrNoneAsync(held)); break; + case "SingleOrNone": got = Show(lam ? q.SingleOrNone(o => o.Key == target) : q.SingleOrNone(held)); break; + case "ElementAtOrNone": got = Show(lam ? q.Where(o => o.Key == target).ElementAtOrNone(1) : q.Where(held).ElementAtOrNone(1)); break; + case "FirstOrError": + var res = lam ? q.FirstOrError(o => o.Key == target, ShapeFault.Refused) : q.FirstOrError(held, ShapeFault.Refused); + got = res.Match(x => "ok:" + x.Id, _ => "err"); break; + default: throw new ArgumentException(op); + } + } + catch (Exception ex) { thrown = ex.GetType().Name; } + return Verify(op, data, got, thrown, matching, cap); + } + static string Show(Option<Ent_int> o) => o.Match(x => "some:" + x.Id, () => "none"); + + public sealed class Ent_Guid { public int Id { get; set; } public PGuid Key { get; set; } } + static async Task<string?> Run_Guid(Probes.Shapes.Row r) + { + var op = r["ef.operator"]; var data = r["ef.data"]; var cond = r["ef.condition"]; _ = r["shape.wrapped"]; + var cap = new SqlCap(); + await using var db = new EfDb(mb => { var eb = mb.Entity<Ent_Guid>(); eb.ToTable("t"); eb.HasKey(x => x.Id); }, typeKit: true, b => b.AddInterceptors(cap)); + await db.Context.Database.EnsureCreatedAsync(); + PGuid[] keys = data == "empty" ? [] : data == "one-match" ? [PGuid.From(new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), PGuid.From(new Guid(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), PGuid.From(new Guid(3, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))] : [PGuid.From(new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), PGuid.From(new Guid(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), PGuid.From(new Guid(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), PGuid.From(new Guid(3, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))]; + for (var i = 0; i < keys.Length; i++) db.Context.Add(new Ent_Guid { Id = i + 1, Key = keys[i] }); + await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var target = PGuid.From(new Guid(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); + var matching = Enumerable.Range(0, keys.Length).Where(i => keys[i] == target).Select(i => i + 1).ToList(); + Expression<Func<Ent_Guid, bool>> held = o => o.Key == target; + IQueryable<Ent_Guid> q = db.Context.Set<Ent_Guid>().OrderBy(o => o.Id); + cap.Sql.Clear(); + var lam = cond == "lambda"; + string got = ""; string? thrown = null; + try + { + switch (op) + { + case "FirstOrNoneAsync": got = Show(lam ? await q.FirstOrNoneAsync(o => o.Key == target) : await q.FirstOrNoneAsync(held)); break; + case "FirstOrNone": got = Show(lam ? q.FirstOrNone(o => o.Key == target) : q.FirstOrNone(held)); break; + case "SingleOrNoneAsync": got = Show(lam ? await q.SingleOrNoneAsync(o => o.Key == target) : await q.SingleOrNoneAsync(held)); break; + case "SingleOrNone": got = Show(lam ? q.SingleOrNone(o => o.Key == target) : q.SingleOrNone(held)); break; + case "ElementAtOrNone": got = Show(lam ? q.Where(o => o.Key == target).ElementAtOrNone(1) : q.Where(held).ElementAtOrNone(1)); break; + case "FirstOrError": + var res = lam ? q.FirstOrError(o => o.Key == target, ShapeFault.Refused) : q.FirstOrError(held, ShapeFault.Refused); + got = res.Match(x => "ok:" + x.Id, _ => "err"); break; + default: throw new ArgumentException(op); + } + } + catch (Exception ex) { thrown = ex.GetType().Name; } + return Verify(op, data, got, thrown, matching, cap); + } + static string Show(Option<Ent_Guid> o) => o.Match(x => "some:" + x.Id, () => "none"); + + public sealed class Ent_string { public int Id { get; set; } public PStr Key { get; set; } } + static async Task<string?> Run_string(Probes.Shapes.Row r) + { + var op = r["ef.operator"]; var data = r["ef.data"]; var cond = r["ef.condition"]; _ = r["shape.wrapped"]; + var cap = new SqlCap(); + await using var db = new EfDb(mb => { var eb = mb.Entity<Ent_string>(); eb.ToTable("t"); eb.HasKey(x => x.Id); }, typeKit: true, b => b.AddInterceptors(cap)); + await db.Context.Database.EnsureCreatedAsync(); + PStr[] keys = data == "empty" ? [] : data == "one-match" ? [PStr.From("val-a"), PStr.From("val-b"), PStr.From("val-c")] : [PStr.From("val-a"), PStr.From("val-b"), PStr.From("val-b"), PStr.From("val-c")]; + for (var i = 0; i < keys.Length; i++) db.Context.Add(new Ent_string { Id = i + 1, Key = keys[i] }); + await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var target = PStr.From("val-b"); + var matching = Enumerable.Range(0, keys.Length).Where(i => keys[i] == target).Select(i => i + 1).ToList(); + Expression<Func<Ent_string, bool>> held = o => o.Key == target; + IQueryable<Ent_string> q = db.Context.Set<Ent_string>().OrderBy(o => o.Id); + cap.Sql.Clear(); + var lam = cond == "lambda"; + string got = ""; string? thrown = null; + try + { + switch (op) + { + case "FirstOrNoneAsync": got = Show(lam ? await q.FirstOrNoneAsync(o => o.Key == target) : await q.FirstOrNoneAsync(held)); break; + case "FirstOrNone": got = Show(lam ? q.FirstOrNone(o => o.Key == target) : q.FirstOrNone(held)); break; + case "SingleOrNoneAsync": got = Show(lam ? await q.SingleOrNoneAsync(o => o.Key == target) : await q.SingleOrNoneAsync(held)); break; + case "SingleOrNone": got = Show(lam ? q.SingleOrNone(o => o.Key == target) : q.SingleOrNone(held)); break; + case "ElementAtOrNone": got = Show(lam ? q.Where(o => o.Key == target).ElementAtOrNone(1) : q.Where(held).ElementAtOrNone(1)); break; + case "FirstOrError": + var res = lam ? q.FirstOrError(o => o.Key == target, ShapeFault.Refused) : q.FirstOrError(held, ShapeFault.Refused); + got = res.Match(x => "ok:" + x.Id, _ => "err"); break; + default: throw new ArgumentException(op); + } + } + catch (Exception ex) { thrown = ex.GetType().Name; } + return Verify(op, data, got, thrown, matching, cap); + } + static string Show(Option<Ent_string> o) => o.Match(x => "some:" + x.Id, () => "none"); +} +public readonly partial record struct PInt : IValue<int>; +public readonly partial record struct PGuid : IValue<Guid>; +public readonly partial record struct PStr : IValue<string>; diff --git a/tools/probe/corpus/probes/E10ReadBackSkipsValidationProbe.cs b/tools/probe/corpus/probes/E10ReadBackSkipsValidationProbe.cs new file mode 100644 index 00000000..5daa5fc1 --- /dev/null +++ b/tools/probe/corpus/probes/E10ReadBackSkipsValidationProbe.cs @@ -0,0 +1,120 @@ +// Claim E10-read-back-skips-validation (docs/ef-core.md#reading-back-without-validation): "EF reads a column back without validation, through the value object's Materialize rather than Create." Revalidate() finds the rows today's rules reject. +// Oracle: claim. A row holding a value the value object's Create refuses (written by a plain-typed twin of the entity into the same table) is read back by EF with UseTypeKit: no exception, .Value is the stored value, Revalidate() is an Error. +// Obligations (claims.json), both on every row, against the docs page (Baseline.Docs): reads-without-create (the row reads back, +// .Value as stored) and revalidate-finds (Revalidate() is an Error). +// Expected: no failure for any row. +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E10ReadBackSkipsValidationProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E10ReadBackSkipsValidationProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static object Refused(string w) => w switch + { + "string" => "SECRET-7f3a", "int" => -1, "Guid" => Guid.Empty, "decimal" => -1m, "DateTime" => DateTime.MinValue, "Uri" => new Uri("https://example.com/refused"), + _ => throw new ArgumentException(w), + }; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var w = r["shape.wrapped"]; var kind = r["shape.kind"]; var mapping = r["ef.mapping"]; + var e = Probes.Ef3.E3.Find(kind, w); + var refused = Refused(w); + // the premise: Create refuses this value + var create = e.Vo.GetMethod("Create", BindingFlags.Public | BindingFlags.Static, [e.Plain])!; + var created = create.Invoke(null, [refused])!; + if (created.GetType().GetProperty("State")!.GetValue(created)!.ToString() != "Error") { r.Outcome("harness-error", "Create accepts the supposedly refused value"); return null; } + var voEntity = EfModel.Entity(mapping, e.Vo); + var plainEntity = EfModel.Entity(mapping, e.Plain); + await using var vo = new EfDb(EfModel.Configure(mapping, voEntity, null, false), typeKit: true); + Exception? voModel = null, plainModel = null, written = null; + try { await vo.Context.Database.EnsureCreatedAsync(); } + catch (Exception ex) + { + voModel = ex; + // the wrapped twin's table on its own, to tell a model the framework refuses from one the value object breaks + await using var alone = new EfDb(EfModel.Configure(mapping, plainEntity, null, false), typeKit: false); + try { await alone.Context.Database.EnsureCreatedAsync(); } catch (Exception pex) { plainModel = pex; } + } + object? read = null; string? readFailed = null; + if (voModel is null) + { + // a plain-typed twin on the same connection writes the row (nothing validates a plain value) + var twinCfg = EfModel.Configure(mapping, plainEntity, p => { if (mapping == "key") p.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never; }, false); + var twinOpts = new DbContextOptionsBuilder<DynCtx>().UseSqlite(vo.Connection).ReplaceService<Microsoft.EntityFrameworkCore.Infrastructure.IModelCacheKeyFactory, UniqueModelKey>().Options; + await using (var twin = new DynCtx(twinOpts, twinCfg)) + { + var row = Activator.CreateInstance(plainEntity)!; + if (mapping == "key") { EfX.Set(row, "Id", refused); EfX.Set(row, "Tag", "t"); } + else + { + EfX.Set(row, "Id", 1); + object? holder = row; + if (mapping is "owned" or "complex") { var prop = mapping == "owned" ? "O" : "C"; holder = EfX.Get(row, prop); } + EfX.Set(holder!, "P", refused); + } + twin.Add(row); + try { await twin.SaveChangesAsync(); } catch (Exception ex) { written = ex; } + } + if (written is null) + try + { + var list = EfX.Enumerate(Q.SetOf(vo.Context, voEntity)).ToList(); + if (list.Count != 1) readFailed = $"expected 1 row, read {list.Count}"; + else read = list[0]; + } + catch (Exception ex) { readFailed = $"reading the row back threw {ex.GetType().Name}: {ex.Message.Split('\n')[0]}"; } + } + object? vobj = read is null ? null : mapping switch + { + "key" => EfX.Get(read, "Id"), + "owned" => EfX.Get(EfX.Get(read, "O")!, "P"), + "complex" => EfX.Get(EfX.Get(read, "C")!, "P"), + _ => EfX.Get(read, "P"), + }; + var docs = Baseline.Docs("docs/ef-core.md#reading-back-without-validation"); + // Where the row could not be written, neither check has anything to read: the model refused (compared with the twin's), or the + // twin's write of the refused value (nothing documents why a plain value would be refused). + string? Unwritten(Probes.Shapes.Check c, out bool ended) + { + ended = true; + if (voModel is not null) return EfSides.Threw(c, voModel, plainModel, "creating the table", out var refused) ? refused : null; + if (written is not null) return c.Inconclusive($"the plain twin could not write the value Create refuses: {written.GetType().Name}: {written.Message.Split('\n')[0]}"); + ended = false; + return null; + } + + // reads-without-create: the stored value Create refuses is read back, as it is stored + var reads = r.Check("reads-without-create", docs, c => + { + var unwritten = Unwritten(c, out var ended); + if (ended) return unwritten; + if (readFailed is not null) return readFailed; + if (vobj is null) return "the value object read back is null"; + var stored = vobj.GetType().GetProperty("Value")!.GetValue(vobj); + if (!Equals(stored, refused)) return $"read-back Value is {stored}, the row holds {refused}"; + c.Note(NoteKeys.StoredFactoryReadUnvalidated, $"{refused}"); + return null; + }); + // revalidate-finds: Revalidate() of what was read back is the refusal Create gives today + var revalidates = r.Check("revalidate-finds", docs, c => + { + var unwritten = Unwritten(c, out var ended); + if (ended) return unwritten; + if (vobj is null) return c.Inconclusive("nothing was read back to revalidate: " + (readFailed ?? "the value object read back is null")); + var reval = vobj.GetType().GetMethod("Revalidate", Type.EmptyTypes); + if (reval is null) return "no Revalidate() on " + vobj.GetType().Name; + var res = reval.Invoke(vobj, null)!; + var state = res.GetType().GetProperty("State")!.GetValue(res)!.ToString(); + if (state != "Error") return $"Revalidate() of the stored refused value is {state}, expected Error"; + c.Note(NoteKeys.Compared, "Revalidate() is an Error"); + return null; + }); + return reads ?? revalidates; + } +} diff --git a/tools/probe/corpus/probes/E11UseTypeKitOrderProbe.cs b/tools/probe/corpus/probes/E11UseTypeKitOrderProbe.cs new file mode 100644 index 00000000..df2415f4 --- /dev/null +++ b/tools/probe/corpus/probes/E11UseTypeKitOrderProbe.cs @@ -0,0 +1,118 @@ +// Claim E11-usetypekit-order (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#setup): "UseTypeKit() adds plugins ... It replaces nothing. So it works alongside a provider ... and it doesn't matter whether you call it before or after UseNpgsql or UseSqlite." +// Obligation (claims.json): order-does-not-matter, on every row, held to the README's sentence (Baseline.Docs) by the differential below. +// Oracle: differential between the two orders (and, where a plain type maps the same way, the wrapped type): the model, the CREATE script, the raw stored row and the values read back are equal in every mapping. +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E11UseTypeKitOrderProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E11UseTypeKitOrderProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static readonly string[] Ws = ["string", "int", "decimal", "Guid", "long"]; + + static async Task<string> Raw(EfDb2 db) + { + await using var cmd = db.Connection.CreateCommand(); cmd.CommandText = "SELECT * FROM t ORDER BY 1"; + await using DbDataReader rd = await cmd.ExecuteReaderAsync(); + var lines = new List<string>(); + while (await rd.ReadAsync()) lines.Add(string.Join("|", Enumerable.Range(0, rd.FieldCount).Select(i => rd.IsDBNull(i) ? "NULL" : rd.GetValue(i).GetType().Name + ":" + Convert.ToString(rd.GetValue(i), System.Globalization.CultureInfo.InvariantCulture)))); + return string.Join(";", lines); + } + + sealed record Out(string Model, string Script, string Raw, string Read); + + /// <summary>One order's model, script, stored row and values; a refusal anywhere on the way is that order's result, compared like the others.</summary> + static async Task<Out> Run(string order, string mapping, Type entity, Func<int, object?> value, Action<ModelBuilder> cfg, string? wrapped) + { + try { return await RunUnguarded(order, mapping, entity, value, cfg, wrapped); } + catch (Exception ex) { return new Out($"threw {ex.GetType().Name}: {ex.Message.Split('\n')[0]}", "", "", ""); } + } + + static async Task<Out> RunUnguarded(string order, string mapping, Type entity, Func<int, object?> value, Action<ModelBuilder> cfg, string? wrapped) + { + await using var db = new EfDb2(cfg, order); + var model = EfModel.Describe(db.Context); + var script = db.Context.Database.GenerateCreateScript(); + await db.Context.Database.EnsureCreatedAsync(); + var set = new List<object>(); + object Mk() => EfX.New(entity); + switch (mapping) + { + case "property" or "closed-set": { var x = Mk(); EfX.Set(x, "P", value(0)); set.Add(x); break; } + case "nullable": { var x = Mk(); EfX.Set(x, "P", value(0)); set.Add(x); var y = Mk(); EfX.Set(y, "Id", 2); set.Add(y); break; } + case "key": { var x = Mk(); EfX.Set(x, "Id", value(0)); EfX.Set(x, "Tag", "a"); set.Add(x); var y = Mk(); EfX.Set(y, "Id", value(1)); EfX.Set(y, "Tag", "b"); set.Add(y); break; } + case "integer-key": { set.Add(Mk()); set.Add(Mk()); break; } + case "owned" or "complex": + { + var x = Mk(); var prop = mapping == "owned" ? "O" : "C"; var own = EfX.Get(x, prop)!; EfX.Set(own, "P", value(0)); set.Add(x); break; + } + } + foreach (var x in set) db.Context.Add(x); + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + var raw = await Raw(db); + var read = string.Join(";", EfX.Enumerate(Q.SetOf(db.Context, entity)).Select(x => mapping switch + { + "property" or "nullable" or "closed-set" => EfX.Plainify(EfX.Get(x, "P")) + "/" + EfX.Plainify(EfX.Get(x, "Id")), + "key" => EfX.Plainify(EfX.Get(x, "Id")) + "/" + EfX.Get(x, "Tag"), + "integer-key" => EfX.Plainify(EfX.Get(x, "Id")), + _ => EfX.Plainify(EfX.Get(EfX.Get(x, mapping == "owned" ? "O" : "C")!, "P")), + }).OrderBy(s => s, StringComparer.Ordinal)); + return new Out(model, script, raw, read); + } + + static async Task<string?> Check(Probes.Shapes.Row r) + { + return await r.Check("order-does-not-matter", Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#setup"), async c => + { + var mapping = r["ef.mapping"]; var order = r["ef.order"]; + var other = order == "before-provider" ? "after-provider" : "before-provider"; + string w = mapping switch { "integer-key" => r.Index % 2 == 0 ? "int" : "long", "key" => r.Index % 2 == 0 ? "Guid" : "string", "closed-set" => "-", _ => Ws[r.Index % Ws.Length] }; + Out a, b; Out? plain = null; + if (mapping == "closed-set") + { + var entity = typeof(EntP<PSet>); + Action<ModelBuilder> cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); }; + a = await Run(order, mapping, entity, _ => PSet.Beta, cfg, null); + b = await Run(other, mapping, entity, _ => PSet.Beta, cfg, null); + if (!a.Raw.Contains("String:Beta")) return "closed set not stored as its name text: " + a.Raw; + } + else + { + var kind = EfX.Kinds[r.Index % EfX.Kinds.Length]; + var e = mapping is "key" or "integer-key" ? KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == w) : EfX.Top(kind, w); + var map = mapping == "integer-key" ? "key" : mapping; + var entity = EfModel.Entity(map, e.Vo); + var cfg = EfModel.Configure(map, entity, null, false); + Func<int, object?> value = i => e.MakeVo(EfX.Sample(w, i)); + a = await Run(order, mapping, entity, value, cfg, w); + b = await Run(other, mapping, entity, value, cfg, w); + if (mapping is "property" or "nullable" or "owned" or "complex") + { + var pe = EfModel.Entity(map, e.Plain); + plain = await Run("none", mapping, pe, i => EfX.Sample(w, i), EfModel.Configure(map, pe, null, false), w); + } + else if (mapping == "integer-key") + { + var ids = a.Read.Split(';'); + if (ids.Distinct().Count() != 2 || ids.Any(x => x is "0" or "")) return "integer key not generated after " + order + ": " + a.Read; + } + } + if (a.Model != b.Model) return $"model differs between {order} and {other}: " + EfModel.FirstDiff(a.Model, b.Model); + if (a.Model.StartsWith("threw ", StringComparison.Ordinal)) return $"the value object's entity is refused in both orders: {a.Model}"; + if (a.Script != b.Script) return $"create script differs between {order} and {other}: " + EfModel.FirstDiff(a.Script, b.Script); + if (a.Raw != b.Raw) return $"stored row differs between {order} and {other}: {a.Raw} | {b.Raw}"; + if (a.Read != b.Read) return $"values read differ between {order} and {other}: {a.Read} | {b.Read}"; + if (plain is not null) + { + if (a.Model != plain.Model) return "model differs from the wrapped type's: " + EfModel.FirstDiff(a.Model, plain.Model); + if (a.Raw != plain.Raw) return $"stored row differs from the wrapped type's: {a.Raw} | {plain.Raw}"; + } + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E12ConventionByTypeProbe.cs b/tools/probe/corpus/probes/E12ConventionByTypeProbe.cs new file mode 100644 index 00000000..0c2581b9 --- /dev/null +++ b/tools/probe/corpus/probes/E12ConventionByTypeProbe.cs @@ -0,0 +1,71 @@ +// Claim E12-convention-by-vo-type (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#model-wide-conventions-follow-the-propertys-type): "EF applies ConfigureConventions by CLR type. So Properties<decimal>().HavePrecision(18, 2) reaches decimal properties, and not an Amount that wraps one. Configure the value object instead: Properties<Amount>().HavePrecision(18, 2)." +// Obligations (claims.json), one per row by ef.convention-type: value-object-type-reaches (value-object), against the same convention on +// the wrapped type and a plain property (Baseline.Twin); underlying-type-does-not-reach (underlying), against the README (Baseline.Docs). +// Oracle: differential + claim. A convention on the value object type reaches the value object's property exactly as the same convention on the wrapped type reaches a plain property; a convention on the wrapped type does not reach the value object's property (its model equals the model without the convention) while it does reach the plain one. +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E12ConventionByTypeProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E12ConventionByTypeProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static string PLine(EfDb2 db) => EfModel.Describe(db.Context).Split('\n').First(l => l.StartsWith("t.P: ")); + + static Action<ModelConfigurationBuilder> Conv(string facet, Type target, string w) => cb => + { + var pb = cb.Properties(target); + switch (facet) + { + case "HaveMaxLength": pb.HaveMaxLength(5); break; + case "HavePrecision": pb.HavePrecision(18, 2); break; + case "HaveColumnType": pb.HaveColumnType(EfModel.ColumnTypes[w]); break; + } + }; + + static async Task<string> Line(Type t, string order, Action<ModelConfigurationBuilder>? conv) + { + var entity = typeof(EntP<>).MakeGenericType(t); + await using var db = new EfDb2(mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); }, order, conv); + return PLine(db); + } + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var facet = r["ef.convention"]; var ct = r["ef.convention-type"]; var w = r["shape.wrapped"]; + var e = EfX.Top(EfX.Kinds[r.Index % EfX.Kinds.Length], w); + var plainNone = await Line(e.Plain, "none", null); + var plainConv = await Line(e.Plain, "none", Conv(facet, e.Plain, w)); + if (plainConv == plainNone) + { + // the facet changes nothing on the wrapped type's own property (HavePrecision on a string): it cannot show anything on the value object either + var fallback = w is "string" or "Uri" ? "HaveMaxLength" : w == "decimal" ? "HavePrecision" : "HaveColumnType"; + r.Substituted("ef.convention", fallback, $"{facet} has no effect on a plain {w} property"); + facet = fallback; + plainConv = await Line(e.Plain, "none", Conv(facet, e.Plain, w)); + } + var underlying = ct == "underlying"; + // value-object-type-reaches: as the same convention on the wrapped type reaches a plain property (the twin); + // underlying-type-does-not-reach: the README's "reaches `decimal` properties, and not an `Amount` that wraps one" + return await r.Check(underlying ? "underlying-type-does-not-reach" : "value-object-type-reaches", + underlying ? Baseline.Docs("src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#model-wide-conventions-follow-the-propertys-type") : Baseline.Twin, async c => + { + if (plainConv == plainNone) return c.Inconclusive($"{facet} changes nothing on a plain {w} property either, so there is nothing to compare"); + var voNone = await Line(e.Vo, "after-provider", null); + if (underlying) + { + var reached = await Line(e.Vo, "after-provider", Conv(facet, e.Plain, w)); + if (reached != voNone) return $"a convention on {w} reached the value object's property: {reached} (without it: {voNone}); the plain property got {plainConv}"; + c.Note(NoteKeys.Compared, $"{facet} on {w} reaches the plain property ({plainConv}), not the value object's ({reached})"); + return null; + } + var voConv = await Line(e.Vo, "after-provider", Conv(facet, e.Vo, w)); + if (voConv == voNone) return $"a convention on the value object type {e.Vo.Name} did not reach its property: {voConv}"; + if (voConv != plainConv) return $"the convention on the value object gave {voConv}; on the plain {w} {plainConv}"; + c.Note(NoteKeys.Compared, voConv); + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E13PrimitiveCollectionProbe.cs b/tools/probe/corpus/probes/E13PrimitiveCollectionProbe.cs new file mode 100644 index 00000000..342ac09e --- /dev/null +++ b/tools/probe/corpus/probes/E13PrimitiveCollectionProbe.cs @@ -0,0 +1,89 @@ +// Claim E13-primitive-collections (README#queries): "db.Orders.Where(o => o.Tags.Contains(tag)); db.Orders.Where(o => o.Tags.Any(t => t.Value == "x"));" over a List<ValueObject> primitive collection. +// Obligations (claims.json), one per row by ef.query, against the ids memory computes (Baseline.Model): element-value (any-value-equals, +// "db.Orders.Where(o => o.Tags.Any(t => t.Value == "x"));") and contains ("db.Orders.Where(o => o.Tags.Contains(tag));"). +// Oracle: claim plus differential. The ids the query returns equal the ids whose collection holds the value (ground truth computed in memory), and equal what the same query returns over List<wrapped type>. +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E13PrimitiveCollectionProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E13PrimitiveCollectionProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var w = r["shape.wrapped"]; var qk = r["ef.query"]; + var kind = EfX.Kinds[r.Index % 2]; // record-struct and sealed-record-class: a validated kind's rule may refuse a sample + var e = EfX.Top(kind, w); + int[][] data = [[0, 1], [1, 2], [2], []]; + var needles = new[] { 0, 1, 2, 3 }; // 3 is in no collection + + async Task<(string res, string? err)> Side(bool vo) + { + var el = vo ? e.Vo : e.Plain; + var entity = typeof(EntL<>).MakeGenericType(el); + object Make(int i) { var p = EfX.Sample(w, i); return vo ? e.MakeVo(p) : p; } + Action<Microsoft.EntityFrameworkCore.ModelBuilder> cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); }; + await using var db = new EfDb(cfg, typeKit: vo); + try + { + await db.Context.Database.EnsureCreatedAsync(); + for (var i = 0; i < data.Length; i++) + { + var ent = EfX.New(entity); EfX.Set(ent, "Id", i + 1); + var list = EfX.Get(ent, "Items")!; + foreach (var k in data[i]) list.GetType().GetMethod("Add")!.Invoke(list, [Make(k)]); + db.Context.Add(ent); + } + await db.Context.SaveChangesAsync(); db.Context.ChangeTracker.Clear(); + var lines = new List<string>(); + foreach (var n in needles) + { + var p = Expression.Parameter(entity, "e"); + var items = Expression.Property(p, "Items"); + Expression body; + if (qk == "contains") + { + var box = Expression.Constant(Q.NewBox(el, Make(n))); + body = Expression.Call(items, typeof(List<>).MakeGenericType(el).GetMethod("Contains")!, Expression.Field(box, "Value")); + } + else + { + var t = Expression.Parameter(el, "t"); + var plainBox = Expression.Constant(Q.NewBox(e.Plain, EfX.Sample(w, n))); + var lhs = vo ? Expression.Property(t, "Value") : (Expression)t; + var inner = Expression.Lambda(Expression.Equal(lhs, Expression.Field(plainBox, "Value")), t); + var any = typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(el); + body = Expression.Call(any, items, inner); + } + var q = Q.Call("Where", [entity], Q.SetOf(db.Context, entity), Expression.Lambda(body, p)); + lines.Add($"{n}:" + string.Join(",", EfX.Enumerate(q).Select(x => (int)EfX.Get(x, "Id")!).OrderBy(x => x))); + db.Context.ChangeTracker.Clear(); + } + return (string.Join(" ", lines), null); + } + catch (Exception ex) { return ("", ex.GetType().Name + ": " + ex.Message.Split('\n')[0]); } + } + var truth = string.Join(" ", needles.Select(n => $"{n}:" + string.Join(",", Enumerable.Range(0, data.Length).Where(i => data[i].Contains(n)).Select(i => i + 1)))); + var v = await Side(true); + // element-value (Any over .Value) and contains (Contains of a value object): the ids memory finds in the same collections + return await r.Check(qk == "contains" ? "contains" : "element-value", Baseline.Model, async c => + { + if (v.err is null) + { + if (v.res != truth) return $"{qk} returned [{v.res}], the collections hold [{truth}]"; + c.Note(NoteKeys.Compared, truth); + return null; + } + // the value object's query failed: the same query over List<wrapped type> tells the framework's refusal from the value object's + var p = await Side(false); + if (p.err is null) return $"{qk} over List<{w} value object> threw ({v.err}) where List<{w}> returned [{p.res}]"; + return v.err.Split(':')[0] == p.err.Split(':')[0] + ? c.Inconclusive($"both sides refuse {qk} alike: {v.err}") + : $"{qk}: the value object's side threw {v.err}, the wrapped type's {p.err}"; + }); + } +} diff --git a/tools/probe/corpus/probes/E14InheritanceKeyProbe.cs b/tools/probe/corpus/probes/E14InheritanceKeyProbe.cs new file mode 100644 index 00000000..e30500c5 --- /dev/null +++ b/tools/probe/corpus/probes/E14InheritanceKeyProbe.cs @@ -0,0 +1,95 @@ +// Claim E14-inheritance-key-generation (README#keys): "The root of an inheritance hierarchy follows the same rules under TPH and TPT. Under TPC, as for an int key, the key is generated only where the provider numbers the whole hierarchy from a sequence (PostgreSQL, SQL Server) ... SQLite has no such sequence, so there you assign it." Key over Guid: yours to assign. +// Oracle: differential against the same hierarchy keyed by the wrapped type (default keys on two leaf types, SaveChanges, the keys read back; an int/long key over TPH/TPT must be generated, distinct and non-zero), +// plus the claim for assigned keys (obligations tph-tpt-root for TPH and TPT, tpc-assign-on-sqlite for TPC, Baseline.Twin): every hierarchy round-trips assigned keys polymorphically, and a Guid key is never generated by default. +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E14InheritanceKeyProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E14InheritanceKeyProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static Action<Microsoft.EntityFrameworkCore.ModelBuilder> Cfg(string strategy, Type root, Type a, Type b) => mb => + { + var rb = mb.Entity(root); rb.HasKey("Id"); + var ab = mb.Entity(a); var bb = mb.Entity(b); + switch (strategy) + { + case "TPH": rb.ToTable("root"); break; + case "TPT": rb.UseTptMappingStrategy(); rb.ToTable("root"); ab.ToTable("leafa"); bb.ToTable("leafb"); break; + case "TPC": rb.UseTpcMappingStrategy(); ab.ToTable("leafa"); bb.ToTable("leafb"); break; + } + }; + + static async Task<string?> Check(Probes.Shapes.Row r) + { + // tph-tpt-root (TPH, TPT) and tpc-assign-on-sqlite (TPC), each against the same hierarchy keyed by the wrapped type + return await r.Check(r["ef.inheritance"] == "TPC" ? "tpc-assign-on-sqlite" : "tph-tpt-root", Baseline.Twin, async c => + { + var w = r["shape.wrapped"]; var strategy = r["ef.inheritance"]; + var kind = EfX.Kinds[r.Index % EfX.Kinds.Length]; + var e = KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == w); + + // phase A: default keys on one leaf of each type + async Task<string> Default(bool vo) + { + var kt = vo ? e.Vo : e.Plain; + Type root = typeof(RootE<>).MakeGenericType(kt), a = typeof(LeafA<>).MakeGenericType(kt), b = typeof(LeafB<>).MakeGenericType(kt); + await using var db = new EfDb(Cfg(strategy, root, a, b), typeKit: vo); + try + { + await db.Context.Database.EnsureCreatedAsync(); + var gen = db.Context.Model.FindEntityType(root)!.FindProperty("Id")!.ValueGenerated; + var items = new[] { EfX.New(a), EfX.New(b), EfX.New(a) }; + foreach (var x in items) db.Context.Add(x); + await db.Context.SaveChangesAsync(); + return $"gen={gen} keys=[" + string.Join(",", items.Select(x => EfX.Plainify(EfX.Get(x, "Id")))) + "]"; + } + catch (Exception ex) { return "threw:" + ex.GetType().Name; } + } + // phase B: assigned keys, polymorphic read back + async Task<string> Assigned(bool vo) + { + var kt = vo ? e.Vo : e.Plain; + Type root = typeof(RootE<>).MakeGenericType(kt), a = typeof(LeafA<>).MakeGenericType(kt), b = typeof(LeafB<>).MakeGenericType(kt); + await using var db = new EfDb(Cfg(strategy, root, a, b), typeKit: vo); + await db.Context.Database.EnsureCreatedAsync(); + object K(int i) { var p = EfX.Sample(w, i); return vo ? e.MakeVo(p) : p; } + var items = new[] { (a, 0, "a0"), (b, 1, "b1"), (a, 2, "a2") }; + foreach (var (t, i, name) in items) { var x = EfX.New(t); EfX.Set(x, "Id", K(i)); EfX.Set(x, "Name", name); db.Context.Add(x); } + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + var all = EfX.Enumerate(Q.SetOf(db.Context, root)).Select(x => $"{x.GetType().Name.Split('`')[0]}:{EfX.Get(x, "Name")}:{EfX.Plainify(EfX.Get(x, "Id"))}").OrderBy(s => s, StringComparer.Ordinal); + db.Context.ChangeTracker.Clear(); + var found = await db.Context.FindAsync(root, K(1)); + return string.Join(",", all) + " find1=" + (found is null ? "null" : (string)EfX.Get(found, "Name")!); + } + + var vA = await Default(true); + var vB = await Assigned(true); + var pB = await Assigned(false); + if (vB != pB) return $"assigned keys differ from the plain hierarchy: VO {vB} | plain {pB}"; + if (!vB.Contains("find1=b1")) return "Find by the assigned key failed: " + vB; + + if (w == "Guid") + { + // documented: yours to assign, never generated by default (EF generates a plain Guid key; TypeKit's is Never): not-promised guid-key-not-generated + if (!vA.StartsWith("gen=Never") && !vA.StartsWith("threw")) return "Guid key under " + strategy + " is not 'yours to assign': " + vA; + return null; + } + var pA = await Default(false); + if (vA != pA) return $"default keys differ from the plain hierarchy under {strategy}: VO {vA} | plain {pA}"; + if (strategy is "TPH" or "TPT") + { + // documented: the root of a TPH or TPT hierarchy is generated + var keys = vA.Substring(vA.IndexOf('[') + 1).TrimEnd(']').Split(','); + if (!vA.StartsWith("gen=OnAdd") || keys.Distinct().Count() != 3 || keys.Any(k => k is "0" or "")) return $"{strategy} root key is not generated on add: {vA}"; + } + else r.Note(NoteKeys.TpcSqliteNoSequenceAssignChecked); + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E15CompositeKeyProbe.cs b/tools/probe/corpus/probes/E15CompositeKeyProbe.cs new file mode 100644 index 00000000..a6e1a5d2 --- /dev/null +++ b/tools/probe/corpus/probes/E15CompositeKeyProbe.cs @@ -0,0 +1,95 @@ +// Claim E15-composite-key (docs/ef-core.md#keys): "A composite key of value objects is HasKey(x => new { x.Order, x.Line }), as for primitives." +// Obligation (claims.json): as-for-primitives, on every row, against the composite key over the wrapped types (Baseline.Twin). +// Oracle: differential against the same composite key declared over the wrapped types: same model description (key, column types, generation), +// same Find / equality-query / ordered results, same refusal of a duplicate key; and the claim's own ground truth for the lookups. +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E15CompositeKeyProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E15CompositeKeyProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + return await r.Check("as-for-primitives", Baseline.Twin, async c => + { + var w = r["shape.wrapped"]; + var first = r["ef.key-position"] == "first"; + var kind = EfX.Kinds[r.Index % EfX.Kinds.Length]; + var ew = KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == w); + var ei = KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == "int"); + // the wrapped-type key under test sits at the requested position; the other element is a value object over int (a "Line") + object WPlain(int i) => EfX.Sample(w, i); + object IPlain(int i) => 1 + i; + + async Task<(string desc, string truth)> Side(bool vo) + { + Type tw = vo ? ew.Vo : ew.Plain, ti = vo ? ei.Vo : ei.Plain; + var (ta, tb) = first ? (tw, ti) : (ti, tw); + var entity = typeof(EntComp<,>).MakeGenericType(ta, tb); + object W(int i) => vo ? ew.MakeVo(WPlain(i)) : WPlain(i); + object I(int i) => vo ? ei.MakeVo(IPlain(i)) : IPlain(i); + (object a, object b) Key(int wi, int ii) => first ? (W(wi), I(ii)) : (I(ii), W(wi)); + Action<Microsoft.EntityFrameworkCore.ModelBuilder> cfg = mb => + { + var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("A", "B"); + }; + await using var db = new EfDb(cfg, typeKit: vo); + await db.Context.Database.EnsureCreatedAsync(); + var model = EfModel.Describe(db.Context); + var pk = string.Join(",", db.Context.Model.FindEntityType(entity)!.FindPrimaryKey()!.Properties.Select(p => p.Name)); + (int wi, int ii)[] seeds = [(0, 0), (0, 1), (1, 0)]; + foreach (var (wi, ii) in seeds) + { + var e = EfX.New(entity); var (a, b) = Key(wi, ii); + EfX.Set(e, "A", a); EfX.Set(e, "B", b); EfX.Set(e, "Tag", $"t{wi}{ii}"); + db.Context.Add(e); + } + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + var lines = new List<string> { "pk=" + pk, model }; + // Find by the whole key + foreach (var (wi, ii) in new[] { (0, 0), (0, 1), (1, 0), (1, 1) }) + { + var (a, b) = Key(wi, ii); + var found = await db.Context.FindAsync(entity, a, b); + lines.Add($"find{wi}{ii}=" + (found is null ? "null" : EfX.Get(found, "Tag"))); + db.Context.ChangeTracker.Clear(); + } + // equality query on the first key column, and on the second + foreach (var col in new[] { "A", "B" }) + { + var (a, b) = Key(0, 0); + var box = Expression.Constant(Q.NewBox(col == "A" ? ta : tb, col == "A" ? a : b)); + var p = Expression.Parameter(entity, "e"); + var pred = Expression.Equal(Expression.Property(p, col), Expression.Field(box, "Value")); + var q = Q.Call("Where", [entity], Q.SetOf(db.Context, entity), Expression.Lambda(pred, p)); + lines.Add($"where{col}=" + string.Join("|", EfX.Enumerate(q).Select(x => (string)EfX.Get(x, "Tag")!).OrderBy(x => x, StringComparer.Ordinal))); + } + db.Context.ChangeTracker.Clear(); + // a duplicate whole key is refused + var dup = EfX.New(entity); var (da, dbk) = Key(0, 1); + EfX.Set(dup, "A", da); EfX.Set(dup, "B", dbk); EfX.Set(dup, "Tag", "dup"); + db.Context.Add(dup); + try { await db.Context.SaveChangesAsync(); lines.Add("dup=accepted"); } catch (Exception ex) { lines.Add("dup=" + ex.GetType().Name); } + db.Context.ChangeTracker.Clear(); + var all = string.Join(",", EfX.Enumerate(Q.SetOf(db.Context, entity)).Select(x => (string)EfX.Get(x, "Tag")!).OrderBy(x => x, StringComparer.Ordinal)); + lines.Add("rows=" + all); + var truth = string.Join(";", lines.Where(l => l.StartsWith("find") || l.StartsWith("where") || l.StartsWith("dup") || l.StartsWith("rows"))); + return (string.Join("\n", lines), truth); + } + var v = await Side(true); + var p = await Side(false); + const string Expected = "find00=t00;find01=t01;find10=t10;find11=null;whereA=t00|t01;whereB=t00|t10;dup=DbUpdateException;rows=t00,t01,t10"; + // whereA/whereB depend on the position: A is the first key element + var expected = first ? Expected : Expected.Replace("whereA=t00|t01;whereB=t00|t10", "whereA=t00|t10;whereB=t00|t01"); + if (v.truth != expected) return $"VO side: {v.truth}; expected {expected}"; + if (v.desc != p.desc) return "model/results differ from the plain composite key: " + EfModel.FirstDiff(v.desc, p.desc); + return null; + }); + } +} diff --git a/tools/probe/corpus/probes/E16OwnConfigurationWinsProbe.cs b/tools/probe/corpus/probes/E16OwnConfigurationWinsProbe.cs new file mode 100644 index 00000000..5fba4b73 --- /dev/null +++ b/tools/probe/corpus/probes/E16OwnConfigurationWinsProbe.cs @@ -0,0 +1,169 @@ +// Claim E16-own-configuration-wins (src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys): "A configuration you write yourself always wins over these defaults." (the key defaults: generated on add for an integer, never for char and Guid) +// Obligations (claims.json), one per row by ef.config, against the wrapped twin (Baseline.Twin): never, on-add, column-type, conversion, optional. +// Oracle: differential against the same key declared with the wrapped type under the same configuration, plus the claim itself: ValueGeneratedNever / ValueGeneratedOnAdd are what the model says; HasConversion stores what the application's converter writes; +// HasColumnType is the column's type; IsRequired(false) is refused for a key as for a plain key, and on a non-key property of the same value object makes the column nullable and null round-trips. +using Microsoft.EntityFrameworkCore; +using Probes.Ef; +using Probes.Shapes; +namespace Probes; +public sealed class E16OwnConfigurationWinsProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("E16OwnConfigurationWinsProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, Check); + + static object Sample(string w, int i) => w == "char" ? (char)('a' + i) : EfX.Sample(w, i); + static string Norm(object? k) + { + if (k is not null && k.GetType().Namespace == "Probes.Ef") k = k.GetType().GetProperty("Value")!.GetValue(k); + return k switch { null => "null", Guid g => g == Guid.Empty ? "empty" : "guid", string s when Guid.TryParse(s, out _) => "guid", _ => Convert.ToString(k, System.Globalization.CultureInfo.InvariantCulture)! }; + } + + sealed record Res(string Model, string Script, string DefaultAdd, string Assigned, string Raw); + + static async Task<Res> Side(EfEntry e, bool vo, string config) + { + var w = e.Wrapped; + var kt = vo ? e.Vo : e.Plain; + var entity = typeof(EntK<>).MakeGenericType(kt); + string err = ""; + Action<ModelBuilder> cfg = mb => + { + var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); + var pb = eb.Property("Id"); + try + { + switch (config) + { + case "ValueGeneratedNever": pb.ValueGeneratedNever(); break; + case "ValueGeneratedOnAdd": pb.ValueGeneratedOnAdd(); break; + case "HasColumnType": pb.HasColumnType(EfModel.ColumnTypes[w]); break; + case "HasConversion": pb.HasConversion(OwnConvs.Setup(e, vo)); break; + case "IsRequired-false": pb.IsRequired(false); break; + } + } + catch (Exception ex) { err = "threw-in-config:" + ex.GetType().Name; } + }; + async Task<string> Safe(Func<Task<string>> f) { try { return await f(); } catch (Exception ex) { return "threw:" + ex.GetType().Name; } } + string model = "", script = ""; + try + { + await using var d0 = new EfDb2(cfg, vo ? "after-provider" : "none"); + model = EfModel.Describe(d0.Context); script = d0.Context.Database.GenerateCreateScript(); + } + catch (Exception ex) { return new Res("threw:" + ex.GetType().Name, err, err, err, err); } + if (err != "") return new Res(err, err, err, err, err); + var def = await Safe(async () => + { + await using var db = new EfDb2(cfg, vo ? "after-provider" : "none"); + await db.Context.Database.EnsureCreatedAsync(); + var rows = new[] { Activator.CreateInstance(entity)!, Activator.CreateInstance(entity)! }; + foreach (var x in rows) db.Context.Add(x); + await db.Context.SaveChangesAsync(); + return "keys=[" + string.Join(",", rows.Select(x => Norm(EfX.Get(x, "Id")))) + "]"; + }); + string raw = ""; + var assigned = await Safe(async () => + { + await using var db = new EfDb2(cfg, vo ? "after-provider" : "none"); + await db.Context.Database.EnsureCreatedAsync(); + object K(int i) { var p = Sample(w, i); return vo ? e.MakeVo(p) : p; } + foreach (var i in new[] { 0, 1 }) { var x = EfX.New(entity); EfX.Set(x, "Id", K(i)); EfX.Set(x, "Tag", "t" + i); db.Context.Add(x); } + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + raw = await EfX2.RawScalar(db.Connection, "SELECT group_concat(Id, ',') FROM (SELECT Id FROM t ORDER BY Tag)") ?? "null"; + var read = EfX.Enumerate(Q.SetOf(db.Context, entity)).Select(x => Norm(EfX.Get(x, "Id")) + "/" + EfX.Get(x, "Tag")).OrderBy(s => s, StringComparer.Ordinal); + db.Context.ChangeTracker.Clear(); + var found = await db.Context.FindAsync(entity, K(1)); + return string.Join(",", read) + " find1=" + (found is null ? "null" : (string)EfX.Get(found, "Tag")!); + }); + return new Res(model, script, def, assigned, raw); + } + + // where it throws (the table, or the second insert) is EF's and SQLite's; that it can or cannot generate is the outcome + static string Kind(string s) => s.StartsWith("threw") ? "threw" : s; + static string StripScript(string s) => s.Replace(" AUTOINCREMENT", ""); + + static async Task<string?> Check(Probes.Shapes.Row r) + { + var config = r["ef.config"]; var w = r["shape.wrapped"]; + var kind = EfX.Kinds[r.Index % EfX.Kinds.Length]; + var e = KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == w); + var v = await Side(e, true, config); + var p = await Side(e, false, config); + var obligation = config switch + { + "ValueGeneratedNever" => "never", "ValueGeneratedOnAdd" => "on-add", "HasColumnType" => "column-type", "HasConversion" => "conversion", + _ => "optional", + }; + // each configuration's obligation, against the same key declared with the wrapped type under the same configuration + return await r.Check(obligation, Baseline.Twin, async c => + { + if (config == "IsRequired-false") + { + // the key attempt: EF refuses an optional key, and must refuse the same way for the value object + if (v.Model.StartsWith("threw") != p.Model.StartsWith("threw")) return $"IsRequired(false) on a key: VO {v.Model.Split('\n')[0]} | plain {p.Model.Split('\n')[0]}"; + if (v.Model.StartsWith("threw") && v.Model != p.Model) return $"IsRequired(false) on a key is refused differently: VO {v.Model} | plain {p.Model}"; + return await OptionalProperty(r, w); + } + + if (v.Model.StartsWith("threw") || p.Model.StartsWith("threw")) + { + if (v.Model == p.Model) return c.Inconclusive("both refuse the model alike: " + v.Model); + return $"model refused for one side only: VO {v.Model} | plain {p.Model}"; + } + // the claim: the configuration is what the model says + if (config == "ValueGeneratedNever" && !v.Model.Contains("pk=True gen=Never")) return "ValueGeneratedNever not kept: " + v.Model; + if (config == "ValueGeneratedOnAdd" && !v.Model.Contains("pk=True gen=OnAdd")) return "ValueGeneratedOnAdd not kept: " + v.Model; + if (config == "HasColumnType" && !v.Model.Contains("type=" + EfModel.ColumnTypes[w])) return $"HasColumnType({EfModel.ColumnTypes[w]}) not kept: " + v.Model; + if (config == "HasConversion") + { + if (!v.Model.Contains("type=TEXT")) return "HasConversion to text: the column is not text: " + v.Model; + var expect = "C:" + OwnConv<int>.Fmt(Sample(w, 0)) + ",C:" + OwnConv<int>.Fmt(Sample(w, 1)); + if (v.Raw != expect) return $"stored values are not the application's converter output: {v.Raw}, expected {expect}"; + } + var vm = v.Model; var pm = p.Model; + if (config is "HasColumnType" or "HasConversion") { vm = vm.Replace("gen=Never", "gen=*").Replace("gen=OnAdd", "gen=*"); pm = pm.Replace("gen=Never", "gen=*").Replace("gen=OnAdd", "gen=*"); r.Note(NoteKeys.DocumentedCharGuidDefaultGenerationNormalized, "not-promised char-key-not-generated, guid-key-not-generated"); } + if (vm != pm) return "model differs from the wrapped type's: " + EfModel.FirstDiff(vm, pm); + if (StripScript(v.Script) != StripScript(p.Script)) return "create script differs from the wrapped type's: " + EfModel.FirstDiff(StripScript(v.Script), StripScript(p.Script)); + if (p.Assigned.StartsWith("threw") && !v.Assigned.StartsWith("threw")) + { + // the wrapped type's own key fails on SQLite (EF writes AUTOINCREMENT on a BIGINT / TEXT key): the value object's must still do what it was configured to + var expectRead = Norm(Sample(w, 0)) + "/t0," + Norm(Sample(w, 1)) + "/t1 find1=t1"; + var sorted = string.Join(",", expectRead.Split(" find1=")[0].Split(',').OrderBy(x => x, StringComparer.Ordinal)) + " find1=t1"; + if (v.Assigned != sorted) return $"assigned keys read back as {v.Assigned}, expected {sorted} (the plain key threw: {p.Assigned})"; + r.Note(NoteKeys.PlainKeyThrowsOnSqliteVoCheckedAgainstExpected); + } + else + { + if (v.Assigned != p.Assigned) return $"assigned keys differ: VO {v.Assigned} | plain {p.Assigned}"; + if (v.Raw != p.Raw) return $"stored keys differ: VO {v.Raw} | plain {p.Raw}"; + } + if (config is "ValueGeneratedNever" or "ValueGeneratedOnAdd" && Kind(v.DefaultAdd) != Kind(p.DefaultAdd)) return $"default keys differ under {config}: VO {v.DefaultAdd} | plain {p.DefaultAdd}"; + return null; + }); + } + + // IsRequired(false) cannot apply to a key at all; on a non-key property of the same wrapped type it must win over the required default. + static async Task<string?> OptionalProperty(Probes.Shapes.Row r, string w) + { + r.Substituted("ef.config", "IsRequired-false", "applied to a non-key property: EF refuses an optional key (checked above for parity)"); + var kind = r.Index % 2 == 0 ? "sealed-record-class" : "validated-class"; + var e = KeyRegistry.All.First(x => x.Kind == kind && x.Wrapped == w); + var entity = typeof(EntP<>).MakeGenericType(e.Vo); + Action<ModelBuilder> cfg = mb => { var eb = mb.Entity(entity); eb.ToTable("t"); eb.HasKey("Id"); eb.Property("P").IsRequired(false); }; + await using var db = new EfDb2(cfg, "after-provider"); + var model = EfModel.Describe(db.Context); + if (!model.Contains("t.P: ") || !model.Split('\n').First(l => l.StartsWith("t.P: ")).Contains("nullable=True")) return "IsRequired(false) not kept on a non-key value object property: " + model; + await db.Context.Database.EnsureCreatedAsync(); + var a = EfX.New(entity); EfX.Set(a, "P", null); + var b = EfX.New(entity); EfX.Set(b, "P", e.MakeVo(Sample(w, 0))); + db.Context.Add(a); db.Context.Add(b); + await db.Context.SaveChangesAsync(); + db.Context.ChangeTracker.Clear(); + var read = EfX.Enumerate(Q.SetOf(db.Context, entity)).Select(x => EfX.Get(x, "P") is null ? "null" : Norm(EfX.Get(x, "P"))).OrderBy(s => s, StringComparer.Ordinal).ToArray(); + var expect = new[] { "null", Norm(Sample(w, 0)) }.OrderBy(s => s, StringComparer.Ordinal).ToArray(); + if (!read.SequenceEqual(expect)) return $"null did not round-trip: read [{string.Join(",", read)}], expected [{string.Join(",", expect)}]"; + return null; + } +} diff --git a/tools/probe/corpus/probes/L05EqualityProbe.cs b/tools/probe/corpus/probes/L05EqualityProbe.cs new file mode 100644 index 00000000..e076c513 --- /dev/null +++ b/tools/probe/corpus/probes/L05EqualityProbe.cs @@ -0,0 +1,12 @@ +// Claim L05-equality (README.md#what-typekit-guarantees; Generators README "Equality" and "Comparison"; oracle "Equality (metamorphic)"): +// equal value objects hash alike, CompareTo is zero exactly when Equals is true, both agree with the wrapped type's, through every base and +// wherever the value is held. The check is Probes.Json.Equality; the shapes are harness/L05Shapes.g.cs. +using Probes.Json; +using Probes.Shapes; +namespace Probes; +public sealed class L05EqualityProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("L05EqualityProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, r => Task.FromResult(Equality.Check(r))); +} diff --git a/tools/probe/corpus/probes/L06OffTheWireProbe.cs b/tools/probe/corpus/probes/L06OffTheWireProbe.cs new file mode 100644 index 00000000..aa9542ed --- /dev/null +++ b/tools/probe/corpus/probes/L06OffTheWireProbe.cs @@ -0,0 +1,12 @@ +// Claim L06-off-the-wire (README.md#what-typekit-guarantees): System.Text.Json throws NotSupportedException for an Option or a Result, in +// both directions, in every position, unless the application registered its own converter, which takes precedence. The check is +// Probes.Json.OffTheWire. +using Probes.Json; +using Probes.Shapes; +namespace Probes; +public sealed class L06OffTheWireProbe +{ + public static TheoryData<string, int> Data() => Rows.Data("L06OffTheWireProbe"); + [Theory, MemberData(nameof(Data))] + public Task Row(string probe, int idx) => Rec.Run(probe, idx, OffTheWire.Check); +} diff --git a/tools/probe/corpus/registry.json b/tools/probe/corpus/registry.json index 26e53c3b..55240d81 100644 --- a/tools/probe/corpus/registry.json +++ b/tools/probe/corpus/registry.json @@ -1,92 +1,1340 @@ { - "probes": { - "BaseProbe": { - "claim": "C07-fault-responses-blanket", - "filter": "*BaseProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" - } - ] - }, - "C03ComposedProbe": { - "claim": "C03-composed-faults", - "filter": "*C03ComposedProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" - } - ] - }, - "C05RuleProbe": { - "claim": "C05-with-rule", - "filter": "*C05RuleProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" - } - ] - }, - "L01JsonBytesProbe": { - "claim": "L01-json-bytes", - "filter": "*L01JsonBytesProbe" - }, - "L02NoQuoteProbe": { - "claim": "L02-no-quote", - "filter": "*L02NoQuoteProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "text.culture=globalization-invariant", - "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" - } - ] - }, - "L03OneRuleProbe": { - "claim": "L03-one-rule", - "filter": "*L03OneRuleProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "text.culture=globalization-invariant", - "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" - } - ] - }, - "L04RoundTripProbe": { - "claim": "L04-round-trip", - "filter": "*L04RoundTripProbe", - "variants": [ - { - "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" - }, - { - "PROBE_ONLY": "text.culture=globalization-invariant", - "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + "probes": { + "BaseProbe": { + "claim": "C07-fault-responses-blanket", + "filter": "*BaseProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "watched": [ + "design/knowledge/KB-037-mvc-under-max-validation-depth-1-runs-an-unbound-action.md", + "design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md", + "design/knowledge/KB-041-mvc-cannot-write-its-400-under-a-context-without-validation-problem-details.md", + "test/CodoMetis.TypeKit.Facts/FactFixture.cs", + "test/CodoMetis.TypeKit.Facts/KB037.fixture.json", + "test/CodoMetis.TypeKit.Facts/KB040.fixture.json", + "test/CodoMetis.TypeKit.Facts/KB041.fixture.json" + ], + "seed": { + "patch": "seeds/C07-fault-responses-blanket.patch", + "defect": "TryParse builds a validated value object without Create, so binding a route, query, header or form value takes a value Create refuses." + } + }, + "C01DecimalSpellingProbe": { + "claim": "C01-decimal-spelling", + "filter": "*C01DecimalSpellingProbe", + "seeds": [ + { + "obligation": "smallest-scale", + "patch": "seeds/C01-decimal-spelling-smallest-scale.patch", + "defect": "Neither decimal converter narrows the scale: both write a decimal with the scale it arrived with (7.80, 7.8000)." + }, + { + "obligation": "number-is-a-number", + "patch": "seeds/C01-decimal-spelling-number-is-a-number.patch", + "defect": "DecimalNumberJsonConverter writes the smallest scale as a string, not as a number." + }, + { + "obligation": "string-is-a-string", + "patch": "seeds/C01-decimal-spelling-string-is-a-string.patch", + "defect": "DecimalStringJsonConverter writes the smallest scale as a number, not as a string." + }, + { + "obligation": "reads-a-number-or-a-string", + "patch": "seeds/C01-decimal-spelling-reads-a-number-or-a-string.patch", + "defect": "Both decimal converters refuse a string holding a number." + }, + { + "obligation": "keeps-the-scale-it-reads", + "patch": "seeds/C01-decimal-spelling-keeps-the-scale-it-reads.patch", + "defect": "Both decimal converters narrow the scale of what they read (7.80 reads as 7.8), as a number or a string." + }, + { + "obligation": "covers-every-decimal", + "patch": "seeds/C01-decimal-spelling-covers-every-decimal.patch", + "defect": "Both decimal converters write a decimal dictionary key with the scale it arrived with, so a key over a decimal, or over a value object over one, keeps its spellings." + } + ] + }, + "C02OptionalTextProbe": { + "claim": "C02-optional-text", + "filter": "*C02OptionalTextProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant,entry=AsText" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant,entry=AsOptionalText" + } + ], + "seeds": [ + { + "obligation": "hidden-is-an-error-in-both", + "patch": "seeds/C02-optional-text-hidden-is-an-error-in-both.patch", + "defect": "AsOptionalText and AsOptionalIdentifier turn a hidden-character string into no text, as a blank one, rather than refusing it as Hidden." + }, + { + "obligation": "required-blank-is-an-error", + "patch": "seeds/C02-optional-text-required-blank-is-an-error.patch", + "defect": "An empty or whitespace-only string passes the guard as empty text; only null is Blank." + }, + { + "obligation": "optional-blank-is-no-value", + "patch": "seeds/C02-optional-text-optional-blank-is-no-value.patch", + "defect": "AsOptionalText and AsOptionalIdentifier keep Blank a refusal instead of no value." + }, + { + "obligation": "null-is-blank", + "patch": "seeds/C02-optional-text-null-is-blank.patch", + "defect": "A null string is Hidden, not Blank." + } + ] + }, + "C03ComposedProbe": { + "claim": "C03-composed-faults", + "filter": "*C03ComposedProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "each-error-own-set", + "patch": "seeds/C03-each-error-own-set.patch", + "defect": "A composed fault's problem drops the problem type its own set's mapping gives it." + }, + { + "obligation": "detail-and-rule-intact", + "patch": "seeds/C03-detail-and-rule-intact.patch", + "defect": "A composed fault's problem drops the occurrence's detail." + }, + { + "obligation": "statuses-ascending", + "patch": "seeds/C03-composed-faults.patch", + "defect": "A composed fault's operation lists its statuses in descending order." + }, + { + "obligation": "shared-status-types", + "patch": "seeds/C03-shared-status-types.patch", + "defect": "A status two fault sets share names only the problem types of the first set the endpoint answers with." + }, + { + "obligation": "unmapped-set-stops-start", + "patch": "seeds/C03-unmapped-set-stops-start.patch", + "defect": "The startup check skips an unmapped fault type on an endpoint that answers with several, so the host starts." + } + ] + }, + "C04WithoutFaultsProbe": { + "claim": "C04-without-faults", + "filter": "*C04WithoutFaultsProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "doc-omits-left-out", + "patch": "seeds/C04-without-faults.patch", + "defect": "WithoutFaults leaves nothing out of the document: an operation still lists the status and problem type of a member its endpoint, group or controller leaves out." + }, + { + "obligation": "reporting-left-out-throws", + "patch": "seeds/C04-reporting-left-out-throws.patch", + "defect": "A member the endpoint leaves out is answered with its mapped status instead of throwing an InvalidOperationException." + }, + { + "obligation": "narrows-wider-status", + "patch": "seeds/C04-narrows-wider-status.patch", + "defect": "A group's or a builder's status for a member stops the host as soon as one of its endpoints leaves the member out, though the others keep it." + }, + { + "obligation": "left-out-everywhere-stops-start", + "patch": "seeds/C04-left-out-everywhere-stops-start.patch", + "defect": "A status that every endpoint of its scope leaves out no longer stops the host." + } + ] + }, + "C05RuleProbe": { + "claim": "C05-with-rule", + "filter": "*C05RuleProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "rule-qualified", + "patch": "seeds/C05-rule-qualified.patch", + "defect": "WithRule writes a closed set's rule as its member's bare name, without its type's: RuleSet.X is written X." + }, + { + "obligation": "nested-chain", + "patch": "seeds/C05-with-rule.patch", + "defect": "WithRule names an enum rule without its nesting chain: Rules.Fault.NoAt is written Fault.NoAt." + }, + { + "obligation": "stays-the-errors", + "patch": "seeds/C05-stays-the-errors.patch", + "defect": "A fault that names a rule is answered 400, not the status its mapping gives the error." + }, + { + "obligation": "unnamed-value-throws", + "patch": "seeds/C05-unnamed-value-throws.patch", + "defect": "WithRule takes an enum value no member names, a combination of flags or a cast number, and writes it as a rule." + }, + { + "obligation": "no-namespace", + "patch": "seeds/C05-no-namespace.patch", + "defect": "WithRule names an enum rule with its namespace: PRule.A is written Probes.PRule.A." + } + ] + }, + "C06FaultKindsProbe": { + "claim": "C06-fault-kinds", + "filter": "*C06FaultKindsProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "kind-gives-status", + "patch": "seeds/C06-fault-kinds.patch", + "defect": "FaultKind.Malformed is answered and documented as 422, not 400." + }, + { + "obligation": "description-as-detail", + "patch": "seeds/C06-description-as-detail.patch", + "defect": "A fault-set member's description is not written as the problem's detail." + }, + { + "obligation": "no-description-no-detail", + "patch": "seeds/C06-no-description-no-detail.patch", + "defect": "A member without a description is given its name as the problem's detail." + }, + { + "obligation": "member-must-have-kind", + "patch": "seeds/C06-member-must-have-kind.patch", + "defect": "AddFaultResponses<TSet>() maps a member of no kind to 500 instead of failing." + } + ] + }, + "C07BlanketMappingProbe": { + "claim": "C07-fault-responses-blanket", + "filter": "*C07BlanketMappingProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "by-kind", + "patch": "seeds/C07-by-kind.patch", + "defect": "AddFaultResponses() answers a member of kind Conflict with 400, not the 409 its kind gives." + }, + { + "obligation": "own-mapping", + "patch": "seeds/C07-own-mapping.patch", + "defect": "AddFaultResponses(fault => ...) keeps only the status its mapping gives and drops the problem type." + }, + { + "obligation": "own-set-keeps-mapping", + "patch": "seeds/C07-blanket-mapping.patch", + "defect": "A fault set mapped on its own loses its mapping to the mapping for every set (AddFaultResponses()), which is consulted first." + }, + { + "obligation": "enum-needs-own-mapping", + "patch": "seeds/C07-enum-needs-own-mapping.patch", + "defect": "The startup check skips an endpoint that answers with an unmapped enum, so the host starts." + }, + { + "obligation": "member-without-kind-stops-start", + "patch": "seeds/C07-member-without-kind.patch", + "defect": "AddFaultResponses() answers a fault-set member of no kind with 500 instead of stopping the start." + }, + { + "obligation": "mapping-refusal-stops-start", + "patch": "seeds/C07-mapping-refused.patch", + "defect": "AddFaultResponses(fault => ...) answers a member its mapping throws for, or gives a status that is no error, with 500 instead of stopping the start." + } + ] + }, + "C08SameNameWarningProbe": { + "claim": "C08-same-name-warning", + "filter": "*C08SameNameWarningProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "bare-names-warn", + "patch": "seeds/C08-same-name-warning.patch", + "defect": "Bare fault names are compared as qualified ones, so two fault types an endpoint answers with that write one name (HFault.Clash beside KFault.Clash) log no startup warning." + }, + { + "obligation": "qualified-chain-warns", + "patch": "seeds/C08-qualified-chain-warns.patch", + "defect": "Qualified fault names are compared with their namespace, so two sets of one nesting chain in two namespaces log no startup warning." + }, + { + "obligation": "warning-names-both", + "patch": "seeds/C08-warning-names-both.patch", + "defect": "The startup warning names only the first of the two members written alike." + }, + { + "obligation": "qualified-names-fix", + "patch": "seeds/C08-qualified-names-fix.patch", + "defect": "Under QualifiedFaultNames, fault names are still compared bare, so HFault.Clash beside KFault.Clash logs a warning." + }, + { + "obligation": "leave-one-out-fixes", + "patch": "seeds/C08-leave-one-out-fixes.patch", + "defect": "The same-name check counts a member the endpoint leaves out, so leaving one out does not end the warning." + } + ] + }, + "C09BaseRecordEqualityProbe": { + "claim": "C09-base-record-equality", + "filter": "*C09BaseRecordEqualityProbe", + "seeds": [ + { + "obligation": "in-a-collection-of-the-base", + "patch": "seeds/C09-base-record-equality-in-a-collection-of-the-base.patch", + "defect": "The value object implements only its direct base's IEquatable<TBase> again, so a collection of a deeper base compares it by that base's own equality." + }, + { + "obligation": "equal-by-the-wrapped-value", + "patch": "seeds/C09-base-record-equality-equal-by-the-wrapped-value.patch", + "defect": "Over a base record, the generated Equals(TSelf) also calls the base's Equals(TBase), so a base comparing by reference makes two value objects over one value unequal." + } + ] + }, + "C10PreserveGuardProbe": { + "claim": "C10-preserve-guard", + "filter": "*C10PreserveGuardProbe", + "watched": [ + "design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md", + "test/CodoMetis.TypeKit.Facts/FactFixture.cs", + "test/CodoMetis.TypeKit.Facts/KB040.fixture.json" + ], + "seeds": [ + { + "obligation": "refused-where-references-could-be-written", + "patch": "seeds/C10-preserve-guard-refused-where-references-could-be-written.patch", + "defect": "The Preserve guard misses a wrapped class the serializer walks as an object, so a value object over it writes and reads its own $id and $ref instead of refusing." + }, + { + "obligation": "value-converters-unaffected", + "patch": "seeds/C10-preserve-guard-value-converters-unaffected.patch", + "defect": "Under ReferenceHandler.Preserve, the guard refuses a value object over any reference type, a string or a Uri written by a value converter included." + }, + { + "obligation": "own-converters-unaffected", + "patch": "seeds/C10-preserve-guard-own-converters-unaffected.patch", + "defect": "Under ReferenceHandler.Preserve, the guard refuses a value object whose wrapped type the application converts with a converter of its own." + }, + { + "obligation": "ignore-cycles-unaffected", + "patch": "seeds/C10-preserve-guard-ignore-cycles-unaffected.patch", + "defect": "The Preserve guard fires under ReferenceHandler.IgnoreCycles too." + } + ] + }, + "C11ParseShapesBuildProbe": { + "claim": "C11-parse-shapes-build", + "filter": "*C11ParseShapesBuildProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant,process.converter-lookup=none", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant,process.converter-lookup=wrapped-type-converter-looked-up-first", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + } + ], + "seeds": [ + { + "obligation": "implicit-result", + "patch": "seeds/C11-parse-shapes-build-implicit-result.patch", + "defect": "A wrapped type whose static Parse returns a type it can't be assigned from is parsed through its type converter, so the value object has parsing." + }, + { + "obligation": "ref-text", + "patch": "seeds/C11-parse-shapes-build-ref-text.patch", + "defect": "A wrapped type whose Parse or string constructor takes its text by ref is parsed through its type converter, so the value object has parsing." + }, + { + "obligation": "abstract-string-constructor", + "patch": "seeds/C11-parse-shapes-build-abstract-string-constructor.patch", + "defect": "An abstract wrapped class with a string constructor is parsed through its type converter, so the value object has parsing." + }, + { + "obligation": "parsing-where-it-can-be-parsed", + "patch": "seeds/C11-parse-shapes-build-parsing-where-it-can-be-parsed.patch", + "defect": "The generated [TypeConverter] is attached to a top-level value object only, so a nested one over a type that parses has no converter from text for MVC binding and the configuration binder." + } + ] + }, + "C12DescribedConverterProbe": { + "claim": "C12-described-converter", + "filter": "*C12DescribedConverterProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "own-converter-describes-itself", + "patch": "seeds/C12-described-converter-own.patch", + "defect": "Only a generated converter's description is read: an application's own IDescribedJsonConverter, the decimal converters among them, leaves its type undescribed." + }, + { + "obligation": "options-describe", + "patch": "seeds/C12-options-descriptions.patch", + "defect": "A description the options give through JsonSchemaDescriptions is never found." + }, + { + "obligation": "every-position", + "patch": "seeds/C12-described-converter.patch", + "defect": "A list's items and a dictionary's additionalProperties of a described type are filled only where ASP.NET already wrote them, so the document drops them." + }, + { + "obligation": "parameter-keeps-aspnet", + "patch": "seeds/C12-parameter-keeps-aspnet.patch", + "defect": "A query parameter of a described type is described as its converter writes JSON, not as ASP.NET writes it without converters." + } + ] + }, + "C13PatchRequiredProbe": { + "claim": "C13-patch-required", + "filter": "*C13PatchRequiredProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "doc-required-where-rejected", + "patch": "seeds/C13-patch-required.patch", + "defect": "Every patch is taken out of required, also a constructor parameter that RespectRequiredConstructorParameters makes the serializer require." + }, + { + "obligation": "doc-optional-where-absent", + "patch": "seeds/C13-doc-optional-where-absent.patch", + "defect": "Every patch is listed in required, also one the serializer lets a body leave out." + }, + { + "obligation": "absent-is-default", + "patch": "seeds/C13-absent-is-default.patch", + "defect": "The default of a Patch<T>, what a field the request leaves out keeps, reads as Null instead of Absent." + } + ] + }, + "C14DeclaredProblemProbe": { + "claim": "C14-declared-problem", + "filter": "*C14DeclaredProblemProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "declared-describes-fault", + "patch": "seeds/C14-declared-describes-fault.patch", + "defect": "A problem response the endpoint declares itself gets no fault or rule on its own schema, only on the shared component." + }, + { + "obligation": "declared-names-types", + "patch": "seeds/C14-declared-problem.patch", + "defect": "A problem response the endpoint declares itself keeps ASP.NET's description, which names none of the problem types answered with its status." + }, + { + "obligation": "ascending-with-declared", + "patch": "seeds/C14-ascending-with-declared.patch", + "defect": "An operation's responses are left in the order they were added, so a status the endpoint declares itself comes before the lower fault statuses." + }, + { + "obligation": "application-json-declared", + "patch": "seeds/C14-application-json-declared.patch", + "defect": "Only an application/problem+json response counts as a declared problem, so [ProducesResponseType<ProblemDetails>(404)] under application/json names no problem types." + } + ] + }, + "C15MvcFaultStatusDocProbe": { + "claim": "C15-mvc-fault-status-doc", + "filter": "*C15MvcFaultStatusDocProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "builder-reaches-doc", + "patch": "seeds/C15-mvc-fault-status-doc.patch", + "defect": "The document reads an MVC action's metadata from its descriptor, so what MapControllers()'s builder declares (WithFaultStatus, WithoutFaults) reaches the response but not the document." + }, + { + "obligation": "without-faults-leaves-doc", + "patch": "seeds/C15-without-faults-doc.patch", + "defect": "The document lists every member, also one the endpoint, its controller or its builder leaves out." + }, + { + "obligation": "status-per-scope", + "patch": "seeds/C15-status-per-scope.patch", + "defect": "The document lists each member's mapped status, not the one its endpoint, controller or builder sets, which the response answers with." + } + ] + }, + "C16OptionsAgreementProbe": { + "claim": "C16-options-agreement", + "filter": "*C16OptionsAgreementProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ], + "seeds": [ + { + "obligation": "differing-refused", + "patch": "seeds/C16-options-agreement.patch", + "defect": "A type the minimal-API options describe and MVC's options say nothing of is not refused, so the document describes JSON the controllers do not write." + }, + { + "obligation": "error-names-type-and-descriptions", + "patch": "seeds/C16-error-names-descriptions.patch", + "defect": "The refusal names the type but not what each set of options says of it." + }, + { + "obligation": "patch-held-refused", + "patch": "seeds/C16-patch-held-refused.patch", + "defect": "A type held only inside a patch is never compared across the two option sets, so the document describes it as the minimal-API options alone say." + }, + { + "obligation": "same-converters-build", + "patch": "seeds/C16-same-converters-build.patch", + "defect": "Two option sets with the same converters are refused when their naming policy or number handling differs." + }, + { + "obligation": "minimal-host-not-compared", + "patch": "seeds/C16-minimal-host-builds.patch", + "defect": "A host of minimal APIs alone is compared against MVC's default options, so a converter on its own options refuses its document." + } + ] + }, + "C17Cmtk0014Probe": { + "claim": "C17-cmtk0014", + "filter": "*C17Cmtk0014Probe", + "seeds": [ + { + "obligation": "a-cast-naming-no-member", + "patch": "seeds/C17-cmtk0014-a-cast-naming-no-member.patch", + "defect": "CMTK0014 no longer judges a conversion, so a cast to a FaultKind or a ConstructorAccessibility that names no member is silent." + }, + { + "obligation": "default-and-zero", + "patch": "seeds/C17-cmtk0014-default-and-zero.patch", + "defect": "CMTK0014 takes a zero for a member, so default, default(FaultKind) and (FaultKind)0 are silent." + }, + { + "obligation": "constructor-accessibility-too", + "patch": "seeds/C17-cmtk0014-constructor-accessibility-too.patch", + "defect": "CMTK0014 no longer reads ConstructorAccessibility, so RequireParameterlessConstructor's argument that names no member is silent." + }, + { + "obligation": "reported-where-written", + "patch": "seeds/C17-cmtk0014-reported-where-written.patch", + "defect": "CMTK0014 is reported at the syntax around the value, not at the value." + }, + { + "obligation": "a-member-is-silent", + "patch": "seeds/C17-cmtk0014-a-member-is-silent.patch", + "defect": "CMTK0014 judges an explicit cast by its form, not by the value it names, so a cast that names a member, such as (FaultKind)2, is reported." + } + ] + }, + "C18Cmtk1023Probe": { + "claim": "C18-cmtk1023", + "filter": "*C18Cmtk1023Probe", + "watched": [ + "src/CodoMetis.TypeKit", + "src/Directory.Build.props", + "src/Directory.Build.targets" + ], + "seeds": [ + { + "obligation": "names-each-base-member", + "patch": "seeds/C18-cmtk1023-names-each-base-member.patch", + "defect": "CMTK1023 leaves out a base's explicit IEquatable<TBase>.Equals, so a base comparing by reference through it compiles without a word." + }, + { + "obligation": "a-warning-and-generated", + "patch": "seeds/C18-cmtk1023-a-warning-and-generated.patch", + "defect": "CMTK1023 is reported as information, not as a warning." + }, + { + "obligation": "generated-equality-never-calls-the-base", + "patch": "seeds/C18-cmtk1023-generated-equality-never-calls-the-base.patch", + "defect": "Over a base record, the generated GetHashCode() combines the base's, so a base hashing by reference makes two value objects over one value hash apart." + } + ] + }, + "C19Cmtk0010KindsProbe": { + "claim": "C19-cmtk0010-kinds", + "filter": "*C19Cmtk0010KindsProbe", + "watched": [ + "test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentKinds.cs", + "test/CodoMetis.TypeKit.Analyzers.Tests/Variants/TypeArgumentSites.cs" + ], + "seeds": [ + { + "obligation": "methods-the-compiler-calls", + "patch": "seeds/C19-cmtk0010-kinds-methods-the-compiler-calls.patch", + "defect": "CMTK0010 no longer checks a collection expression's builder method, so a breaking argument it infers there compiles." + }, + { + "obligation": "interface-and-unsealed", + "patch": "seeds/C19-cmtk0010-kinds-interface-and-unsealed.patch", + "defect": "CMTK0010 takes a sealed class for [Unsealed]." + }, + { + "obligation": "value-object-and-closed-set", + "patch": "seeds/C19-cmtk0010-kinds-value-object-and-closed-set.patch", + "defect": "CMTK0010 takes any type that is not an interface for [ValueObject], value object or not." + }, + { + "obligation": "flags-enum-and-read-only-struct", + "patch": "seeds/C19-cmtk0010-kinds-flags-enum-and-read-only-struct.patch", + "defect": "CMTK0010 takes a mutable struct for [ReadOnlyStruct]." + }, + { + "obligation": "a-pair-once-at-the-later-argument", + "patch": "seeds/C19-cmtk0010-kinds-a-pair-once-at-the-later-argument.patch", + "defect": "CMTK0010 reports a breach of [Distinct] or [Unrelated] at the earlier argument of the pair." + } + ] + }, + "C20ShouldHoldProbe": { + "claim": "C20-testing-shouldhold", + "filter": "*C20ShouldHoldProbe", + "seeds": [ + { + "obligation": "fails-unless-it-holds-that-reason", + "patch": "seeds/C20-testing-shouldhold-fails-unless-it-holds-that-reason.patch", + "defect": "ShouldHold(reason) checks only the held fault's type, so a fault of that type with another reason passes." + }, + { + "obligation": "whichever-of-its-types", + "patch": "seeds/C20-testing-shouldhold-whichever-of-its-types.patch", + "defect": "ShouldHold(reason) never finds a reason of a composed fault's last type: it reads that case as the bare reason, not as a Fault<TFault>." + }, + { + "obligation": "hands-back-detail-and-rule", + "patch": "seeds/C20-testing-shouldhold-hands-back-detail-and-rule.patch", + "defect": "ShouldHold(reason) hands back a new Fault<TFault> of the reason alone, without the held fault's detail and rule." + }, + { + "obligation": "uninitialized-fails", + "patch": "seeds/C20-testing-shouldhold-uninitialized-fails.patch", + "defect": "ShouldHold(reason) reads an uninitialized composed fault instead of failing as an assertion." + }, + { + "obligation": "a-failure-says-the-outcome", + "patch": "seeds/C20-testing-shouldhold-a-failure-says-the-outcome.patch", + "defect": "A failed ShouldHold(reason) no longer says which fault the composed fault holds." + } + ] + }, + "C21OpenapiSchemaParityProbe": { + "claim": "C21-openapi-schema-parity", + "filter": "*C21OpenapiSchemaParityProbe", + "seeds": [ + { + "obligation": "the-underlying-types-schema", + "patch": "seeds/C21-openapi-schema-parity-the-underlying-types-schema.patch", + "defect": "A value object's OpenAPI schema drops the wrapped type's format (int32, uuid, date-time)." + }, + { + "obligation": "container-elements", + "patch": "seeds/C21-openapi-schema-parity-container-elements.patch", + "defect": "A list's or an array's items are not put back for a container of value objects, so the elements lose their schema." + }, + { + "obligation": "dictionary-key", + "patch": "seeds/C21-openapi-schema-parity-dictionary-key.patch", + "defect": "A dictionary keyed by a value object is described with the key's schema for its values." + }, + { + "obligation": "every-document-version", + "patch": "seeds/C21-openapi-schema-parity-every-document-version.patch", + "defect": "Under OpenAPI 2.0 and 3.0 a value object's schema drops the wrapped type's format." + }, + { + "obligation": "nullable-admits-null-as-the-wrapped-does", + "patch": "seeds/C21-openapi-schema-parity-nullable-admits-null-as-the-wrapped-does.patch", + "defect": "6866822 and F3's fixes reversed: every nullable value object in a JSON position takes a nullable property's null (a query, header or form VO? admits null in its type, a nullable struct over string or Uri at a root admits null), and a parameter MVC hands over as a string admits none (an MVC [FromBody] VO? over ulong or long, a query VO? over an enum written by name)." + } + ], + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first" + } + ] + }, + "C22GeneratorRefusalsProbe": { + "claim": "C22-generator-refusals", + "filter": "*C22GeneratorRefusalsProbe", + "watched": [ + "src/CodoMetis.TypeKit", + "src/CodoMetis.TypeKit.Analyzers", + "src/CodoMetis.TypeKit.Generators", + "src/Directory.Build.props", + "src/Directory.Build.targets" + ], + "seeds": [ + { + "obligation": "every-error-links-the-table", + "patch": "seeds/C22-generator-refusals-every-error-links-the-table.patch", + "defect": "CMTK1005's message no longer ends with the link to the Build errors table." + }, + { + "obligation": "unsealed-record-class", + "patch": "seeds/C22-generator-refusals-unsealed-record-class.patch", + "defect": "A record class not declared sealed is no longer refused (CMTK1006 is never reported)." + }, + { + "obligation": "over-value-object", + "patch": "seeds/C22-generator-refusals-over-value-object.patch", + "defect": "The fabric hands the aspect no wrapped-value-object refusal, so a value object over another value object is generated." + }, + { + "obligation": "generic-wrapped", + "patch": "seeds/C22-generator-refusals-generic-wrapped.patch", + "defect": "A generic wrapped type is no longer refused, so List<T> fails inside the generated code (LAMA0611) instead of CMTK1005." + }, + { + "obligation": "abstract", + "patch": "seeds/C22-generator-refusals-abstract.patch", + "defect": "1ef30be reversed: the fabric no longer reports CMTK1022, so an abstract type with a marker compiles with nothing generated and nothing said." + }, + { + "obligation": "over-system-enum", + "patch": "seeds/C22-generator-refusals-over-system-enum.patch", + "defect": "e4b751c's System.Enum refusal reversed: a value object over System.Enum is no longer refused (CMTK1005)." + }, + { + "obligation": "named-as-generated-member", + "patch": "seeds/C22-generator-refusals-named-as-generated-member.patch", + "defect": "e4b751c's name refusal reversed: a value object named From, Parse, MinValue or another generated member is not refused, and fails inside the generated code (LAMA0611, CS0542)." + }, + { + "obligation": "closed-set-named-as-generated-member", + "patch": "seeds/C22-generator-refusals-closed-set-named-as-generated-member.patch", + "defect": "e4b751c's set-name refusal reversed: a closed set named Name, All, Match or another of its generated members is not refused (CMTK1021) and fails inside the generated code." + }, + { + "obligation": "equality-operator", + "patch": "seeds/C22-generator-refusals-equality-operator.patch", + "defect": "e4b751c's operator refusal reversed: a hand-written operator == or != between two of the type is C#'s bare CS0111, with no CMTK1011 naming it." + }, + { + "obligation": "no-type-half-generated", + "patch": "seeds/C22-generator-refusals-no-type-half-generated.patch", + "defect": "CMTK1005 is reported as a warning, so a declaration that cannot be generated builds, with nothing generated for it." + }, + { + "obligation": "over-object", + "patch": "seeds/C22-generator-refusals-over-object.patch", + "defect": "53188ed reversed: a value object over object is no longer refused (CMTK1005) and compiles." + } + ] + }, + "E01ColumnParityProbe": { + "claim": "E01-column-parity", + "filter": "*E01ColumnParityProbe", + "seeds": [ + { + "obligation": "column-type", + "patch": "seeds/E01-column-parity-column-type.patch", + "defect": "A value object over an enum is mapped as a text column, as a closed set is, not as the enum's integer column." + }, + { + "obligation": "nullable", + "patch": "seeds/E01-column-parity-nullable.patch", + "defect": "A convention marks every value-object property required, so a nullable one gets a NOT NULL column." + }, + { + "obligation": "facet-kept", + "patch": "seeds/E01-column-parity-facet-kept.patch", + "defect": "A finalizing convention clears the maximum length, precision, scale and column type configured on a value-object property." + } + ] + }, + "E02ValueTranslatesProbe": { + "claim": "E02-value-translates-to-column", + "filter": "*E02ValueTranslatesProbe", + "seeds": [ + { + "obligation": "value", + "patch": "seeds/E02-value-translates-to-column-value.patch", + "defect": "The member translator leaves .Value to EF, so a filter, sort or grouping on it is not translated." + }, + { + "obligation": "get-value", + "patch": "seeds/E02-value-translates-to-column-get-value.patch", + "defect": "The method-call translator leaves GetValue() to EF, so a query on it is not translated." + }, + { + "obligation": "value-or-null", + "patch": "seeds/E02-value-translates-to-column-value-or-null.patch", + "defect": "The method-call translator leaves ValueOrNull() to EF, so a query on an optional value object's value is not translated." + }, + { + "obligation": "column-not-cast", + "patch": "seeds/E02-value-translates-to-column-column-not-cast.patch", + "defect": ".Value on a column is translated as a cast of the column, not the column itself." + } + ] + }, + "E03ColumnRoundTripProbe": { + "claim": "E03-column-round-trip", + "filter": "*E03ColumnRoundTripProbe", + "seeds": [ + { + "obligation": "stored-as-underlying", + "patch": "seeds/E03-column-round-trip-stored-as-underlying.patch", + "defect": "A decimal value object is mapped through the double mapping, a REAL column, not the TEXT column a decimal gets." + }, + { + "obligation": "keys", + "patch": "seeds/E03-column-round-trip-keys.patch", + "defect": "The type mapping plugin maps no value object used as a key or an index, so a value-object key cannot be stored." + }, + { + "obligation": "nullable-properties", + "patch": "seeds/E03-column-round-trip-nullable-properties.patch", + "defect": "A convention marks every value-object property required, so a nullable one cannot store null." + } + ] + }, + "E04IntegerKeyProbe": { + "claim": "E04-integer-key-generated", + "filter": "*E04IntegerKeyProbe", + "seeds": [ + { + "obligation": "generated-on-add", + "patch": "seeds/E04-integer-key-generated-generated-on-add.patch", + "defect": "A key over an integer is declared never generated, so entities added without a key are stored with 0." + }, + { + "obligation": "byte-not-generated", + "patch": "seeds/E04-integer-key-generated-byte-not-generated.patch", + "defect": "A key over byte is declared generated on add, which EF does not do for a plain byte key." + }, + { + "obligation": "char-key-yours", + "patch": "seeds/E04-integer-key-generated-char-key-yours.patch", + "defect": "A key over char is declared generated on add, as EF would for a plain char key." + }, + { + "obligation": "guid-key-yours", + "patch": "seeds/E04-integer-key-generated-guid-key-yours.patch", + "defect": "A key over a Guid is declared generated on add, so EF invents a Guid for an entity added without one." + }, + { + "obligation": "not-on-efs-list", + "patch": "seeds/E04-integer-key-generated-not-on-efs-list.patch", + "defect": "A key over a string is declared generated on add, as a key over an integer is, so EF invents a Guid's text for an entity added without one, where a plain string key is never generated." + }, + { + "obligation": "own-configuration-wins", + "patch": "seeds/E04-integer-key-generated-own-configuration-wins.patch", + "defect": "A key over char or Guid is declared never generated as an explicit configuration, so it overrides the ValueGeneratedOnAdd() the application wrote." + } + ] + }, + "E05OwnCompareToProbe": { + "claim": "E05-own-compareto-rejected", + "filter": "*E05OwnCompareToProbe", + "seeds": [ + { + "obligation": "comparison", + "patch": "seeds/E05-own-compareto-rejected-comparison.patch", + "defect": "A query comparing a value object with a hand-written CompareTo (an operator or CompareTo) runs in the database instead of throwing." + }, + { + "obligation": "ordering", + "patch": "seeds/E05-own-compareto-rejected-ordering.patch", + "defect": "A query ordering by a value object with a hand-written CompareTo runs in the database instead of throwing." + }, + { + "obligation": "min-max", + "patch": "seeds/E05-own-compareto-rejected-min-max.patch", + "defect": "Min or Max of a value object with a hand-written CompareTo runs in the database instead of throwing." + }, + { + "obligation": "equality-allowed", + "patch": "seeds/E05-own-compareto-rejected-equality-allowed.patch", + "defect": "An equality of a value object with a hand-written CompareTo is refused as if it were a comparison." + }, + { + "obligation": "value-order-allowed", + "patch": "seeds/E05-own-compareto-rejected-value-order-allowed.patch", + "defect": "Ordering by .Value of a value object with a hand-written CompareTo is refused as if it ordered by the value object." + }, + { + "obligation": "query-filter", + "patch": "seeds/E05-own-compareto-rejected-query-filter.patch", + "defect": "A query filter comparing a value object with a hand-written CompareTo is accepted when the model is built." + } + ] + }, + "E06OwnConverterNoValueProbe": { + "claim": "E06-own-converter-no-value", + "filter": "*E06OwnConverterNoValueProbe", + "seeds": [ + { + "obligation": "value-not-translated", + "patch": "seeds/E06-own-converter-no-value-value-not-translated.patch", + "defect": ".Value translates on a column the application converts itself, so the query compares against the converted text instead of failing." + }, + { + "obligation": "value-object-comparison", + "patch": "seeds/E06-own-converter-no-value-value-object-comparison.patch", + "defect": "The query interceptor rewrites a value-object equality as an equality of .Value, which a column the application converts itself cannot answer." + } + ] + }, + "E07ClosedSetNameProbe": { + "claim": "E07-closed-set-stored-as-name", + "filter": "*E07ClosedSetNameProbe", + "seeds": [ + { + "obligation": "stored-as-name", + "patch": "seeds/E07-closed-set-stored-as-name-stored-as-name.patch", + "defect": "A closed set's member is stored as its name in upper case, not its name." + }, + { + "obligation": "in-collections-and-json", + "patch": "seeds/E07-closed-set-stored-as-name-in-collections-and-json.patch", + "defect": "The JSON reader/writer of a closed set writes something other than the member's name, so a primitive collection or a JSON column holds no names." + }, + { + "obligation": "unknown-name-throws", + "patch": "seeds/E07-closed-set-stored-as-name-unknown-name-throws.patch", + "defect": "A stored name the closed set no longer has reads back as null instead of throwing." + }, + { + "obligation": "non-member-throws", + "patch": "seeds/E07-closed-set-stored-as-name-non-member-throws.patch", + "defect": "An instance that is not a member of its closed set is written as the name Unknown instead of throwing." + } + ] + }, + "E08OptionResultNotColumnsProbe": { + "claim": "E08-option-result-not-columns", + "filter": "*E08OptionResultNotColumnsProbe", + "seeds": [ + { + "obligation": "model-fails", + "patch": "seeds/E08-option-result-not-columns-model-fails.patch", + "defect": "TypeKit's conventions ignore an Option or Result property, so the model builds without the column instead of failing." + }, + { + "obligation": "error-says-so", + "patch": "seeds/E08-option-result-not-columns-error-says-so.patch", + "defect": "The type mapping plugin refuses an Option or Result property with an error that does not name the type." + } + ] + }, + "E09OrNoneInDbProbe": { + "claim": "E09-or-none-in-database", + "filter": "*E09OrNoneInDbProbe", + "seeds": [ + { + "obligation": "first-reads-one-row", + "patch": "seeds/E09-or-none-in-database-first-reads-one-row.patch", + "defect": "FirstOrNoneAsync with a condition reads every matching row, without LIMIT 1." + }, + { + "obligation": "single-asks-for-two", + "patch": "seeds/E09-or-none-in-database-single-asks-for-two.patch", + "defect": "SingleOrNoneAsync answers the first of two matching rows instead of throwing." + }, + { + "obligation": "element-at-skips", + "patch": "seeds/E09-or-none-in-database-element-at-skips.patch", + "defect": "ElementAtOrNone on a query reads every row up to its index and skips in memory, without OFFSET." + } + ] + }, + "E10ReadBackSkipsValidationProbe": { + "claim": "E10-read-back-skips-validation", + "filter": "*E10ReadBackSkipsValidationProbe", + "seeds": [ + { + "obligation": "reads-without-create", + "patch": "seeds/E10-read-back-skips-validation-reads-without-create.patch", + "defect": "Reading a column back runs Create and throws for a value today's rules refuse, instead of materializing it." + }, + { + "obligation": "revalidate-finds", + "patch": "seeds/E10-read-back-skips-validation-revalidate-finds.patch", + "defect": "The generated Revalidate() answers the value object itself, so a stored value today's rules refuse reads as valid." + } + ] + }, + "E11UseTypeKitOrderProbe": { + "claim": "E11-usetypekit-order", + "filter": "*E11UseTypeKitOrderProbe", + "seeds": [ + { + "obligation": "order-does-not-matter", + "patch": "seeds/E11-usetypekit-order-order-does-not-matter.patch", + "defect": "UseTypeKit() adds its services only when the provider's are already registered, so calling it before UseSqlite does nothing." + } + ] + }, + "E12ConventionByTypeProbe": { + "claim": "E12-convention-by-vo-type", + "filter": "*E12ConventionByTypeProbe", + "seeds": [ + { + "obligation": "value-object-type-reaches", + "patch": "seeds/E12-convention-by-vo-type-value-object-type-reaches.patch", + "defect": "A finalizing convention clears the max length, precision and scale of value-object properties, so a convention on the value object's type does not reach its property." + }, + { + "obligation": "underlying-type-does-not-reach", + "patch": "seeds/E12-convention-by-vo-type-underlying-type-does-not-reach.patch", + "defect": "A value-object property is configured as the type it wraps is, so a ConfigureConventions convention on decimal reaches an Amount property." + } + ] + }, + "E13PrimitiveCollectionProbe": { + "claim": "E13-primitive-collections", + "filter": "*E13PrimitiveCollectionProbe", + "seeds": [ + { + "obligation": "element-value", + "patch": "seeds/E13-primitive-collections-element-value.patch", + "defect": ".Value on an element of a primitive collection of value objects is not translated, so Any(t => t.Value == x) fails." + }, + { + "obligation": "contains", + "patch": "seeds/E13-primitive-collections-contains.patch", + "defect": "The query interceptor rewrites Contains of a value object in a primitive collection as false, so the query matches no row." + } + ] + }, + "E14InheritanceKeyProbe": { + "claim": "E14-inheritance-key-generation", + "filter": "*E14InheritanceKeyProbe", + "seeds": [ + { + "obligation": "tph-tpt-root", + "patch": "seeds/E14-inheritance-key-generation-tph-tpt-root.patch", + "defect": "The integer key of a TPH or TPT hierarchy's root is left as EF leaves a value object, never generated." + }, + { + "obligation": "tpc-assign-on-sqlite", + "patch": "seeds/E14-inheritance-key-generation-tpc-assign-on-sqlite.patch", + "defect": "Under TPC on a provider without a sequence (SQLite), an integer key of the hierarchy's root stays generated on add." + } + ] + }, + "E15CompositeKeyProbe": { + "claim": "E15-composite-key", + "filter": "*E15CompositeKeyProbe", + "seeds": [ + { + "obligation": "as-for-primitives", + "patch": "seeds/E15-composite-key-as-for-primitives.patch", + "defect": "The first value-object property of a composite key is treated as a single integer key and generated on add." + } + ] + }, + "E16OwnConfigurationWinsProbe": { + "claim": "E16-own-configuration-wins", + "filter": "*E16OwnConfigurationWinsProbe", + "seeds": [ + { + "obligation": "never", + "patch": "seeds/E16-own-configuration-wins-never.patch", + "defect": "The integer key default is set as an explicit configuration, so it overrides the application's own ValueGeneratedNever." + }, + { + "obligation": "on-add", + "patch": "seeds/E16-own-configuration-wins-on-add.patch", + "defect": "A key over char or Guid is declared never generated as an explicit configuration, so it overrides the application's own ValueGeneratedOnAdd." + }, + { + "obligation": "column-type", + "patch": "seeds/E16-own-configuration-wins-column-type.patch", + "defect": "A finalizing convention clears the column type configured on a value-object property." + }, + { + "obligation": "conversion", + "patch": "seeds/E16-own-configuration-wins-conversion.patch", + "defect": "A finalizing convention removes the converter the application configured on a value-object property, so TypeKit's mapping stores the plain value." + }, + { + "obligation": "optional", + "patch": "seeds/E16-own-configuration-wins-optional.patch", + "defect": "A finalizing convention makes a value-object property required again after the application configured IsRequired(false)." + } + ] + }, + "L01JsonBytesProbe": { + "claim": "L01-json-bytes", + "filter": "*L01JsonBytesProbe", + "watched": [ + "design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md", + "test/CodoMetis.TypeKit.Facts/FactFixture.cs", + "test/CodoMetis.TypeKit.Facts/KB040.fixture.json" + ], + "seeds": [ + { + "obligation": "as-a-dictionary-key-too", + "patch": "seeds/L01-json-bytes-as-a-dictionary-key-too.patch", + "defect": "A dictionary key is written with ToString(), not the wrapped type's key converter and DictionaryKeyPolicy." + }, + { + "obligation": "the-underlying-types-json", + "patch": "seeds/L01-json-bytes-the-underlying-types-json.patch", + "defect": "The generated converter writes a number as it is, ignoring the options' JsonNumberHandling.WriteAsString." + }, + { + "obligation": "read-as-the-underlying-type-reads", + "patch": "seeds/L01-json-bytes-read-as-the-underlying-type-reads.patch", + "defect": "The generated converter refuses a JSON string that holds an escape sequence, which the wrapped type reads." + } + ] + }, + "L02NoQuoteProbe": { + "claim": "L02-no-quote", + "filter": "*L02NoQuoteProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + } + ], + "seeds": [ + { + "obligation": "parsing-never-repeats", + "patch": "seeds/L02-no-quote-parsing-never-repeats.patch", + "defect": "Generated parsing calls the wrapped type's Parse instead of its TryParse, whose FormatException quotes the input." + }, + { + "obligation": "json-never-repeats", + "patch": "seeds/L02-no-quote-json-never-repeats.patch", + "defect": "A JSON value the wrapped type can't read is refused with its text in the JsonException's message." + }, + { + "obligation": "no-inner-that-quotes", + "patch": "seeds/L02-no-quote-no-inner-that-quotes.patch", + "defect": "Generated parsing refuses unreadable text with an inner FormatException that quotes it." + } + ] + }, + "L03OneRuleProbe": { + "claim": "L03-one-rule", + "filter": "*L03OneRuleProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + } + ], + "watched": [ + "design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md", + "test/CodoMetis.TypeKit.Facts/FactFixture.cs", + "test/CodoMetis.TypeKit.Facts/KB040.fixture.json" + ], + "seeds": [ + { + "obligation": "parsing-runs-create", + "patch": "seeds/L03-one-rule-parsing-runs-create.patch", + "defect": "Parse and the type converter build a validated value object without Create." + }, + { + "obligation": "tryfrom-runs-create", + "patch": "seeds/L03-one-rule-tryfrom-runs-create.patch", + "defect": "TryFrom wraps the value without Create, so it gives a value Create refuses." + }, + { + "obligation": "json-runs-create", + "patch": "seeds/L03-one-rule-json-runs-create.patch", + "defect": "The JSON converter's materialize flag is read backwards: a JSON read skips Create, and only StoredJsonConverterFactory runs it." + }, + { + "obligation": "refusals-name-the-fault", + "patch": "seeds/L03-one-rule-refusals-name-the-fault.patch", + "defect": "The JsonException, the FormatException and the FromKnownGood exception name the type of the fault Create returned, not the fault." + } + ] + }, + "L04RoundTripProbe": { + "claim": "L04-round-trip", + "filter": "*L04RoundTripProbe", + "variants": [ + { + "PROBE_ONLY": "process.converter-lookup=none,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "process.converter-lookup=wrapped-type-converter-looked-up-first,text.culture!=globalization-invariant" + }, + { + "PROBE_ONLY": "text.culture=globalization-invariant", + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": "1" + } + ], + "watched": [ + "design/knowledge/KB-040-a-converter-is-charged-a-level-of-max-depth.md", + "test/CodoMetis.TypeKit.Facts/FactFixture.cs", + "test/CodoMetis.TypeKit.Facts/KB040.fixture.json" + ], + "seeds": [ + { + "obligation": "invariant-whatever-the-culture", + "patch": "seeds/L04-round-trip-invariant-whatever-the-culture.patch", + "defect": "Generated formatting uses the current culture instead of the provider (invariant by default)." + }, + { + "obligation": "the-type-converter-writes-the-same", + "patch": "seeds/L04-round-trip-the-type-converter-writes-the-same.patch", + "defect": "The type converter writes the wrapped value in the current culture when no culture is passed." + }, + { + "obligation": "text-reads-back", + "patch": "seeds/L04-round-trip-text-reads-back.patch", + "defect": "The type converter reads text in the current culture when no culture is passed." + }, + { + "obligation": "json-refusal-is-a-json-exception", + "patch": "seeds/L04-round-trip-json-refusal-is-a-json-exception.patch", + "defect": "The generated converter lets the wrapped converter's own exception out for input it can't read (a FormatException for a Guid)." + } + ] + }, + "L05EqualityProbe": { + "claim": "L05-equality", + "filter": "*L05EqualityProbe", + "seeds": [ + { + "obligation": "through-every-base", + "patch": "seeds/L05-equality-through-every-base.patch", + "defect": "A deeper base record whose IEquatable is implemented explicitly keeps its own Equals, so equal value objects compare unequal through that base and a HashSet of it holds both." + }, + { + "obligation": "equality-is-the-underlying-values", + "patch": "seeds/L05-equality-equality-is-the-underlying-values.patch", + "defect": "Over a base record, the generated GetHashCode() hashes the value's text, so 7.8m and 7.80m, or 0.0 and -0.0, hash apart." + }, + { + "obligation": "ordering-follows-the-underlying-type", + "patch": "seeds/L05-equality-ordering-follows-the-underlying-type.patch", + "defect": "The generated CompareTo orders the wrapped values in reverse." + } + ] + }, + "L06OffTheWireProbe": { + "claim": "L06-off-the-wire", + "filter": "*L06OffTheWireProbe", + "seeds": [ + { + "obligation": "never-written", + "patch": "seeds/L06-off-the-wire-never-written.patch", + "defect": "The refusing converter writes {} for an Option or a Result instead of throwing NotSupportedException, so a Some is written as {} and reads back as None." + }, + { + "obligation": "never-read", + "patch": "seeds/L06-off-the-wire-never-read.patch", + "defect": "Reading an Option or a Result throws a JsonException, as for malformed input, not NotSupportedException." + } + ] } - ] } - } -} \ No newline at end of file +} diff --git a/tools/probe/corpus/rows/BaseProbe.json.gz b/tools/probe/corpus/rows/BaseProbe.json.gz new file mode 100644 index 00000000..078ef2fe Binary files /dev/null and b/tools/probe/corpus/rows/BaseProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C01DecimalSpellingProbe.json.gz b/tools/probe/corpus/rows/C01DecimalSpellingProbe.json.gz new file mode 100644 index 00000000..59d80c38 Binary files /dev/null and b/tools/probe/corpus/rows/C01DecimalSpellingProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C02OptionalTextProbe.json.gz b/tools/probe/corpus/rows/C02OptionalTextProbe.json.gz new file mode 100644 index 00000000..51817f86 Binary files /dev/null and b/tools/probe/corpus/rows/C02OptionalTextProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C03-composed-faults.json.gz b/tools/probe/corpus/rows/C03-composed-faults.json.gz deleted file mode 100644 index 1ea1f2c5..00000000 Binary files a/tools/probe/corpus/rows/C03-composed-faults.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/C03ComposedProbe.json.gz b/tools/probe/corpus/rows/C03ComposedProbe.json.gz new file mode 100644 index 00000000..f2a7899e Binary files /dev/null and b/tools/probe/corpus/rows/C03ComposedProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C04WithoutFaultsProbe.json.gz b/tools/probe/corpus/rows/C04WithoutFaultsProbe.json.gz new file mode 100644 index 00000000..d5fa3f24 Binary files /dev/null and b/tools/probe/corpus/rows/C04WithoutFaultsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C05-with-rule.json.gz b/tools/probe/corpus/rows/C05-with-rule.json.gz deleted file mode 100644 index ec7fc19b..00000000 Binary files a/tools/probe/corpus/rows/C05-with-rule.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/C05RuleProbe.json.gz b/tools/probe/corpus/rows/C05RuleProbe.json.gz new file mode 100644 index 00000000..7ad0dc77 Binary files /dev/null and b/tools/probe/corpus/rows/C05RuleProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C06FaultKindsProbe.json.gz b/tools/probe/corpus/rows/C06FaultKindsProbe.json.gz new file mode 100644 index 00000000..562ae319 Binary files /dev/null and b/tools/probe/corpus/rows/C06FaultKindsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C07-fault-responses-blanket.json.gz b/tools/probe/corpus/rows/C07-fault-responses-blanket.json.gz deleted file mode 100644 index 9d56a7c2..00000000 Binary files a/tools/probe/corpus/rows/C07-fault-responses-blanket.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/C07BlanketMappingProbe.json.gz b/tools/probe/corpus/rows/C07BlanketMappingProbe.json.gz new file mode 100644 index 00000000..078ef2fe Binary files /dev/null and b/tools/probe/corpus/rows/C07BlanketMappingProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C08SameNameWarningProbe.json.gz b/tools/probe/corpus/rows/C08SameNameWarningProbe.json.gz new file mode 100644 index 00000000..316ea2be Binary files /dev/null and b/tools/probe/corpus/rows/C08SameNameWarningProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C09BaseRecordEqualityProbe.json.gz b/tools/probe/corpus/rows/C09BaseRecordEqualityProbe.json.gz new file mode 100644 index 00000000..61cf4ba6 Binary files /dev/null and b/tools/probe/corpus/rows/C09BaseRecordEqualityProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C10PreserveGuardProbe.json.gz b/tools/probe/corpus/rows/C10PreserveGuardProbe.json.gz new file mode 100644 index 00000000..c1206100 Binary files /dev/null and b/tools/probe/corpus/rows/C10PreserveGuardProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C11ParseShapesBuildProbe.json.gz b/tools/probe/corpus/rows/C11ParseShapesBuildProbe.json.gz new file mode 100644 index 00000000..ffe8f639 Binary files /dev/null and b/tools/probe/corpus/rows/C11ParseShapesBuildProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C12DescribedConverterProbe.json.gz b/tools/probe/corpus/rows/C12DescribedConverterProbe.json.gz new file mode 100644 index 00000000..694df302 Binary files /dev/null and b/tools/probe/corpus/rows/C12DescribedConverterProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C13PatchRequiredProbe.json.gz b/tools/probe/corpus/rows/C13PatchRequiredProbe.json.gz new file mode 100644 index 00000000..28c4797f Binary files /dev/null and b/tools/probe/corpus/rows/C13PatchRequiredProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C14DeclaredProblemProbe.json.gz b/tools/probe/corpus/rows/C14DeclaredProblemProbe.json.gz new file mode 100644 index 00000000..c3da4a42 Binary files /dev/null and b/tools/probe/corpus/rows/C14DeclaredProblemProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C15MvcFaultStatusDocProbe.json.gz b/tools/probe/corpus/rows/C15MvcFaultStatusDocProbe.json.gz new file mode 100644 index 00000000..1c4b21d1 Binary files /dev/null and b/tools/probe/corpus/rows/C15MvcFaultStatusDocProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C16OptionsAgreementProbe.json.gz b/tools/probe/corpus/rows/C16OptionsAgreementProbe.json.gz new file mode 100644 index 00000000..762426f8 Binary files /dev/null and b/tools/probe/corpus/rows/C16OptionsAgreementProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C17Cmtk0014Probe.json.gz b/tools/probe/corpus/rows/C17Cmtk0014Probe.json.gz new file mode 100644 index 00000000..c373c112 Binary files /dev/null and b/tools/probe/corpus/rows/C17Cmtk0014Probe.json.gz differ diff --git a/tools/probe/corpus/rows/C18Cmtk1023Probe.json.gz b/tools/probe/corpus/rows/C18Cmtk1023Probe.json.gz new file mode 100644 index 00000000..0daaefd6 Binary files /dev/null and b/tools/probe/corpus/rows/C18Cmtk1023Probe.json.gz differ diff --git a/tools/probe/corpus/rows/C19Cmtk0010KindsProbe.json.gz b/tools/probe/corpus/rows/C19Cmtk0010KindsProbe.json.gz new file mode 100644 index 00000000..f0242cec Binary files /dev/null and b/tools/probe/corpus/rows/C19Cmtk0010KindsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C20ShouldHoldProbe.json.gz b/tools/probe/corpus/rows/C20ShouldHoldProbe.json.gz new file mode 100644 index 00000000..6f5be161 Binary files /dev/null and b/tools/probe/corpus/rows/C20ShouldHoldProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C21OpenapiSchemaParityProbe.json.gz b/tools/probe/corpus/rows/C21OpenapiSchemaParityProbe.json.gz new file mode 100644 index 00000000..bdcf9ef2 Binary files /dev/null and b/tools/probe/corpus/rows/C21OpenapiSchemaParityProbe.json.gz differ diff --git a/tools/probe/corpus/rows/C22GeneratorRefusalsProbe.json.gz b/tools/probe/corpus/rows/C22GeneratorRefusalsProbe.json.gz new file mode 100644 index 00000000..a435a310 Binary files /dev/null and b/tools/probe/corpus/rows/C22GeneratorRefusalsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E01ColumnParityProbe.json.gz b/tools/probe/corpus/rows/E01ColumnParityProbe.json.gz new file mode 100644 index 00000000..e47d8738 Binary files /dev/null and b/tools/probe/corpus/rows/E01ColumnParityProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E02ValueTranslatesProbe.json.gz b/tools/probe/corpus/rows/E02ValueTranslatesProbe.json.gz new file mode 100644 index 00000000..b420251c Binary files /dev/null and b/tools/probe/corpus/rows/E02ValueTranslatesProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E03ColumnRoundTripProbe.json.gz b/tools/probe/corpus/rows/E03ColumnRoundTripProbe.json.gz new file mode 100644 index 00000000..7b341810 Binary files /dev/null and b/tools/probe/corpus/rows/E03ColumnRoundTripProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E04IntegerKeyProbe.json.gz b/tools/probe/corpus/rows/E04IntegerKeyProbe.json.gz new file mode 100644 index 00000000..d8e9aee1 Binary files /dev/null and b/tools/probe/corpus/rows/E04IntegerKeyProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E05OwnCompareToProbe.json.gz b/tools/probe/corpus/rows/E05OwnCompareToProbe.json.gz new file mode 100644 index 00000000..4e110c7a Binary files /dev/null and b/tools/probe/corpus/rows/E05OwnCompareToProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E06OwnConverterNoValueProbe.json.gz b/tools/probe/corpus/rows/E06OwnConverterNoValueProbe.json.gz new file mode 100644 index 00000000..a8a88b64 Binary files /dev/null and b/tools/probe/corpus/rows/E06OwnConverterNoValueProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E07ClosedSetNameProbe.json.gz b/tools/probe/corpus/rows/E07ClosedSetNameProbe.json.gz new file mode 100644 index 00000000..ca9d3c13 Binary files /dev/null and b/tools/probe/corpus/rows/E07ClosedSetNameProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E08OptionResultNotColumnsProbe.json.gz b/tools/probe/corpus/rows/E08OptionResultNotColumnsProbe.json.gz new file mode 100644 index 00000000..aaaf1496 Binary files /dev/null and b/tools/probe/corpus/rows/E08OptionResultNotColumnsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E09OrNoneInDbProbe.json.gz b/tools/probe/corpus/rows/E09OrNoneInDbProbe.json.gz new file mode 100644 index 00000000..7b19af30 Binary files /dev/null and b/tools/probe/corpus/rows/E09OrNoneInDbProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E10ReadBackSkipsValidationProbe.json.gz b/tools/probe/corpus/rows/E10ReadBackSkipsValidationProbe.json.gz new file mode 100644 index 00000000..331e16cf Binary files /dev/null and b/tools/probe/corpus/rows/E10ReadBackSkipsValidationProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E11UseTypeKitOrderProbe.json.gz b/tools/probe/corpus/rows/E11UseTypeKitOrderProbe.json.gz new file mode 100644 index 00000000..8af704b4 Binary files /dev/null and b/tools/probe/corpus/rows/E11UseTypeKitOrderProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E12ConventionByTypeProbe.json.gz b/tools/probe/corpus/rows/E12ConventionByTypeProbe.json.gz new file mode 100644 index 00000000..e520ae6a Binary files /dev/null and b/tools/probe/corpus/rows/E12ConventionByTypeProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E13PrimitiveCollectionProbe.json.gz b/tools/probe/corpus/rows/E13PrimitiveCollectionProbe.json.gz new file mode 100644 index 00000000..665ad59f Binary files /dev/null and b/tools/probe/corpus/rows/E13PrimitiveCollectionProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E14InheritanceKeyProbe.json.gz b/tools/probe/corpus/rows/E14InheritanceKeyProbe.json.gz new file mode 100644 index 00000000..c48350c4 Binary files /dev/null and b/tools/probe/corpus/rows/E14InheritanceKeyProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E15CompositeKeyProbe.json.gz b/tools/probe/corpus/rows/E15CompositeKeyProbe.json.gz new file mode 100644 index 00000000..c081c2ac Binary files /dev/null and b/tools/probe/corpus/rows/E15CompositeKeyProbe.json.gz differ diff --git a/tools/probe/corpus/rows/E16OwnConfigurationWinsProbe.json.gz b/tools/probe/corpus/rows/E16OwnConfigurationWinsProbe.json.gz new file mode 100644 index 00000000..ccc341ce Binary files /dev/null and b/tools/probe/corpus/rows/E16OwnConfigurationWinsProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L01-json-bytes.json.gz b/tools/probe/corpus/rows/L01-json-bytes.json.gz deleted file mode 100644 index 2efe168b..00000000 Binary files a/tools/probe/corpus/rows/L01-json-bytes.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/L01JsonBytesProbe.json.gz b/tools/probe/corpus/rows/L01JsonBytesProbe.json.gz new file mode 100644 index 00000000..10767ecc Binary files /dev/null and b/tools/probe/corpus/rows/L01JsonBytesProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L02-no-quote.json.gz b/tools/probe/corpus/rows/L02-no-quote.json.gz deleted file mode 100644 index 0d83e917..00000000 Binary files a/tools/probe/corpus/rows/L02-no-quote.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/L02NoQuoteProbe.json.gz b/tools/probe/corpus/rows/L02NoQuoteProbe.json.gz new file mode 100644 index 00000000..8c537a24 Binary files /dev/null and b/tools/probe/corpus/rows/L02NoQuoteProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L03-one-rule.json.gz b/tools/probe/corpus/rows/L03-one-rule.json.gz deleted file mode 100644 index ae8ace4f..00000000 Binary files a/tools/probe/corpus/rows/L03-one-rule.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/L03OneRuleProbe.json.gz b/tools/probe/corpus/rows/L03OneRuleProbe.json.gz new file mode 100644 index 00000000..fffe2faa Binary files /dev/null and b/tools/probe/corpus/rows/L03OneRuleProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L04-round-trip.json.gz b/tools/probe/corpus/rows/L04-round-trip.json.gz deleted file mode 100644 index aa8e97e6..00000000 Binary files a/tools/probe/corpus/rows/L04-round-trip.json.gz and /dev/null differ diff --git a/tools/probe/corpus/rows/L04RoundTripProbe.json.gz b/tools/probe/corpus/rows/L04RoundTripProbe.json.gz new file mode 100644 index 00000000..3792095b Binary files /dev/null and b/tools/probe/corpus/rows/L04RoundTripProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L05EqualityProbe.json.gz b/tools/probe/corpus/rows/L05EqualityProbe.json.gz new file mode 100644 index 00000000..515deaf4 Binary files /dev/null and b/tools/probe/corpus/rows/L05EqualityProbe.json.gz differ diff --git a/tools/probe/corpus/rows/L06OffTheWireProbe.json.gz b/tools/probe/corpus/rows/L06OffTheWireProbe.json.gz new file mode 100644 index 00000000..46bf206f Binary files /dev/null and b/tools/probe/corpus/rows/L06OffTheWireProbe.json.gz differ diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-covers-every-decimal.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-covers-every-decimal.patch new file mode 100644 index 00000000..ba0401e4 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-covers-every-decimal.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..b60dc4d 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -55,7 +55,7 @@ public sealed class DecimalNumberJsonConverter : JsonConverter<decimal>, IDescri + public override void WriteAsPropertyName(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); +- writer.WritePropertyName(DecimalJson.Text(value)); ++ writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); + } + } + +@@ -105,7 +105,7 @@ public sealed class DecimalStringJsonConverter : JsonConverter<decimal>, IDescri + public override void WriteAsPropertyName(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); +- writer.WritePropertyName(DecimalJson.Text(value)); ++ writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); + } + } + diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-keeps-the-scale-it-reads.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-keeps-the-scale-it-reads.patch new file mode 100644 index 00000000..ea049332 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-keeps-the-scale-it-reads.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..471f160 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -143,7 +143,7 @@ internal static class DecimalJson + /// <summary>A number, or a string holding one.</summary> + public static decimal Read(ref Utf8JsonReader reader) => reader.TokenType switch + { +- JsonTokenType.Number => reader.TryGetDecimal(out var value) ? value : throw new JsonException(), ++ JsonTokenType.Number => reader.TryGetDecimal(out var value) ? Minimal(value) : throw new JsonException(), + JsonTokenType.String => ReadText(ref reader), + _ => throw new JsonException(), + }; +@@ -164,7 +164,7 @@ internal static class DecimalJson + return text is not null && TryParse(Encoding.UTF8.GetBytes(text), out var unescaped) ? unescaped : throw new JsonException(); + } + +- return TryParse(reader.ValueSpan, out var value) ? value : throw new JsonException(); ++ return TryParse(reader.ValueSpan, out var value) ? Minimal(value) : throw new JsonException(); + } + + private static bool TryParse(ReadOnlySpan<byte> utf8, out decimal value) => diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-number-is-a-number.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-number-is-a-number.patch new file mode 100644 index 00000000..a4947393 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-number-is-a-number.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..ef426a1 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -45,7 +45,7 @@ public sealed class DecimalNumberJsonConverter : JsonConverter<decimal>, IDescri + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); +- writer.WriteNumberValue(DecimalJson.Minimal(value)); ++ writer.WriteStringValue(DecimalJson.Text(value)); + } + + /// <inheritdoc /> diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-reads-a-number-or-a-string.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-reads-a-number-or-a-string.patch new file mode 100644 index 00000000..b5c82df6 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-reads-a-number-or-a-string.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..cd760e4 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -144,7 +144,7 @@ internal static class DecimalJson + public static decimal Read(ref Utf8JsonReader reader) => reader.TokenType switch + { + JsonTokenType.Number => reader.TryGetDecimal(out var value) ? value : throw new JsonException(), +- JsonTokenType.String => ReadText(ref reader), ++ JsonTokenType.String => throw new JsonException(), + _ => throw new JsonException(), + }; + diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-smallest-scale.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-smallest-scale.patch new file mode 100644 index 00000000..5afc4c05 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-smallest-scale.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..3c3c878 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -135,7 +135,7 @@ internal static class DecimalJson + }); + + /// <summary>The value at its smallest scale; a zero of any scale or sign is <c>0</c>.</summary> +- public static decimal Minimal(decimal value) => value == 0m ? 0m : value / OneAtLargestScale; ++ public static decimal Minimal(decimal value) => value; + + /// <summary>The minimal-scale spelling, invariant, never with an exponent.</summary> + public static string Text(decimal value) => Minimal(value).ToString(CultureInfo.InvariantCulture); diff --git a/tools/probe/corpus/seeds/C01-decimal-spelling-string-is-a-string.patch b/tools/probe/corpus/seeds/C01-decimal-spelling-string-is-a-string.patch new file mode 100644 index 00000000..0801a816 --- /dev/null +++ b/tools/probe/corpus/seeds/C01-decimal-spelling-string-is-a-string.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +index 9007276..60cebf9 100644 +--- a/src/CodoMetis.TypeKit/DecimalJsonConverters.cs ++++ b/src/CodoMetis.TypeKit/DecimalJsonConverters.cs +@@ -95,7 +95,7 @@ public sealed class DecimalStringJsonConverter : JsonConverter<decimal>, IDescri + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); +- writer.WriteStringValue(DecimalJson.Text(value)); ++ writer.WriteNumberValue(DecimalJson.Minimal(value)); + } + + /// <inheritdoc /> diff --git a/tools/probe/corpus/seeds/C02-optional-text-hidden-is-an-error-in-both.patch b/tools/probe/corpus/seeds/C02-optional-text-hidden-is-an-error-in-both.patch new file mode 100644 index 00000000..7433212c --- /dev/null +++ b/tools/probe/corpus/seeds/C02-optional-text-hidden-is-an-error-in-both.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/StringTextExtensions.cs b/src/CodoMetis.TypeKit/StringTextExtensions.cs +index 29681aa..62ab884 100644 +--- a/src/CodoMetis.TypeKit/StringTextExtensions.cs ++++ b/src/CodoMetis.TypeKit/StringTextExtensions.cs +@@ -163,7 +163,7 @@ public static class StringTextExtensions + /// <summary>A guard's answer for an optional field: <see cref="TextFault.Blank"/> is absence, every other fault a refusal.</summary> + private static Result<Option<string>, TextFault> Optional(Result<string, TextFault> guarded) => + guarded.Map(Option.Some) +- .Recover(fault => fault == TextFault.Blank ? Option.None<string>() : Result.Error(fault)); ++ .Recover(fault => fault is TextFault.Blank or TextFault.Hidden ? Option.None<string>() : Result.Error(fault)); + + private static Result<string, TextFault> Clean(string? text, bool tolerateJoiners, bool canonicalize) + { diff --git a/tools/probe/corpus/seeds/C02-optional-text-null-is-blank.patch b/tools/probe/corpus/seeds/C02-optional-text-null-is-blank.patch new file mode 100644 index 00000000..b68f2896 --- /dev/null +++ b/tools/probe/corpus/seeds/C02-optional-text-null-is-blank.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit/StringTextExtensions.cs b/src/CodoMetis.TypeKit/StringTextExtensions.cs +index 29681aa..76d683e 100644 +--- a/src/CodoMetis.TypeKit/StringTextExtensions.cs ++++ b/src/CodoMetis.TypeKit/StringTextExtensions.cs +@@ -167,6 +167,7 @@ public static class StringTextExtensions + + private static Result<string, TextFault> Clean(string? text, bool tolerateJoiners, bool canonicalize) + { ++ if (text is null) return Result.Error(TextFault.Hidden); + var trimmed = text?.Trim() ?? string.Empty; + + if (trimmed.Length == 0) return Result.Error(TextFault.Blank); diff --git a/tools/probe/corpus/seeds/C02-optional-text-optional-blank-is-no-value.patch b/tools/probe/corpus/seeds/C02-optional-text-optional-blank-is-no-value.patch new file mode 100644 index 00000000..24d74224 --- /dev/null +++ b/tools/probe/corpus/seeds/C02-optional-text-optional-blank-is-no-value.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/StringTextExtensions.cs b/src/CodoMetis.TypeKit/StringTextExtensions.cs +index 29681aa..4eb6b00 100644 +--- a/src/CodoMetis.TypeKit/StringTextExtensions.cs ++++ b/src/CodoMetis.TypeKit/StringTextExtensions.cs +@@ -163,7 +163,7 @@ public static class StringTextExtensions + /// <summary>A guard's answer for an optional field: <see cref="TextFault.Blank"/> is absence, every other fault a refusal.</summary> + private static Result<Option<string>, TextFault> Optional(Result<string, TextFault> guarded) => + guarded.Map(Option.Some) +- .Recover(fault => fault == TextFault.Blank ? Option.None<string>() : Result.Error(fault)); ++ .Recover(fault => fault == (TextFault)99 ? Option.None<string>() : Result.Error(fault)); + + private static Result<string, TextFault> Clean(string? text, bool tolerateJoiners, bool canonicalize) + { diff --git a/tools/probe/corpus/seeds/C02-optional-text-required-blank-is-an-error.patch b/tools/probe/corpus/seeds/C02-optional-text-required-blank-is-an-error.patch new file mode 100644 index 00000000..2efb96c3 --- /dev/null +++ b/tools/probe/corpus/seeds/C02-optional-text-required-blank-is-an-error.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit/StringTextExtensions.cs b/src/CodoMetis.TypeKit/StringTextExtensions.cs +index 29681aa..5a6eea9 100644 +--- a/src/CodoMetis.TypeKit/StringTextExtensions.cs ++++ b/src/CodoMetis.TypeKit/StringTextExtensions.cs +@@ -169,6 +169,7 @@ public static class StringTextExtensions + { + var trimmed = text?.Trim() ?? string.Empty; + ++ if (trimmed.Length == 0 && text is not null) return trimmed; + if (trimmed.Length == 0) return Result.Error(TextFault.Blank); + if (IsHidden(trimmed, tolerateJoiners)) return Result.Error(TextFault.Hidden); + diff --git a/tools/probe/corpus/seeds/C03-composed-faults.patch b/tools/probe/corpus/seeds/C03-composed-faults.patch new file mode 100644 index 00000000..f4b6159e --- /dev/null +++ b/tools/probe/corpus/seeds/C03-composed-faults.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 475e121..90b855b 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -148,7 +148,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + /// </summary> + private static void Ascending(OpenApiResponses responses) + { +- var ordered = responses.OrderBy(response => Rank(response.Key)).ToList(); ++ var ordered = responses.OrderByDescending(response => Rank(response.Key)).ToList(); + if (ordered.Select(response => response.Key).SequenceEqual(responses.Keys, StringComparer.Ordinal)) return; + + // A dictionary cleared and filled again enumerates in the order it was filled, as the writer reads it. diff --git a/tools/probe/corpus/seeds/C03-detail-and-rule-intact.patch b/tools/probe/corpus/seeds/C03-detail-and-rule-intact.patch new file mode 100644 index 00000000..a7ea20bf --- /dev/null +++ b/tools/probe/corpus/seeds/C03-detail-and-rule-intact.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs b/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs +index 6df36fb..17611f3 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs +@@ -249,7 +249,7 @@ public static class TypeKitHttpResultExtensions + static fault => fault.Match<Results<TSuccess, FaultProblem<T1>, FaultProblem<T2>, FaultProblem<T3>>>(static f => Problem(f), static f => Problem(f), static f => Problem(f))); + } + +- private static FaultProblem<TFault> Problem<TFault>(Fault<TFault> fault) where TFault : notnull => new(fault.Reason, fault.Detail, fault.Rule); ++ private static FaultProblem<TFault> Problem<TFault>(Fault<TFault> fault) where TFault : notnull => new(fault.Reason, null, fault.Rule); + } + + /// <summary>Turns a <c>Result</c> whose fault is a closed set into the HTTP response an endpoint returns, as <see cref="TypeKitHttpResultExtensions"/> does for an enum.</summary> diff --git a/tools/probe/corpus/seeds/C03-each-error-own-set.patch b/tools/probe/corpus/seeds/C03-each-error-own-set.patch new file mode 100644 index 00000000..61b70590 --- /dev/null +++ b/tools/probe/corpus/seeds/C03-each-error-own-set.patch @@ -0,0 +1,35 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +index f9919a2..1e4ed87 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +@@ -47,6 +47,8 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + /// <summary>The rule that refused, <c>EmailFault.NoAt</c>, where the result carried a <see cref="Fault{TFault}"/> naming one.</summary> + public string? Rule { get; } + ++ internal bool Composed { get; init; } ++ + /// <inheritdoc /> + public async Task ExecuteAsync(HttpContext httpContext) + { +@@ -77,7 +79,7 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + { + await TypedResults.Problem( + statusCode: status, +- type: answer.Type, ++ type: Composed ? null : answer.Type, + title: answer.Title, + detail: detail, + extensions: extensions) +diff --git a/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs b/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs +index 6df36fb..83ae661 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/TypeKitHttpResultExtensions.cs +@@ -249,7 +249,7 @@ public static class TypeKitHttpResultExtensions + static fault => fault.Match<Results<TSuccess, FaultProblem<T1>, FaultProblem<T2>, FaultProblem<T3>>>(static f => Problem(f), static f => Problem(f), static f => Problem(f))); + } + +- private static FaultProblem<TFault> Problem<TFault>(Fault<TFault> fault) where TFault : notnull => new(fault.Reason, fault.Detail, fault.Rule); ++ private static FaultProblem<TFault> Problem<TFault>(Fault<TFault> fault) where TFault : notnull => new(fault.Reason, fault.Detail, fault.Rule) { Composed = true }; + } + + /// <summary>Turns a <c>Result</c> whose fault is a closed set into the HTTP response an endpoint returns, as <see cref="TypeKitHttpResultExtensions"/> does for an enum.</summary> diff --git a/tools/probe/corpus/seeds/C03-shared-status-types.patch b/tools/probe/corpus/seeds/C03-shared-status-types.patch new file mode 100644 index 00000000..84054939 --- /dev/null +++ b/tools/probe/corpus/seeds/C03-shared-status-types.patch @@ -0,0 +1,14 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 8e936ee..934c951 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -98,7 +98,8 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + + foreach (var status in statuses) + { +- if (!answered.TryGetValue(status, out var types)) answered[status] = types = []; ++ if (answered.TryGetValue(status, out var types)) continue; ++ answered[status] = types = []; + types.AddRange(metadata.Types(context.ApplicationServices, status, endpointMetadata).Except(types, StringComparer.Ordinal).ToList()); + } + } diff --git a/tools/probe/corpus/seeds/C03-unmapped-set-stops-start.patch b/tools/probe/corpus/seeds/C03-unmapped-set-stops-start.patch new file mode 100644 index 00000000..a5429447 --- /dev/null +++ b/tools/probe/corpus/seeds/C03-unmapped-set-stops-start.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..2ff421e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -50,7 +50,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + + var unmapped = endpoints.Endpoints + .SelectMany(endpoint => endpoint.Metadata.OfType<FaultResponsesMetadata>().Select(metadata => (Endpoint: endpoint, Metadata: metadata))) +- .Where(pair => pair.Metadata.Statuses(services, pair.Endpoint.Metadata) is null) ++ .Where(pair => pair.Metadata.Statuses(services, pair.Endpoint.Metadata) is null && pair.Endpoint.Metadata.OfType<FaultResponsesMetadata>().Distinct().Count() < 2) + // By type, not by name: two nested types of one simple name are two refusals. + .GroupBy(pair => pair.Metadata.FaultType, pair => (pair.Metadata.FaultName, Endpoint: Name(pair.Endpoint))) + .Select(fault => diff --git a/tools/probe/corpus/seeds/C04-left-out-everywhere-stops-start.patch b/tools/probe/corpus/seeds/C04-left-out-everywhere-stops-start.patch new file mode 100644 index 00000000..d539295c --- /dev/null +++ b/tools/probe/corpus/seeds/C04-left-out-everywhere-stops-start.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..707ddd2 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -88,7 +88,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + .Select(left => (left.Status, left.Member, left.Message, Endpoint: endpoint))) + .GroupBy(left => left.Status, DeclarationComparer) + .SelectMany(status => status.GroupBy(left => left.Member, StringComparer.Ordinal) +- .Where(member => scopes[status.Key].All(endpoint => member.Any(left => left.Endpoint == endpoint))) ++ .Where(member => scopes[status.Key].All(endpoint => member.Any(left => left.Endpoint == endpoint)) && scopes[status.Key].Count == 0) + .Select(member => $"{member.First().Message}, on {string.Join(", ", member.Select(left => Name(left.Endpoint)).Distinct())}.")); + + // Two fault types an endpoint answers with should not write one name: the client reads two faults diff --git a/tools/probe/corpus/seeds/C04-narrows-wider-status.patch b/tools/probe/corpus/seeds/C04-narrows-wider-status.patch new file mode 100644 index 00000000..f42097b8 --- /dev/null +++ b/tools/probe/corpus/seeds/C04-narrows-wider-status.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..46478c5 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -88,7 +88,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + .Select(left => (left.Status, left.Member, left.Message, Endpoint: endpoint))) + .GroupBy(left => left.Status, DeclarationComparer) + .SelectMany(status => status.GroupBy(left => left.Member, StringComparer.Ordinal) +- .Where(member => scopes[status.Key].All(endpoint => member.Any(left => left.Endpoint == endpoint))) ++ .Where(member => scopes[status.Key].Any(endpoint => member.Any(left => left.Endpoint == endpoint))) + .Select(member => $"{member.First().Message}, on {string.Join(", ", member.Select(left => Name(left.Endpoint)).Distinct())}.")); + + // Two fault types an endpoint answers with should not write one name: the client reads two faults diff --git a/tools/probe/corpus/seeds/C04-reporting-left-out-throws.patch b/tools/probe/corpus/seeds/C04-reporting-left-out-throws.patch new file mode 100644 index 00000000..cf3b3824 --- /dev/null +++ b/tools/probe/corpus/seeds/C04-reporting-left-out-throws.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +index f9919a2..03c9112 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +@@ -57,7 +57,7 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + var endpoint = httpContext.GetEndpoint(); + + // The document lists no status for a member the endpoint leaves out: answering it would contradict it. +- if (endpoint is not null && FaultExclusions.Excluding(endpoint.Metadata, Fault, responses.Type) is { } exclusion) ++ if (endpoint is not null && FaultExclusions.Excluding([], Fault, responses.Type) is { } exclusion) + throw new InvalidOperationException( + $"{endpoint.DisplayName ?? "The endpoint"} reported {answer.QualifiedName}, which {exclusion.Describe()} leaves out of its document, " + + "so no status the document lists answers it. Remove the member from what the endpoint leaves out, or report another." + FaultRefusals.LeftOut); diff --git a/tools/probe/corpus/seeds/C04-without-faults.patch b/tools/probe/corpus/seeds/C04-without-faults.patch new file mode 100644 index 00000000..28e340aa --- /dev/null +++ b/tools/probe/corpus/seeds/C04-without-faults.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..60bc81e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -157,7 +157,7 @@ internal sealed class FaultResponses<TFault> where TFault : notnull + + /// <summary>The members an endpoint answers with: every member, but those its metadata leaves out.</summary> + public IEnumerable<TFault> AnsweredBy(IReadOnlyList<object> endpointMetadata) => +- Type.Members.Where(fault => FaultExclusions.Excluding(endpointMetadata, fault, Type) is null); ++ Type.Members; + + private int StatusOf(TFault fault, IReadOnlyList<object> endpointMetadata) => + FaultStatuses.Override(endpointMetadata, fault, Type) ?? _answers[fault].Status; diff --git a/tools/probe/corpus/seeds/C05-no-namespace.patch b/tools/probe/corpus/seeds/C05-no-namespace.patch new file mode 100644 index 00000000..da87d2dc --- /dev/null +++ b/tools/probe/corpus/seeds/C05-no-namespace.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/Fault.cs b/src/CodoMetis.TypeKit/Fault.cs +index e2f33a8..88c10d3 100644 +--- a/src/CodoMetis.TypeKit/Fault.cs ++++ b/src/CodoMetis.TypeKit/Fault.cs +@@ -180,7 +180,7 @@ internal static class FaultRuleNames + { + /// <summary><c>Billing.Fault</c> for an enum nested in <c>Billing</c>, without a generic arity.</summary> + public static string NestingChain(Type type) => +- type.DeclaringType is { } declaring ? $"{NestingChain(declaring)}.{Simple(type)}" : Simple(type); ++ type.DeclaringType is { } declaring ? $"{NestingChain(declaring)}.{Simple(type)}" : $"{type.Namespace}.{Simple(type)}"; + + private static string Simple(Type type) => + type.Name.IndexOf('`') is var tick and >= 0 ? type.Name[..tick] : type.Name; diff --git a/tools/probe/corpus/seeds/C05-rule-qualified.patch b/tools/probe/corpus/seeds/C05-rule-qualified.patch new file mode 100644 index 00000000..9e37bad3 --- /dev/null +++ b/tools/probe/corpus/seeds/C05-rule-qualified.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/Fault.cs b/src/CodoMetis.TypeKit/Fault.cs +index e2f33a8..700c935 100644 +--- a/src/CodoMetis.TypeKit/Fault.cs ++++ b/src/CodoMetis.TypeKit/Fault.cs +@@ -170,7 +170,7 @@ public static class FaultClosedSetRuleExtensions + public Fault<TFault> WithRule<TRule>(TRule rule) where TRule : class, IClosedSet<TRule> + { + ArgumentNullException.ThrowIfNull(rule); +- return fault.Naming(rule.QualifiedName, typeof(TRule)); ++ return fault.Naming(rule.QualifiedName[(rule.QualifiedName.LastIndexOf('.') + 1)..], typeof(TRule)); + } + } + } diff --git a/tools/probe/corpus/seeds/C05-stays-the-errors.patch b/tools/probe/corpus/seeds/C05-stays-the-errors.patch new file mode 100644 index 00000000..df10b2ab --- /dev/null +++ b/tools/probe/corpus/seeds/C05-stays-the-errors.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +index f9919a2..d8a9e52 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +@@ -62,7 +62,7 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + $"{endpoint.DisplayName ?? "The endpoint"} reported {answer.QualifiedName}, which {exclusion.Describe()} leaves out of its document, " + + "so no status the document lists answers it. Remove the member from what the endpoint leaves out, or report another." + FaultRefusals.LeftOut); + +- var status = endpoint is not null ? FaultStatuses.Override(endpoint.Metadata, Fault, responses.Type) ?? answer.Status : answer.Status; ++ var status = Rule is not null ? 400 : endpoint is not null ? FaultStatuses.Override(endpoint.Metadata, Fault, responses.Type) ?? answer.Status : answer.Status; + var name = httpContext.RequestServices.GetService<IOptions<FaultResponseOptions>>()?.Value.QualifiedFaultNames == true ? answer.QualifiedName : answer.Name; + var detail = Detail ?? answer.Description; + diff --git a/tools/probe/corpus/seeds/C05-unnamed-value-throws.patch b/tools/probe/corpus/seeds/C05-unnamed-value-throws.patch new file mode 100644 index 00000000..80e56053 --- /dev/null +++ b/tools/probe/corpus/seeds/C05-unnamed-value-throws.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/Fault.cs b/src/CodoMetis.TypeKit/Fault.cs +index e2f33a8..e62ef70 100644 +--- a/src/CodoMetis.TypeKit/Fault.cs ++++ b/src/CodoMetis.TypeKit/Fault.cs +@@ -144,7 +144,7 @@ public static class FaultRuleExtensions + /// <returns>A new fault.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="rule"/> is no member of <typeparamref name="TRule"/>.</exception> + public Fault<TFault> WithRule<TRule>(TRule rule) where TRule : struct, Enum => +- Enum.IsDefined(rule) ++ Enum.IsDefined(rule) || rule.GetType().IsEnum + ? fault.Naming($"{FaultRuleNames.NestingChain(typeof(TRule))}.{rule}", typeof(TRule)) + : throw new ArgumentOutOfRangeException(nameof(rule), $"A rule is one of {FaultRuleNames.NestingChain(typeof(TRule))}'s members, and no member is this value."); + } diff --git a/tools/probe/corpus/seeds/C05-with-rule.patch b/tools/probe/corpus/seeds/C05-with-rule.patch new file mode 100644 index 00000000..d90f93c3 --- /dev/null +++ b/tools/probe/corpus/seeds/C05-with-rule.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/Fault.cs b/src/CodoMetis.TypeKit/Fault.cs +index e2f33a8..a73dcad 100644 +--- a/src/CodoMetis.TypeKit/Fault.cs ++++ b/src/CodoMetis.TypeKit/Fault.cs +@@ -180,7 +180,7 @@ internal static class FaultRuleNames + { + /// <summary><c>Billing.Fault</c> for an enum nested in <c>Billing</c>, without a generic arity.</summary> + public static string NestingChain(Type type) => +- type.DeclaringType is { } declaring ? $"{NestingChain(declaring)}.{Simple(type)}" : Simple(type); ++ Simple(type); + + private static string Simple(Type type) => + type.Name.IndexOf('`') is var tick and >= 0 ? type.Name[..tick] : type.Name; diff --git a/tools/probe/corpus/seeds/C06-description-as-detail.patch b/tools/probe/corpus/seeds/C06-description-as-detail.patch new file mode 100644 index 00000000..9613e7d3 --- /dev/null +++ b/tools/probe/corpus/seeds/C06-description-as-detail.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +index f9919a2..6e9a2ff 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +@@ -64,7 +64,7 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + + var status = endpoint is not null ? FaultStatuses.Override(endpoint.Metadata, Fault, responses.Type) ?? answer.Status : answer.Status; + var name = httpContext.RequestServices.GetService<IOptions<FaultResponseOptions>>()?.Value.QualifiedFaultNames == true ? answer.QualifiedName : answer.Name; +- var detail = Detail ?? answer.Description; ++ var detail = Detail; + + // Present only while this problem is written, so a CustomizeProblemDetails that reads it sees it + // for this problem and for no other the request writes. diff --git a/tools/probe/corpus/seeds/C06-fault-kinds.patch b/tools/probe/corpus/seeds/C06-fault-kinds.patch new file mode 100644 index 00000000..9b96e486 --- /dev/null +++ b/tools/probe/corpus/seeds/C06-fault-kinds.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponse.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponse.cs +index fac2b51..2733f4a 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponse.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponse.cs +@@ -61,7 +61,7 @@ public sealed class FaultResponse + FaultKind.Forbidden => 403, + FaultKind.Unavailable => 503, + FaultKind.PreconditionFailed => 412, +- FaultKind.Malformed => 400, ++ FaultKind.Malformed => 422, + _ => throw new ArgumentOutOfRangeException(nameof(kind), $"{(int)kind} is no FaultKind: a fault set's member declares one of its members, and a default kind is none." + FaultRefusals.Mapping), + }); + diff --git a/tools/probe/corpus/seeds/C06-member-must-have-kind.patch b/tools/probe/corpus/seeds/C06-member-must-have-kind.patch new file mode 100644 index 00000000..52ff5bc4 --- /dev/null +++ b/tools/probe/corpus/seeds/C06-member-must-have-kind.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/TypeKitFaultResponsesServiceCollectionExtensions.cs b/src/CodoMetis.TypeKit.AspNetCore/TypeKitFaultResponsesServiceCollectionExtensions.cs +index 778a3b0..af9eeaa 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/TypeKitFaultResponsesServiceCollectionExtensions.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/TypeKitFaultResponsesServiceCollectionExtensions.cs +@@ -91,7 +91,7 @@ public static class TypeKitClosedSetFaultResponsesServiceCollectionExtensions + { + ArgumentNullException.ThrowIfNull(services); + +- return FaultRegistration.Add(services, FaultTypes.Set<TFault>(), static fault => FaultRegistration.ByKind(fault, fault.QualifiedName, nameof(TFault)), nameof(TFault)); ++ return FaultRegistration.Add(services, FaultTypes.Set<TFault>(), static fault => fault.Kind == default ? FaultResponse.Status(500) : FaultRegistration.ByKind(fault, fault.QualifiedName, nameof(TFault)), nameof(TFault)); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/C06-no-description-no-detail.patch b/tools/probe/corpus/seeds/C06-no-description-no-detail.patch new file mode 100644 index 00000000..eae704b2 --- /dev/null +++ b/tools/probe/corpus/seeds/C06-no-description-no-detail.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +index f9919a2..8975c02 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultProblem.cs +@@ -64,7 +64,7 @@ public sealed class FaultProblem<TFault> : IResult, IEndpointMetadataProvider + + var status = endpoint is not null ? FaultStatuses.Override(endpoint.Metadata, Fault, responses.Type) ?? answer.Status : answer.Status; + var name = httpContext.RequestServices.GetService<IOptions<FaultResponseOptions>>()?.Value.QualifiedFaultNames == true ? answer.QualifiedName : answer.Name; +- var detail = Detail ?? answer.Description; ++ var detail = Detail ?? answer.Description ?? answer.Name; + + // Present only while this problem is written, so a CustomizeProblemDetails that reads it sees it + // for this problem and for no other the request writes. diff --git a/tools/probe/corpus/seeds/C07-blanket-mapping.patch b/tools/probe/corpus/seeds/C07-blanket-mapping.patch new file mode 100644 index 00000000..dca9eb55 --- /dev/null +++ b/tools/probe/corpus/seeds/C07-blanket-mapping.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..402c843 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -167,8 +167,8 @@ internal sealed class FaultResponses<TFault> where TFault : notnull + /// fault set, or <see langword="null"/> where neither answers it. + /// </summary> + public static FaultResponses<TFault>? Of(IServiceProvider services) => +- services.GetService(typeof(FaultResponses<TFault>)) as FaultResponses<TFault> +- ?? (services.GetService(typeof(DefaultFaultResponses)) as DefaultFaultResponses)?.For<TFault>(); ++ (services.GetService(typeof(DefaultFaultResponses)) as DefaultFaultResponses)?.For<TFault>() ++ ?? services.GetService(typeof(FaultResponses<TFault>)) as FaultResponses<TFault>; + + public static InvalidOperationException Unmapped() => + new($"No HTTP status is mapped for the fault type {FaultTypes.NameOf(typeof(TFault))}. " + diff --git a/tools/probe/corpus/seeds/C07-by-kind.patch b/tools/probe/corpus/seeds/C07-by-kind.patch new file mode 100644 index 00000000..5f85cc51 --- /dev/null +++ b/tools/probe/corpus/seeds/C07-by-kind.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..83f4de0 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -74,7 +74,7 @@ internal sealed class DefaultFaultResponses(Func<IFault, FaultResponse>? respons + private string Parameter => parameter; + + private FaultResponse ResponseOf(IFault fault, string qualifiedName) => +- responseOf is not null ? responseOf(fault) : FaultRegistration.ByKind(fault, qualifiedName, parameter); ++ responseOf is not null ? responseOf(fault) : fault.Kind == FaultKind.Conflict ? FaultResponse.Status(400) : FaultRegistration.ByKind(fault, qualifiedName, parameter); + + private sealed class Mapper(DefaultFaultResponses defaults) : IClosedSetVisitor<object> + { diff --git a/tools/probe/corpus/seeds/C07-enum-needs-own-mapping.patch b/tools/probe/corpus/seeds/C07-enum-needs-own-mapping.patch new file mode 100644 index 00000000..9ac0cd54 --- /dev/null +++ b/tools/probe/corpus/seeds/C07-enum-needs-own-mapping.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..e1fb380 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -50,7 +50,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + + var unmapped = endpoints.Endpoints + .SelectMany(endpoint => endpoint.Metadata.OfType<FaultResponsesMetadata>().Select(metadata => (Endpoint: endpoint, Metadata: metadata))) +- .Where(pair => pair.Metadata.Statuses(services, pair.Endpoint.Metadata) is null) ++ .Where(pair => pair.Metadata.Statuses(services, pair.Endpoint.Metadata) is null && !pair.Metadata.FaultType.IsEnum) + // By type, not by name: two nested types of one simple name are two refusals. + .GroupBy(pair => pair.Metadata.FaultType, pair => (pair.Metadata.FaultName, Endpoint: Name(pair.Endpoint))) + .Select(fault => diff --git a/tools/probe/corpus/seeds/C07-fault-responses-blanket.patch b/tools/probe/corpus/seeds/C07-fault-responses-blanket.patch new file mode 100644 index 00000000..83e4a77b --- /dev/null +++ b/tools/probe/corpus/seeds/C07-fault-responses-blanket.patch @@ -0,0 +1,19 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +index 185f65e..a1c4646 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +@@ -81,12 +81,7 @@ internal sealed partial class ValueObjectAspect + return true; + } + +- result = meta.Default(target); +- +- return (bool)ExpressionFactory.Parse( +- $"{ValueObjectTypes.SourceName(target)}.Create(value).TryGetValue(out result, out _)", +- TypeFactory.GetType(SpecialType.Boolean), +- false +- ).Value!; ++ result = constructor.Invoke(value); ++ return true; + } + } diff --git a/tools/probe/corpus/seeds/C07-mapping-refused.patch b/tools/probe/corpus/seeds/C07-mapping-refused.patch new file mode 100644 index 00000000..330023b6 --- /dev/null +++ b/tools/probe/corpus/seeds/C07-mapping-refused.patch @@ -0,0 +1,27 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..f8547f9 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -74,7 +74,21 @@ internal sealed class DefaultFaultResponses(Func<IFault, FaultResponse>? respons + private string Parameter => parameter; + + private FaultResponse ResponseOf(IFault fault, string qualifiedName) => +- responseOf is not null ? responseOf(fault) : FaultRegistration.ByKind(fault, qualifiedName, parameter); ++ responseOf is not null ? Lenient(fault) : FaultRegistration.ByKind(fault, qualifiedName, parameter); ++ ++ // A member of no kind still fails: FaultResponse.For throws an ArgumentException for it. ++ private FaultResponse Lenient(IFault fault) ++ { ++ try ++ { ++ var response = responseOf!(fault); ++ return response.StatusCode is < 400 or > 599 ? FaultResponse.Status(500) : response; ++ } ++ catch (Exception refused) when (refused is not ArgumentException) ++ { ++ return FaultResponse.Status(500); ++ } ++ } + + private sealed class Mapper(DefaultFaultResponses defaults) : IClosedSetVisitor<object> + { diff --git a/tools/probe/corpus/seeds/C07-member-without-kind.patch b/tools/probe/corpus/seeds/C07-member-without-kind.patch new file mode 100644 index 00000000..285f8fef --- /dev/null +++ b/tools/probe/corpus/seeds/C07-member-without-kind.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..27e934e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -74,7 +74,7 @@ internal sealed class DefaultFaultResponses(Func<IFault, FaultResponse>? respons + private string Parameter => parameter; + + private FaultResponse ResponseOf(IFault fault, string qualifiedName) => +- responseOf is not null ? responseOf(fault) : FaultRegistration.ByKind(fault, qualifiedName, parameter); ++ responseOf is not null ? responseOf(fault) : fault.Kind == default ? FaultResponse.Status(500) : FaultRegistration.ByKind(fault, qualifiedName, parameter); + + private sealed class Mapper(DefaultFaultResponses defaults) : IClosedSetVisitor<object> + { diff --git a/tools/probe/corpus/seeds/C07-own-mapping.patch b/tools/probe/corpus/seeds/C07-own-mapping.patch new file mode 100644 index 00000000..ee35390d --- /dev/null +++ b/tools/probe/corpus/seeds/C07-own-mapping.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..d82f12d 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -74,7 +74,7 @@ internal sealed class DefaultFaultResponses(Func<IFault, FaultResponse>? respons + private string Parameter => parameter; + + private FaultResponse ResponseOf(IFault fault, string qualifiedName) => +- responseOf is not null ? responseOf(fault) : FaultRegistration.ByKind(fault, qualifiedName, parameter); ++ responseOf is not null ? FaultResponse.Status(responseOf(fault).StatusCode) : FaultRegistration.ByKind(fault, qualifiedName, parameter); + + private sealed class Mapper(DefaultFaultResponses defaults) : IClosedSetVisitor<object> + { diff --git a/tools/probe/corpus/seeds/C08-leave-one-out-fixes.patch b/tools/probe/corpus/seeds/C08-leave-one-out-fixes.patch new file mode 100644 index 00000000..dbc3dcb1 --- /dev/null +++ b/tools/probe/corpus/seeds/C08-leave-one-out-fixes.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..1621816 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -264,7 +264,7 @@ internal sealed class FaultResponsesMetadata<TFault> : FaultResponsesMetadata wh + + public override IEnumerable<(string Name, string QualifiedName)> Names(IServiceProvider services, IReadOnlyList<object> endpointMetadata) => + Responses(services) is { } responses +- ? responses.AnsweredBy(endpointMetadata).Select(fault => (responses.Type.NameOf(fault), responses.Type.QualifiedNameOf(fault))) ++ ? responses.Type.Members.Select(fault => (responses.Type.NameOf(fault), responses.Type.QualifiedNameOf(fault))) + : []; + + private static FaultResponses<TFault>? Responses(IServiceProvider services) => FaultResponses<TFault>.Of(services); diff --git a/tools/probe/corpus/seeds/C08-qualified-chain-warns.patch b/tools/probe/corpus/seeds/C08-qualified-chain-warns.patch new file mode 100644 index 00000000..30beb2af --- /dev/null +++ b/tools/probe/corpus/seeds/C08-qualified-chain-warns.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..b46402c 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -115,7 +115,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + if (answered.Count < 2) return []; + + return answered.SelectMany(metadata => metadata.Names(services, endpoint.Metadata) +- .Select(name => (metadata.FaultType, Written: qualified ? name.QualifiedName : name.Name, ++ .Select(name => (metadata.FaultType, Written: qualified ? $"{metadata.FaultType.Namespace}.{name.QualifiedName}" : name.Name, + Described: qualified ? $"{metadata.FaultType.Namespace}.{name.QualifiedName}" : name.QualifiedName))) + .GroupBy(name => name.Written, StringComparer.Ordinal) + .Where(name => name.Select(member => member.FaultType).Distinct().Count() > 1) diff --git a/tools/probe/corpus/seeds/C08-qualified-names-fix.patch b/tools/probe/corpus/seeds/C08-qualified-names-fix.patch new file mode 100644 index 00000000..edc67141 --- /dev/null +++ b/tools/probe/corpus/seeds/C08-qualified-names-fix.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..88eae5b 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -115,7 +115,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + if (answered.Count < 2) return []; + + return answered.SelectMany(metadata => metadata.Names(services, endpoint.Metadata) +- .Select(name => (metadata.FaultType, Written: qualified ? name.QualifiedName : name.Name, ++ .Select(name => (metadata.FaultType, Written: name.Name, + Described: qualified ? $"{metadata.FaultType.Namespace}.{name.QualifiedName}" : name.QualifiedName))) + .GroupBy(name => name.Written, StringComparer.Ordinal) + .Where(name => name.Select(member => member.FaultType).Distinct().Count() > 1) diff --git a/tools/probe/corpus/seeds/C08-same-name-warning.patch b/tools/probe/corpus/seeds/C08-same-name-warning.patch new file mode 100644 index 00000000..c3a0026c --- /dev/null +++ b/tools/probe/corpus/seeds/C08-same-name-warning.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..af9d46d 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -115,7 +115,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + if (answered.Count < 2) return []; + + return answered.SelectMany(metadata => metadata.Names(services, endpoint.Metadata) +- .Select(name => (metadata.FaultType, Written: qualified ? name.QualifiedName : name.Name, ++ .Select(name => (metadata.FaultType, Written: name.QualifiedName, + Described: qualified ? $"{metadata.FaultType.Namespace}.{name.QualifiedName}" : name.QualifiedName))) + .GroupBy(name => name.Written, StringComparer.Ordinal) + .Where(name => name.Select(member => member.FaultType).Distinct().Count() > 1) diff --git a/tools/probe/corpus/seeds/C08-warning-names-both.patch b/tools/probe/corpus/seeds/C08-warning-names-both.patch new file mode 100644 index 00000000..b5b2b95b --- /dev/null +++ b/tools/probe/corpus/seeds/C08-warning-names-both.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +index c537edf..3788edc 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesStartupCheck.cs +@@ -120,7 +120,7 @@ internal sealed class FaultResponsesStartupCheck : IStartupFilter + .GroupBy(name => name.Written, StringComparer.Ordinal) + .Where(name => name.Select(member => member.FaultType).Distinct().Count() > 1) + .Select(name => +- $"{Name(endpoint)} answers {string.Join(" and ", name.Select(member => member.Described))} alike, as the fault \"{name.Key}\": " + ++ $"{Name(endpoint)} answers {string.Join(" and ", name.Select(member => member.Described).Take(1))} alike, as the fault \"{name.Key}\": " + + (qualified + ? "rename one type, or leave out the one the endpoint never answers with." + : "set FaultResponseOptions.QualifiedFaultNames, rename one, or leave out the one the endpoint never answers with.")); diff --git a/tools/probe/corpus/seeds/C09-base-record-equality-equal-by-the-wrapped-value.patch b/tools/probe/corpus/seeds/C09-base-record-equality-equal-by-the-wrapped-value.patch new file mode 100644 index 00000000..3282ac6b --- /dev/null +++ b/tools/probe/corpus/seeds/C09-base-record-equality-equal-by-the-wrapped-value.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +index 07a7ecb..6ee3f6e 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +@@ -51,7 +51,7 @@ internal sealed partial class ValueObjectContractAspect + [Template] + public bool EqualsTemplate(dynamic? other, [CompileTime] INamedType valueType) => + (bool)ExpressionFactory.Parse( +- $"global::System.Object.ReferenceEquals(this, other) || (other is not null && global::System.Collections.Generic.EqualityComparer<{ValueObjectTypes.SourceName(valueType)}>.Default.Equals(this._value, other._value))" ++ $"global::System.Object.ReferenceEquals(this, other) || (other is not null && base.Equals(other) && global::System.Collections.Generic.EqualityComparer<{ValueObjectTypes.SourceName(valueType)}>.Default.Equals(this._value, other._value))" + ).Value!; + + /// <summary>The wrapped value's hash code, which a default instance over a reference type has as 0.</summary> diff --git a/tools/probe/corpus/seeds/C09-base-record-equality-in-a-collection-of-the-base.patch b/tools/probe/corpus/seeds/C09-base-record-equality-in-a-collection-of-the-base.patch new file mode 100644 index 00000000..8db69b8b --- /dev/null +++ b/tools/probe/corpus/seeds/C09-base-record-equality-in-a-collection-of-the-base.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs +index c5f440a..420295b 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs +@@ -96,6 +96,8 @@ internal sealed partial class ValueObjectContractAspect : TypeAspect + + for (var type = direct; type is { SpecialType: not SpecialType.Object }; type = type.BaseType) + { ++ if (!type.Equals(direct)) break; ++ + builder.ImplementInterface(TypeFactory.GetNamedType(typeof(IReimplementedEquatable<>)).MakeGenericInstance(type), OverrideStrategy.Ignore); + + if (type.Equals(direct) || !ExplicitlyImplementsEquatableBetween(direct, type, equatable.MakeGenericInstance(type))) continue; diff --git a/tools/probe/corpus/seeds/C10-preserve-guard-ignore-cycles-unaffected.patch b/tools/probe/corpus/seeds/C10-preserve-guard-ignore-cycles-unaffected.patch new file mode 100644 index 00000000..58a428d0 --- /dev/null +++ b/tools/probe/corpus/seeds/C10-preserve-guard-ignore-cycles-unaffected.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..0208a71 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -63,7 +63,7 @@ public sealed class GeneratedJsonPlan<T> + _writesAsIs = !IsNumber || (handling & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) == 0; + _readsAsIs = !IsNumber || handling == JsonNumberHandling.Strict; + +- if (_direct is null && options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && MayHoldReferences(options, typeInfo, [])) ++ if (_direct is null && options.ReferenceHandler is { } handler && MayHoldReferences(options, typeInfo, [])) + throw new NotSupportedException( + $"A value object over {typeof(T)} cannot be written or read under {nameof(JsonSerializerOptions.ReferenceHandler)}.{nameof(ReferenceHandler.Preserve)}: " + + $"the serializer numbers references per call, and the value object's converter reaches the {typeof(T)} through a call of its own, so its \"$id\" and \"$ref\" " diff --git a/tools/probe/corpus/seeds/C10-preserve-guard-own-converters-unaffected.patch b/tools/probe/corpus/seeds/C10-preserve-guard-own-converters-unaffected.patch new file mode 100644 index 00000000..04890491 --- /dev/null +++ b/tools/probe/corpus/seeds/C10-preserve-guard-own-converters-unaffected.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..fc19136 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -63,7 +63,7 @@ public sealed class GeneratedJsonPlan<T> + _writesAsIs = !IsNumber || (handling & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) == 0; + _readsAsIs = !IsNumber || handling == JsonNumberHandling.Strict; + +- if (_direct is null && options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && MayHoldReferences(options, typeInfo, [])) ++ if (options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && (_direct is null ? MayHoldReferences(options, typeInfo, []) : !GeneratedJson.IsTheSerializers(typeInfo.Converter))) + throw new NotSupportedException( + $"A value object over {typeof(T)} cannot be written or read under {nameof(JsonSerializerOptions.ReferenceHandler)}.{nameof(ReferenceHandler.Preserve)}: " + + $"the serializer numbers references per call, and the value object's converter reaches the {typeof(T)} through a call of its own, so its \"$id\" and \"$ref\" " diff --git a/tools/probe/corpus/seeds/C10-preserve-guard-refused-where-references-could-be-written.patch b/tools/probe/corpus/seeds/C10-preserve-guard-refused-where-references-could-be-written.patch new file mode 100644 index 00000000..32712200 --- /dev/null +++ b/tools/probe/corpus/seeds/C10-preserve-guard-refused-where-references-could-be-written.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..059cea0 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -107,7 +107,7 @@ public sealed class GeneratedJsonPlan<T> + + if (typeInfo.Type == typeof(object)) return GeneratedJson.IsTheSerializers(typeInfo.Converter); + if (typeInfo.Kind == JsonTypeInfoKind.None) return false; +- if (!typeInfo.Type.IsValueType) return true; ++ if (!typeInfo.Type.IsValueType) return typeInfo.Kind != JsonTypeInfoKind.Object; + if (!visited.Add(typeInfo.Type)) return false; + + // A struct collection has no $id of its own either; its elements may (dictionary keys are names). diff --git a/tools/probe/corpus/seeds/C10-preserve-guard-value-converters-unaffected.patch b/tools/probe/corpus/seeds/C10-preserve-guard-value-converters-unaffected.patch new file mode 100644 index 00000000..cacf509a --- /dev/null +++ b/tools/probe/corpus/seeds/C10-preserve-guard-value-converters-unaffected.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..af85b03 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -63,7 +63,7 @@ public sealed class GeneratedJsonPlan<T> + _writesAsIs = !IsNumber || (handling & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) == 0; + _readsAsIs = !IsNumber || handling == JsonNumberHandling.Strict; + +- if (_direct is null && options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && MayHoldReferences(options, typeInfo, [])) ++ if ((_direct is null || !typeof(T).IsValueType) && options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && MayHoldReferences(options, typeInfo, [])) + throw new NotSupportedException( + $"A value object over {typeof(T)} cannot be written or read under {nameof(JsonSerializerOptions.ReferenceHandler)}.{nameof(ReferenceHandler.Preserve)}: " + + $"the serializer numbers references per call, and the value object's converter reaches the {typeof(T)} through a call of its own, so its \"$id\" and \"$ref\" " +@@ -106,7 +106,7 @@ public sealed class GeneratedJsonPlan<T> + return MayHoldReferences(options, options.GetTypeInfo(held), visited); + + if (typeInfo.Type == typeof(object)) return GeneratedJson.IsTheSerializers(typeInfo.Converter); +- if (typeInfo.Kind == JsonTypeInfoKind.None) return false; ++ if (typeInfo.Kind == JsonTypeInfoKind.None) return !typeInfo.Type.IsValueType; + if (!typeInfo.Type.IsValueType) return true; + if (!visited.Add(typeInfo.Type)) return false; + diff --git a/tools/probe/corpus/seeds/C11-parse-shapes-build-abstract-string-constructor.patch b/tools/probe/corpus/seeds/C11-parse-shapes-build-abstract-string-constructor.patch new file mode 100644 index 00000000..d582d085 --- /dev/null +++ b/tools/probe/corpus/seeds/C11-parse-shapes-build-abstract-string-constructor.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +index c734b98..2c9c01e 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +@@ -231,6 +231,8 @@ internal sealed partial class ValueObjectParsableAspect : TypeAspect + && method.ReturnType.IsConvertibleTo(valueType, ConversionKind.Implicit))) + return ValueParseStrategy.StaticParse; + ++ if (valueType.IsAbstract && valueType.Constructors.Any(constructor => constructor is { Parameters: [{ } text] } && TakesText(text))) return ValueParseStrategy.TypeConverter; ++ + return ValueParseStrategy.Unsupported; + } + diff --git a/tools/probe/corpus/seeds/C11-parse-shapes-build-implicit-result.patch b/tools/probe/corpus/seeds/C11-parse-shapes-build-implicit-result.patch new file mode 100644 index 00000000..6fd464f9 --- /dev/null +++ b/tools/probe/corpus/seeds/C11-parse-shapes-build-implicit-result.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +index c734b98..6be03d2 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +@@ -231,6 +231,8 @@ internal sealed partial class ValueObjectParsableAspect : TypeAspect + && method.ReturnType.IsConvertibleTo(valueType, ConversionKind.Implicit))) + return ValueParseStrategy.StaticParse; + ++ if (valueType.Methods.Any(method => method is { Name: "Parse", IsStatic: true, Parameters: [{ } text] } && TakesText(text) && !method.ReturnType.IsConvertibleTo(valueType, ConversionKind.Implicit))) return ValueParseStrategy.TypeConverter; ++ + return ValueParseStrategy.Unsupported; + } + diff --git a/tools/probe/corpus/seeds/C11-parse-shapes-build-parsing-where-it-can-be-parsed.patch b/tools/probe/corpus/seeds/C11-parse-shapes-build-parsing-where-it-can-be-parsed.patch new file mode 100644 index 00000000..232d8f1b --- /dev/null +++ b/tools/probe/corpus/seeds/C11-parse-shapes-build-parsing-where-it-can-be-parsed.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.cs +index ab5020e..d8067bb 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.cs +@@ -80,6 +80,6 @@ internal sealed partial class ValueObjectTypeConverterAspect : TypeAspect + typeof(TypeConverterAttribute), + constructorArguments: [typeConverter.Target] + ); +- builder.IntroduceAttribute(typeConverterAttribute); ++ if (builder.Target.DeclaringType is null) builder.IntroduceAttribute(typeConverterAttribute); + } + } diff --git a/tools/probe/corpus/seeds/C11-parse-shapes-build-ref-text.patch b/tools/probe/corpus/seeds/C11-parse-shapes-build-ref-text.patch new file mode 100644 index 00000000..2b95fc19 --- /dev/null +++ b/tools/probe/corpus/seeds/C11-parse-shapes-build-ref-text.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +index c734b98..cb4177b 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +@@ -231,6 +231,8 @@ internal sealed partial class ValueObjectParsableAspect : TypeAspect + && method.ReturnType.IsConvertibleTo(valueType, ConversionKind.Implicit))) + return ValueParseStrategy.StaticParse; + ++ if (valueType.Methods.Any(method => method is { Name: "Parse", IsStatic: true, Parameters: [{ RefKind: RefKind.Ref }] }) || valueType.Constructors.Any(constructor => constructor.Parameters is [{ RefKind: RefKind.Ref }])) return ValueParseStrategy.TypeConverter; ++ + return ValueParseStrategy.Unsupported; + } + diff --git a/tools/probe/corpus/seeds/C12-described-converter-own.patch b/tools/probe/corpus/seeds/C12-described-converter-own.patch new file mode 100644 index 00000000..8154b1da --- /dev/null +++ b/tools/probe/corpus/seeds/C12-described-converter-own.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/JsonSchemaDescription.cs b/src/CodoMetis.TypeKit/JsonSchemaDescription.cs +index 246ba41..9c9ad6b 100644 +--- a/src/CodoMetis.TypeKit/JsonSchemaDescription.cs ++++ b/src/CodoMetis.TypeKit/JsonSchemaDescription.cs +@@ -168,7 +168,7 @@ public sealed class JsonSchemaDescription + /// </summary> + private static JsonSchemaDescription? Describe(JsonConverter? converter, Type value, JsonSerializerOptions options) => converter switch + { +- IDescribedJsonConverter described => described.DescribeJson(converter.Type ?? value, options), ++ IDescribedJsonConverter described when value.GetCustomAttribute<JsonConverterAttribute>(inherit: false) is not null => described.DescribeJson(converter.Type ?? value, options), + not null when value.GetCustomAttribute<JsonConverterAttribute>(inherit: false)?.ConverterType == converter.GetType() => Generated.GetOrAdd(value, Generate), + _ => null, + }; diff --git a/tools/probe/corpus/seeds/C12-described-converter.patch b/tools/probe/corpus/seeds/C12-described-converter.patch new file mode 100644 index 00000000..0ca68044 --- /dev/null +++ b/tools/probe/corpus/seeds/C12-described-converter.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs +index d873ba6..a014cab 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs +@@ -94,7 +94,7 @@ internal sealed class DescribedSchemaTransformer : IOpenApiSchemaTransformer + if (Description(element, options, property: null) is not { } elementDescription) return; + + // Only what ASP.NET left out: a schema it did build is its own description of the elements. +- var missing = info.Kind == JsonTypeInfoKind.Enumerable ? schema.Items is null : schema.AdditionalProperties is null; ++ var missing = info.Kind == JsonTypeInfoKind.Enumerable ? schema.Items is not null : schema.AdditionalProperties is not null; + if (!missing) return; + + var elementSchema = new OpenApiSchema(); diff --git a/tools/probe/corpus/seeds/C12-options-descriptions.patch b/tools/probe/corpus/seeds/C12-options-descriptions.patch new file mode 100644 index 00000000..7a1dc736 --- /dev/null +++ b/tools/probe/corpus/seeds/C12-options-descriptions.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/JsonSchemaDescription.cs b/src/CodoMetis.TypeKit/JsonSchemaDescription.cs +index 246ba41..008bc05 100644 +--- a/src/CodoMetis.TypeKit/JsonSchemaDescription.cs ++++ b/src/CodoMetis.TypeKit/JsonSchemaDescription.cs +@@ -343,7 +343,7 @@ public sealed class JsonSchemaDescriptions : JsonConverterFactory + var value = Nullable.GetUnderlyingType(type) ?? type; + foreach (var converter in options.Converters) + { +- if (converter is JsonSchemaDescriptions descriptions && descriptions.Of(value) is { } described) return described; ++ if (converter is JsonSchemaDescriptions descriptions && descriptions.Of(typeof(void)) is { } described) return described; + } + + return null; diff --git a/tools/probe/corpus/seeds/C12-parameter-keeps-aspnet.patch b/tools/probe/corpus/seeds/C12-parameter-keeps-aspnet.patch new file mode 100644 index 00000000..c9cdc47c --- /dev/null +++ b/tools/probe/corpus/seeds/C12-parameter-keeps-aspnet.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs +index 072f8a9..2e2c406 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedSchemaTransformer.cs +@@ -66,7 +66,7 @@ internal sealed class DescribedSchemaTransformer : IOpenApiSchemaTransformer + + // A body is a parameter too, and so is every schema inside one: only a parameter bound from the + // route, the query, a header or a form field is text TryParse reads. +- if (context is { JsonPropertyInfo: null, ParameterDescription: { } parameter } && parameter.Source != BindingSource.Body) ++ if (context is { JsonPropertyInfo: null, ParameterDescription: { } parameter } && parameter.Source == BindingSource.Special) + { + if (Description(info.Type, options, property: null) is not null) + { diff --git a/tools/probe/corpus/seeds/C13-absent-is-default.patch b/tools/probe/corpus/seeds/C13-absent-is-default.patch new file mode 100644 index 00000000..9af3fb96 --- /dev/null +++ b/tools/probe/corpus/seeds/C13-absent-is-default.patch @@ -0,0 +1,20 @@ +diff --git a/src/CodoMetis.TypeKit/Patch.cs b/src/CodoMetis.TypeKit/Patch.cs +index 7487883..e0bd75b 100644 +--- a/src/CodoMetis.TypeKit/Patch.cs ++++ b/src/CodoMetis.TypeKit/Patch.cs +@@ -12,12 +12,12 @@ namespace CodoMetis.TypeKit; + /// <summary>What a JSON Merge Patch field says about a value.</summary> + public enum PatchState + { +- /// <summary>The field was not mentioned: the value stays as it is. The <c>default</c> of a <see cref="Patch{T}"/>.</summary> +- Absent, +- + /// <summary>The field was <c>null</c>: the value is cleared.</summary> + Null, + ++ /// <summary>The field was not mentioned: the value stays as it is. The <c>default</c> of a <see cref="Patch{T}"/>.</summary> ++ Absent, ++ + /// <summary>The field carried a value: the value is set to it.</summary> + Value + } diff --git a/tools/probe/corpus/seeds/C13-doc-optional-where-absent.patch b/tools/probe/corpus/seeds/C13-doc-optional-where-absent.patch new file mode 100644 index 00000000..72a19abc --- /dev/null +++ b/tools/probe/corpus/seeds/C13-doc-optional-where-absent.patch @@ -0,0 +1,18 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +index 850e25a..dc0cebd 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +@@ -78,11 +78,11 @@ internal sealed class PatchSchemaTransformer : IOpenApiSchemaTransformer + // The object holding patch properties: none of them is required, since absent is a state, unless + // the serializer requires it, as RespectRequiredConstructorParameters does a constructor + // parameter without a default: then a body without it is refused, and the document says so. +- if (info.Kind == JsonTypeInfoKind.Object && schema.Required is { Count: > 0 } required) ++ if (info.Kind == JsonTypeInfoKind.Object && (schema.Required ??= new HashSet<string>()) is { } required) + { + foreach (var property in info.Properties) + { +- if (IsPatch(property.PropertyType) && !property.IsRequired) required.Remove(property.Name); ++ if (IsPatch(property.PropertyType)) required.Add(property.Name); + } + } + diff --git a/tools/probe/corpus/seeds/C13-patch-required.patch b/tools/probe/corpus/seeds/C13-patch-required.patch new file mode 100644 index 00000000..17c25814 --- /dev/null +++ b/tools/probe/corpus/seeds/C13-patch-required.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +index 850e25a..112eb48 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +@@ -82,7 +82,7 @@ internal sealed class PatchSchemaTransformer : IOpenApiSchemaTransformer + { + foreach (var property in info.Properties) + { +- if (IsPatch(property.PropertyType) && !property.IsRequired) required.Remove(property.Name); ++ if (IsPatch(property.PropertyType)) required.Remove(property.Name); + } + } + diff --git a/tools/probe/corpus/seeds/C14-application-json-declared.patch b/tools/probe/corpus/seeds/C14-application-json-declared.patch new file mode 100644 index 00000000..8dab7070 --- /dev/null +++ b/tools/probe/corpus/seeds/C14-application-json-declared.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 8e936ee..be6b6a8 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -183,7 +183,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + { + var declaresProblem = problems.Any(declared => declared.ApiResponseFormats.Count == 0 + || declared.ApiResponseFormats.Any(format => string.Equals(format.MediaType, mediaType, StringComparison.OrdinalIgnoreCase))); +- if (!declaresProblem && mediaType != "application/problem+json") continue; ++ if (mediaType != "application/problem+json") continue; + + schemas ??= []; + if (media.Schema is OpenApiSchema inline) schemas.Add(inline); diff --git a/tools/probe/corpus/seeds/C14-ascending-with-declared.patch b/tools/probe/corpus/seeds/C14-ascending-with-declared.patch new file mode 100644 index 00000000..39abccb3 --- /dev/null +++ b/tools/probe/corpus/seeds/C14-ascending-with-declared.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 8e936ee..691cb3e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -137,7 +137,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + touched = true; + } + +- if (touched) Ascending(operation.Responses!); ++ if (touched && operation.Responses!.Count == 0) Ascending(operation.Responses!); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/C14-declared-describes-fault.patch b/tools/probe/corpus/seeds/C14-declared-describes-fault.patch new file mode 100644 index 00000000..80c276aa --- /dev/null +++ b/tools/probe/corpus/seeds/C14-declared-describes-fault.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 8e936ee..6bd7f14 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -120,7 +120,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + { + problem ??= await ProblemSchemaAsync(context, cancellationToken); + touched = true; +- foreach (var schema in schemas) WithFault(schema, Qualified(context)); ++ foreach (var schema in schemas.Take(0)) WithFault(schema, Qualified(context)); + if (string.IsNullOrEmpty(response.Description) || response.Description == ReasonPhrases.GetReasonPhrase(status)) + response.Description = Description(status, types); + } diff --git a/tools/probe/corpus/seeds/C14-declared-problem.patch b/tools/probe/corpus/seeds/C14-declared-problem.patch new file mode 100644 index 00000000..adc22b2b --- /dev/null +++ b/tools/probe/corpus/seeds/C14-declared-problem.patch @@ -0,0 +1,14 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 8e936ee..4f1c71d 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -121,8 +121,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + problem ??= await ProblemSchemaAsync(context, cancellationToken); + touched = true; + foreach (var schema in schemas) WithFault(schema, Qualified(context)); +- if (string.IsNullOrEmpty(response.Description) || response.Description == ReasonPhrases.GetReasonPhrase(status)) +- response.Description = Description(status, types); ++ + } + + continue; diff --git a/tools/probe/corpus/seeds/C15-mvc-fault-status-doc.patch b/tools/probe/corpus/seeds/C15-mvc-fault-status-doc.patch new file mode 100644 index 00000000..448057da --- /dev/null +++ b/tools/probe/corpus/seeds/C15-mvc-fault-status-doc.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +index 475e121..db05bf3 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponsesOperationTransformer.cs +@@ -74,7 +74,7 @@ internal sealed class FaultResponsesOperationTransformer : IOpenApiOperationTran + { + foreach (var endpoint in endpoints.Endpoints) + { +- if (ReferenceEquals(endpoint.Metadata.GetMetadata<ActionDescriptor>(), action)) return [.. endpoint.Metadata]; ++ if (ReferenceEquals(endpoint.Metadata.GetMetadata<ActionDescriptor>(), action)) return [.. action.EndpointMetadata]; + } + } + diff --git a/tools/probe/corpus/seeds/C15-status-per-scope.patch b/tools/probe/corpus/seeds/C15-status-per-scope.patch new file mode 100644 index 00000000..22e2e058 --- /dev/null +++ b/tools/probe/corpus/seeds/C15-status-per-scope.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..3acdab7 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -160,7 +160,7 @@ internal sealed class FaultResponses<TFault> where TFault : notnull + Type.Members.Where(fault => FaultExclusions.Excluding(endpointMetadata, fault, Type) is null); + + private int StatusOf(TFault fault, IReadOnlyList<object> endpointMetadata) => +- FaultStatuses.Override(endpointMetadata, fault, Type) ?? _answers[fault].Status; ++ FaultStatuses.Override(endpointMetadata.Take(0).ToList(), fault, Type) ?? _answers[fault].Status; + + /// <summary> + /// How the host answers <typeparamref name="TFault"/>: its own mapping, or else the default for every diff --git a/tools/probe/corpus/seeds/C15-without-faults-doc.patch b/tools/probe/corpus/seeds/C15-without-faults-doc.patch new file mode 100644 index 00000000..28e340aa --- /dev/null +++ b/tools/probe/corpus/seeds/C15-without-faults-doc.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +index 45c20b0..60bc81e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/FaultResponses.cs +@@ -157,7 +157,7 @@ internal sealed class FaultResponses<TFault> where TFault : notnull + + /// <summary>The members an endpoint answers with: every member, but those its metadata leaves out.</summary> + public IEnumerable<TFault> AnsweredBy(IReadOnlyList<object> endpointMetadata) => +- Type.Members.Where(fault => FaultExclusions.Excluding(endpointMetadata, fault, Type) is null); ++ Type.Members; + + private int StatusOf(TFault fault, IReadOnlyList<object> endpointMetadata) => + FaultStatuses.Override(endpointMetadata, fault, Type) ?? _answers[fault].Status; diff --git a/tools/probe/corpus/seeds/C16-error-names-descriptions.patch b/tools/probe/corpus/seeds/C16-error-names-descriptions.patch new file mode 100644 index 00000000..48d773d1 --- /dev/null +++ b/tools/probe/corpus/seeds/C16-error-names-descriptions.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +index d8b04d4..2c78b7e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +@@ -45,7 +45,7 @@ internal static class DescribedOptionsAgreement + + throw new InvalidOperationException( + $"The OpenAPI document is built from the minimal-API JSON options (ConfigureHttpJsonOptions), and the controllers write with MVC's " +- + $"(AddControllers().AddJsonOptions), and the two describe '{type}' differently: the minimal-API options as {Say(http)}, MVC's as {Say(controllers)}. " ++ + $"(AddControllers().AddJsonOptions), and the two describe '{type}' differently: the minimal-API options as {Say(null)}, MVC's as {Say(null)}. " + + "Register the same converters and descriptions on both, in the same order, or the document describes JSON the controllers do not write."); + } + diff --git a/tools/probe/corpus/seeds/C16-minimal-host-builds.patch b/tools/probe/corpus/seeds/C16-minimal-host-builds.patch new file mode 100644 index 00000000..dd465222 --- /dev/null +++ b/tools/probe/corpus/seeds/C16-minimal-host-builds.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +index d8b04d4..39572de 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +@@ -53,7 +53,7 @@ internal static class DescribedOptionsAgreement + private static StrongBox<JsonSerializerOptions?> MvcOptions(IServiceProvider services) + { + var actions = services.GetService<IActionDescriptorCollectionProvider>(); +- if (actions is null || !actions.ActionDescriptors.Items.OfType<ControllerActionDescriptor>().Any()) return new(null); ++ if (actions is null || !actions.ActionDescriptors.Items.OfType<ControllerActionDescriptor>().Any()) return new(services.GetService<IOptions<MvcJsonOptions>>()?.Value.JsonSerializerOptions); + + return new(services.GetRequiredService<IOptions<MvcJsonOptions>>().Value.JsonSerializerOptions); + } diff --git a/tools/probe/corpus/seeds/C16-options-agreement.patch b/tools/probe/corpus/seeds/C16-options-agreement.patch new file mode 100644 index 00000000..fcbe69e3 --- /dev/null +++ b/tools/probe/corpus/seeds/C16-options-agreement.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +index d8b04d4..b333446 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +@@ -41,7 +41,7 @@ internal static class DescribedOptionsAgreement + + var http = JsonSchemaDescription.For(type, document); + var controllers = JsonSchemaDescription.For(type, mvc); +- if (Same(http, controllers)) return; ++ if (controllers is null || Same(http, controllers)) return; + + throw new InvalidOperationException( + $"The OpenAPI document is built from the minimal-API JSON options (ConfigureHttpJsonOptions), and the controllers write with MVC's " diff --git a/tools/probe/corpus/seeds/C16-patch-held-refused.patch b/tools/probe/corpus/seeds/C16-patch-held-refused.patch new file mode 100644 index 00000000..01bb888c --- /dev/null +++ b/tools/probe/corpus/seeds/C16-patch-held-refused.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +index 850e25a..0af8e31 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/PatchSchemaTransformer.cs +@@ -93,7 +93,6 @@ internal sealed class PatchSchemaTransformer : IOpenApiSchemaTransformer + var argument = info.Type.GetGenericArguments()[0]; + // The argument is held by the patch alone, where ASP.NET asks no transformer of it: a document and a + // controller that describe it differently are told apart here. +- DescribedOptionsAgreement.ThrowIfDisagreeing(context.ApplicationServices, argument, info.Options); + // One the options write with a converter that says nothing is what ASP.NET writes for any such: {}. + var inner = DescribedSchemaTransformer.Description(argument, info.Options, property: null) is { } description + ? await DescribedSchemaTransformer.SchemaAsync(description, argument, context, cancellationToken) diff --git a/tools/probe/corpus/seeds/C16-same-converters-build.patch b/tools/probe/corpus/seeds/C16-same-converters-build.patch new file mode 100644 index 00000000..b6dc817e --- /dev/null +++ b/tools/probe/corpus/seeds/C16-same-converters-build.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +index d8b04d4..967f60a 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/DescribedOptionsAgreement.cs +@@ -41,7 +41,7 @@ internal static class DescribedOptionsAgreement + + var http = JsonSchemaDescription.For(type, document); + var controllers = JsonSchemaDescription.For(type, mvc); +- if (Same(http, controllers)) return; ++ if (Same(http, controllers) && document.PropertyNamingPolicy == mvc.PropertyNamingPolicy && document.NumberHandling == mvc.NumberHandling) return; + + throw new InvalidOperationException( + $"The OpenAPI document is built from the minimal-API JSON options (ConfigureHttpJsonOptions), and the controllers write with MVC's " diff --git a/tools/probe/corpus/seeds/C17-cmtk0014-a-cast-naming-no-member.patch b/tools/probe/corpus/seeds/C17-cmtk0014-a-cast-naming-no-member.patch new file mode 100644 index 00000000..f7c4fce5 --- /dev/null +++ b/tools/probe/corpus/seeds/C17-cmtk0014-a-cast-naming-no-member.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +index 7265ea3..3967d2b 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +@@ -50,7 +50,7 @@ public sealed class UndefinedEnumValueAnalyzer : DiagnosticAnalyzer + + /// <summary>The operations that can be a constant of an enum, folded or not; each is judged only where no other contains it.</summary> + private static readonly ImmutableArray<OperationKind> Kinds = +- [OperationKind.Conversion, OperationKind.DefaultValue, OperationKind.Binary, OperationKind.Unary, OperationKind.Conditional]; ++ [OperationKind.DefaultValue, OperationKind.Binary, OperationKind.Unary, OperationKind.Conditional]; + + /// <inheritdoc/> + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule]; diff --git a/tools/probe/corpus/seeds/C17-cmtk0014-a-member-is-silent.patch b/tools/probe/corpus/seeds/C17-cmtk0014-a-member-is-silent.patch new file mode 100644 index 00000000..2cb967ad --- /dev/null +++ b/tools/probe/corpus/seeds/C17-cmtk0014-a-member-is-silent.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +index 7265ea3..1750073 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +@@ -105,7 +105,7 @@ public sealed class UndefinedEnumValueAnalyzer : DiagnosticAnalyzer + if (operation.Type is not INamedTypeSymbol type || !enums.TryGetValue(type, out var known)) return null; + if (!operation.ConstantValue.HasValue || operation.ConstantValue.Value is not { } value) return null; + +- return known.Members.Contains(value) ? null : (type, known.Consequence); ++ return known.Members.Contains(value) && operation is not IConversionOperation { IsImplicit: false } ? null : (type, known.Consequence); + } + + /// <summary>Whether <paramref name="operation"/> is an operand of a larger constant of an enum, which is judged in its place.</summary> diff --git a/tools/probe/corpus/seeds/C17-cmtk0014-constructor-accessibility-too.patch b/tools/probe/corpus/seeds/C17-cmtk0014-constructor-accessibility-too.patch new file mode 100644 index 00000000..0451dff2 --- /dev/null +++ b/tools/probe/corpus/seeds/C17-cmtk0014-constructor-accessibility-too.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +index 7265ea3..52c2a37 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +@@ -45,7 +45,6 @@ public sealed class UndefinedEnumValueAnalyzer : DiagnosticAnalyzer + private static readonly (string MetadataName, string Consequence)[] Enums = + [ + ("CodoMetis.TypeKit.ClosedSets.FaultKind", "a host refuses a fault set's member of no kind where it maps the set, when it starts"), +- ("CodoMetis.TypeKit.ConstructorAccessibility", "CMTK0011 reads it as public"), + ]; + + /// <summary>The operations that can be a constant of an enum, folded or not; each is judged only where no other contains it.</summary> diff --git a/tools/probe/corpus/seeds/C17-cmtk0014-default-and-zero.patch b/tools/probe/corpus/seeds/C17-cmtk0014-default-and-zero.patch new file mode 100644 index 00000000..0f1cb0fb --- /dev/null +++ b/tools/probe/corpus/seeds/C17-cmtk0014-default-and-zero.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +index 7265ea3..cf1a34a 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +@@ -105,7 +105,7 @@ public sealed class UndefinedEnumValueAnalyzer : DiagnosticAnalyzer + if (operation.Type is not INamedTypeSymbol type || !enums.TryGetValue(type, out var known)) return null; + if (!operation.ConstantValue.HasValue || operation.ConstantValue.Value is not { } value) return null; + +- return known.Members.Contains(value) ? null : (type, known.Consequence); ++ return known.Members.Contains(value) || value is 0 ? null : (type, known.Consequence); + } + + /// <summary>Whether <paramref name="operation"/> is an operand of a larger constant of an enum, which is judged in its place.</summary> diff --git a/tools/probe/corpus/seeds/C17-cmtk0014-reported-where-written.patch b/tools/probe/corpus/seeds/C17-cmtk0014-reported-where-written.patch new file mode 100644 index 00000000..afb80f41 --- /dev/null +++ b/tools/probe/corpus/seeds/C17-cmtk0014-reported-where-written.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +index 7265ea3..f26fec1 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/UndefinedEnumValueAnalyzer.cs +@@ -93,7 +93,7 @@ public sealed class UndefinedEnumValueAnalyzer : DiagnosticAnalyzer + + context.Report(Diagnostic.Create( + Rule, +- operation.Syntax.GetLocation(), ++ (operation.Syntax.Parent ?? operation.Syntax).GetLocation(), + operation.Syntax.ToString(), + enumeration.Type.Name, + enumeration.Consequence)); diff --git a/tools/probe/corpus/seeds/C18-cmtk1023-a-warning-and-generated.patch b/tools/probe/corpus/seeds/C18-cmtk1023-a-warning-and-generated.patch new file mode 100644 index 00000000..cf9e979a --- /dev/null +++ b/tools/probe/corpus/seeds/C18-cmtk1023-a-warning-and-generated.patch @@ -0,0 +1,11 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +index 6fd2f8d..9a8f738 100644 +--- a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs ++++ b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +@@ -100,5 +100,5 @@ internal static class AspectDiagnostics + /// longer calls (decision 75). + /// </summary> + public static readonly DiagnosticDefinition<(INamedType Type, string Members)> InheritedEqualityNotUsed = +- new("CMTK1023", Severity.Warning, "'{0}' inherits {1}, which its generated equality does not call: a value object's equality is its wrapped value's, as its ordering, its JSON and its EF Core column are, and a base's would make values equal that sort apart. Remove them from the base, or derive from a record without them; to make values that differ only in form equal, normalise them in Create." + SeeBuildErrors); ++ new("CMTK1023", Severity.Info, "'{0}' inherits {1}, which its generated equality does not call: a value object's equality is its wrapped value's, as its ordering, its JSON and its EF Core column are, and a base's would make values equal that sort apart. Remove them from the base, or derive from a record without them; to make values that differ only in form equal, normalise them in Create." + SeeBuildErrors); + } diff --git a/tools/probe/corpus/seeds/C18-cmtk1023-generated-equality-never-calls-the-base.patch b/tools/probe/corpus/seeds/C18-cmtk1023-generated-equality-never-calls-the-base.patch new file mode 100644 index 00000000..9be9b8f1 --- /dev/null +++ b/tools/probe/corpus/seeds/C18-cmtk1023-generated-equality-never-calls-the-base.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +index 07a7ecb..1bc5231 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +@@ -58,7 +58,7 @@ internal sealed partial class ValueObjectContractAspect + [Template] + public int GetHashCodeTemplate([CompileTime] INamedType valueType) => + (int)ExpressionFactory.Parse( +- $"global::System.Collections.Generic.EqualityComparer<{ValueObjectTypes.SourceName(valueType)}>.Default.GetHashCode(this._value!)" ++ $"global::System.HashCode.Combine(base.GetHashCode(), global::System.Collections.Generic.EqualityComparer<{ValueObjectTypes.SourceName(valueType)}>.Default.GetHashCode(this._value!))" + ).Value!; + + /// <summary><c>Create</c> with the fault carried into the throw.</summary> diff --git a/tools/probe/corpus/seeds/C18-cmtk1023-names-each-base-member.patch b/tools/probe/corpus/seeds/C18-cmtk1023-names-each-base-member.patch new file mode 100644 index 00000000..006ca6b3 --- /dev/null +++ b/tools/probe/corpus/seeds/C18-cmtk1023-names-each-base-member.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs +index cb0cbae..f713c7e 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs +@@ -436,7 +436,7 @@ internal static class ValueObjectDeclaration + .. SelfAndBases(target).Skip(1).SelectMany(type => type.Methods + .Where(method => method is { IsStatic: false } && IsHandWritten(method) + && (method.IsExplicitInterfaceImplementation +- ? method.ExplicitInterfaceImplementations.Any(implemented => implemented.DeclaringType.Definition.Equals(equatable)) ++ ? false + : (method is { Name: nameof(Equals), Parameters: [{ } other] } && other.Type.Equals(type)) + || method is { Name: nameof(GetHashCode), Parameters.Count: 0 })) + .Select(method => $"{Describe(method)} in its base type {type.ToDisplayString()}")) diff --git a/tools/probe/corpus/seeds/C19-cmtk0010-kinds-a-pair-once-at-the-later-argument.patch b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-a-pair-once-at-the-later-argument.patch new file mode 100644 index 00000000..8194f01c --- /dev/null +++ b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-a-pair-once-at-the-later-argument.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +index 5c1fd7e..703f84c 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +@@ -518,7 +518,7 @@ public sealed class TypeArgumentConstraintAnalyzer : DiagnosticAnalyzer + { + yield return Diagnostic.Create( + Rule, +- locationOf(later), ++ locationOf(earlier), + arguments[later].Argument.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + arguments[later].Parameter.Name, + Owner(arguments[later].Owner), diff --git a/tools/probe/corpus/seeds/C19-cmtk0010-kinds-flags-enum-and-read-only-struct.patch b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-flags-enum-and-read-only-struct.patch new file mode 100644 index 00000000..db7b18fc --- /dev/null +++ b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-flags-enum-and-read-only-struct.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +index 5c1fd7e..2f1a554 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +@@ -812,7 +812,7 @@ public sealed class TypeArgumentConstraintAnalyzer : DiagnosticAnalyzer + TypeArgumentConstraint.ValueObject => argument.TypeKind is not TypeKind.Interface && symbols.FindMarker(argument, out _) is not null, + TypeArgumentConstraint.ClosedSet => symbols.FindClosedSetMarker(argument) is not null, + TypeArgumentConstraint.FlagsEnum => symbols.IsFlagsEnum(argument), +- TypeArgumentConstraint.ReadOnlyStruct => argument is INamedTypeSymbol { TypeKind: TypeKind.Struct, IsReadOnly: true }, ++ TypeArgumentConstraint.ReadOnlyStruct => argument is INamedTypeSymbol { TypeKind: TypeKind.Struct }, + _ => argument.TypeKind is TypeKind.Array or TypeKind.Enum or TypeKind.Delegate or TypeKind.Dynamic + || argument is INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct, IsAbstract: false, IsStatic: false }, + }; diff --git a/tools/probe/corpus/seeds/C19-cmtk0010-kinds-interface-and-unsealed.patch b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-interface-and-unsealed.patch new file mode 100644 index 00000000..13b21c27 --- /dev/null +++ b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-interface-and-unsealed.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +index 5c1fd7e..a7c9f49 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +@@ -808,7 +808,7 @@ public sealed class TypeArgumentConstraintAnalyzer : DiagnosticAnalyzer + TypeArgumentConstraint.Interface => argument.TypeKind is TypeKind.Interface, + // dynamic is object, a class that can be derived from, as [Concrete] takes it. The abstract bases nothing + // derives from (Enum, Delegate, MulticastDelegate, ValueType, Array) are taken too until 3.0.0 (decision 34). +- TypeArgumentConstraint.Unsealed => argument.TypeKind is TypeKind.Dynamic || argument is INamedTypeSymbol { TypeKind: TypeKind.Class, IsSealed: false, IsStatic: false }, ++ TypeArgumentConstraint.Unsealed => argument.TypeKind is TypeKind.Dynamic || argument is INamedTypeSymbol { TypeKind: TypeKind.Class, IsStatic: false }, + TypeArgumentConstraint.ValueObject => argument.TypeKind is not TypeKind.Interface && symbols.FindMarker(argument, out _) is not null, + TypeArgumentConstraint.ClosedSet => symbols.FindClosedSetMarker(argument) is not null, + TypeArgumentConstraint.FlagsEnum => symbols.IsFlagsEnum(argument), diff --git a/tools/probe/corpus/seeds/C19-cmtk0010-kinds-methods-the-compiler-calls.patch b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-methods-the-compiler-calls.patch new file mode 100644 index 00000000..8d87ff1c --- /dev/null +++ b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-methods-the-compiler-calls.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +index 5c1fd7e..c297b6a 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +@@ -161,7 +161,6 @@ public sealed class TypeArgumentConstraintAnalyzer : DiagnosticAnalyzer + + if (construct.MethodKind == MethodKind.Ordinary) + { +- yield return (construct, collection); + break; + } + diff --git a/tools/probe/corpus/seeds/C19-cmtk0010-kinds-value-object-and-closed-set.patch b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-value-object-and-closed-set.patch new file mode 100644 index 00000000..58d99d22 --- /dev/null +++ b/tools/probe/corpus/seeds/C19-cmtk0010-kinds-value-object-and-closed-set.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +index 5c1fd7e..bce7547 100644 +--- a/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs ++++ b/src/CodoMetis.TypeKit.Analyzers/TypeArgumentConstraintAnalyzer.cs +@@ -809,7 +809,7 @@ public sealed class TypeArgumentConstraintAnalyzer : DiagnosticAnalyzer + // dynamic is object, a class that can be derived from, as [Concrete] takes it. The abstract bases nothing + // derives from (Enum, Delegate, MulticastDelegate, ValueType, Array) are taken too until 3.0.0 (decision 34). + TypeArgumentConstraint.Unsealed => argument.TypeKind is TypeKind.Dynamic || argument is INamedTypeSymbol { TypeKind: TypeKind.Class, IsSealed: false, IsStatic: false }, +- TypeArgumentConstraint.ValueObject => argument.TypeKind is not TypeKind.Interface && symbols.FindMarker(argument, out _) is not null, ++ TypeArgumentConstraint.ValueObject => argument.TypeKind is not TypeKind.Interface, + TypeArgumentConstraint.ClosedSet => symbols.FindClosedSetMarker(argument) is not null, + TypeArgumentConstraint.FlagsEnum => symbols.IsFlagsEnum(argument), + TypeArgumentConstraint.ReadOnlyStruct => argument is INamedTypeSymbol { TypeKind: TypeKind.Struct, IsReadOnly: true }, diff --git a/tools/probe/corpus/seeds/C20-testing-shouldhold-a-failure-says-the-outcome.patch b/tools/probe/corpus/seeds/C20-testing-shouldhold-a-failure-says-the-outcome.patch new file mode 100644 index 00000000..bdd6a3de --- /dev/null +++ b/tools/probe/corpus/seeds/C20-testing-shouldhold-a-failure-says-the-outcome.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +index 6b825b3..443a910 100644 +--- a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs ++++ b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +@@ -68,6 +68,6 @@ internal static class HeldFaults + var held = read(); + return held is Fault<TFault> asked && EqualityComparer<TFault>.Default.Equals(asked.Reason, reason) + ? asked +- : throw new TypeKitAssertionException($"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} is the fault {held}."); ++ : throw new TypeKitAssertionException($"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} holds another."); + } + } diff --git a/tools/probe/corpus/seeds/C20-testing-shouldhold-fails-unless-it-holds-that-reason.patch b/tools/probe/corpus/seeds/C20-testing-shouldhold-fails-unless-it-holds-that-reason.patch new file mode 100644 index 00000000..b54b85ea --- /dev/null +++ b/tools/probe/corpus/seeds/C20-testing-shouldhold-fails-unless-it-holds-that-reason.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +index 6b825b3..c09f2da 100644 +--- a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs ++++ b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +@@ -66,7 +66,7 @@ internal static class HeldFaults + $"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} is uninitialized: a default instance, which holds no fault."); + + var held = read(); +- return held is Fault<TFault> asked && EqualityComparer<TFault>.Default.Equals(asked.Reason, reason) ++ return held is Fault<TFault> asked + ? asked + : throw new TypeKitAssertionException($"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} is the fault {held}."); + } diff --git a/tools/probe/corpus/seeds/C20-testing-shouldhold-hands-back-detail-and-rule.patch b/tools/probe/corpus/seeds/C20-testing-shouldhold-hands-back-detail-and-rule.patch new file mode 100644 index 00000000..f1281a45 --- /dev/null +++ b/tools/probe/corpus/seeds/C20-testing-shouldhold-hands-back-detail-and-rule.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +index 6b825b3..e02bba5 100644 +--- a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs ++++ b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +@@ -67,7 +67,7 @@ internal static class HeldFaults + + var held = read(); + return held is Fault<TFault> asked && EqualityComparer<TFault>.Default.Equals(asked.Reason, reason) +- ? asked ++ ? Fault.Of(asked.Reason) + : throw new TypeKitAssertionException($"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} is the fault {held}."); + } + } diff --git a/tools/probe/corpus/seeds/C20-testing-shouldhold-uninitialized-fails.patch b/tools/probe/corpus/seeds/C20-testing-shouldhold-uninitialized-fails.patch new file mode 100644 index 00000000..3548a2e2 --- /dev/null +++ b/tools/probe/corpus/seeds/C20-testing-shouldhold-uninitialized-fails.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +index 6b825b3..61ae1a6 100644 +--- a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs ++++ b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +@@ -61,7 +61,7 @@ internal static class HeldFaults + /// <param name="read">The held fault, boxed: the composed fault's types are told apart only at run time.</param> + public static Fault<TFault> Expect<TComposed, TFault>(TFault reason, bool uninitialized, Func<object> read) where TFault : notnull + { +- if (uninitialized) ++ if (uninitialized && reason is null) + throw new TypeKitAssertionException( + $"Expected the fault {reason}, but the {Outcomes.Name<TComposed>()} is uninitialized: a default instance, which holds no fault."); + diff --git a/tools/probe/corpus/seeds/C20-testing-shouldhold-whichever-of-its-types.patch b/tools/probe/corpus/seeds/C20-testing-shouldhold-whichever-of-its-types.patch new file mode 100644 index 00000000..9be5f520 --- /dev/null +++ b/tools/probe/corpus/seeds/C20-testing-shouldhold-whichever-of-its-types.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +index 6b825b3..9839131 100644 +--- a/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs ++++ b/src/CodoMetis.TypeKit.Testing/FaultAssertions.cs +@@ -23,7 +23,7 @@ public static class FaultAssertions + /// <returns>The held fault, its detail and rule intact.</returns> + /// <exception cref="TypeKitAssertionException">The fault is another, of either type, or uninitialized.</exception> + public static Fault<T2> ShouldHold<[Distinct] T1, [Distinct] T2>(this Fault<T1, T2> fault, T2 reason) where T1 : notnull where T2 : notnull => +- HeldFaults.Expect<Fault<T1, T2>, T2>(reason, Uninitialized(fault), () => fault.Match<object>(held => held, held => held)); ++ HeldFaults.Expect<Fault<T1, T2>, T2>(reason, Uninitialized(fault), () => fault.Match<object>(held => held, held => held.Reason)); + + /// <summary>The fault a composed fault must hold, of its first type.</summary> + /// <param name="fault">The fault under test.</param> +@@ -47,7 +47,7 @@ public static class FaultAssertions + /// <returns>The held fault, its detail and rule intact.</returns> + /// <exception cref="TypeKitAssertionException">The fault is another, of any of the types, or uninitialized.</exception> + public static Fault<T3> ShouldHold<[Distinct] T1, [Distinct] T2, [Distinct] T3>(this Fault<T1, T2, T3> fault, T3 reason) where T1 : notnull where T2 : notnull where T3 : notnull => +- HeldFaults.Expect<Fault<T1, T2, T3>, T3>(reason, Uninitialized(fault), () => fault.Match<object>(held => held, held => held, held => held)); ++ HeldFaults.Expect<Fault<T1, T2, T3>, T3>(reason, Uninitialized(fault), () => fault.Match<object>(held => held, held => held, held => held.Reason)); + + private static bool Uninitialized<TComposed>(TComposed fault) where TComposed : struct => + EqualityComparer<TComposed>.Default.Equals(fault, default); diff --git a/tools/probe/corpus/seeds/C21-openapi-schema-parity-container-elements.patch b/tools/probe/corpus/seeds/C21-openapi-schema-parity-container-elements.patch new file mode 100644 index 00000000..a7da6f5e --- /dev/null +++ b/tools/probe/corpus/seeds/C21-openapi-schema-parity-container-elements.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +index 4c226cd..5b478a8 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +@@ -108,7 +108,7 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + + // Only what ASP.NET left out: a schema it did build is its own description of the elements. + if (info.Kind == JsonTypeInfoKind.Enumerable && schema.Items is null) +- schema.Items = await ElementSchemaAsync(element, context, cancellationToken); ++ return; + else if (info.Kind == JsonTypeInfoKind.Dictionary && schema.AdditionalProperties is null) + schema.AdditionalProperties = await ElementSchemaAsync(element, context, cancellationToken); + } diff --git a/tools/probe/corpus/seeds/C21-openapi-schema-parity-dictionary-key.patch b/tools/probe/corpus/seeds/C21-openapi-schema-parity-dictionary-key.patch new file mode 100644 index 00000000..2ff5246c --- /dev/null +++ b/tools/probe/corpus/seeds/C21-openapi-schema-parity-dictionary-key.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +index 4c226cd..98cffde 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +@@ -104,6 +104,12 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + return; + } + ++ if (info.Kind == JsonTypeInfoKind.Dictionary && info.KeyType is { } key && ValueObjectTypes.IsValueObject(key)) ++ { ++ schema.AdditionalProperties = await ElementSchemaAsync(key, context, cancellationToken); ++ return; ++ } ++ + if (info.ElementType is not { } element || !ValueObjectTypes.IsValueObject(element) || DescribedSchemaTransformer.Replaced(element, info.Options)) return; + + // Only what ASP.NET left out: a schema it did build is its own description of the elements. diff --git a/tools/probe/corpus/seeds/C21-openapi-schema-parity-every-document-version.patch b/tools/probe/corpus/seeds/C21-openapi-schema-parity-every-document-version.patch new file mode 100644 index 00000000..a24446e2 --- /dev/null +++ b/tools/probe/corpus/seeds/C21-openapi-schema-parity-every-document-version.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +index b415acf..b99800e 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +@@ -267,6 +267,7 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + if (position == NullablePosition.None) + { + schema.FillFrom(wrappedSchema); ++ if (DescribedSchemaTransformer.Version(context) < OpenApiSpecVersion.OpenApi3_1) schema.Format = null; + } + else if (SchemaReferenceId(context.JsonTypeInfo.Options.GetTypeInfo(wrapped), context) is not null) + { diff --git a/tools/probe/corpus/seeds/C21-openapi-schema-parity-nullable-admits-null-as-the-wrapped-does.patch b/tools/probe/corpus/seeds/C21-openapi-schema-parity-nullable-admits-null-as-the-wrapped-does.patch new file mode 100644 index 00000000..688f8828 --- /dev/null +++ b/tools/probe/corpus/seeds/C21-openapi-schema-parity-nullable-admits-null-as-the-wrapped-does.patch @@ -0,0 +1,28 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +index 6078f79..2493ef9 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +@@ -108,10 +108,8 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + if (component && nullableProperty) MarkNullableProperty(schema); + + var position = component ? NullablePosition.None +- : nullableProperty ? NullablePosition.Property +- : Nullable.GetUnderlyingType(info.Type) is null || !wrapped.IsValueType ? NullablePosition.None +- : IsParameterOutsideTheBody(context) ? NullablePosition.ParameterOutsideTheBody +- : NullablePosition.Root; ++ : Nullable.GetUnderlyingType(info.Type) is not null || nullableProperty ? NullablePosition.Property ++ : NullablePosition.None; + + await DescribeAsync(schema, info.Type, wrapped, outside, position, context, cancellationToken); + return; +@@ -129,9 +127,7 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + // MVC hands a value object bound from the body here too, as the string it would parse. There + // ASP.NET's exporter admits null in the schema of the Nullable<T> of a wrapped value type, inside + // the null-or ASP.NET puts around a nullable body, and outside the body among its values alone. +- var position = Nullable.GetUnderlyingType(valueObject) is null || !parameterWrapped.IsValueType ? NullablePosition.None +- : parameter.Source == BindingSource.Body ? NullablePosition.Root +- : NullablePosition.ParameterOutsideTheBody; ++ var position = NullablePosition.None; + + // ASP.NET's placeholder for any TryParse-bound parameter; the wrapped type says what it is. + schema.Type = null; diff --git a/tools/probe/corpus/seeds/C21-openapi-schema-parity-the-underlying-types-schema.patch b/tools/probe/corpus/seeds/C21-openapi-schema-parity-the-underlying-types-schema.patch new file mode 100644 index 00000000..9f4d1e88 --- /dev/null +++ b/tools/probe/corpus/seeds/C21-openapi-schema-parity-the-underlying-types-schema.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +index b415acf..9b7e9e0 100644 +--- a/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs ++++ b/src/CodoMetis.TypeKit.AspNetCore/ValueObjectSchemaTransformer.cs +@@ -267,6 +267,7 @@ internal sealed class ValueObjectSchemaTransformer : IOpenApiSchemaTransformer + if (position == NullablePosition.None) + { + schema.FillFrom(wrappedSchema); ++ schema.Format = null; + } + else if (SchemaReferenceId(context.JsonTypeInfo.Options.GetTypeInfo(wrapped), context) is not null) + { diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-abstract.patch b/tools/probe/corpus/seeds/C22-generator-refusals-abstract.patch new file mode 100644 index 00000000..2d33feb8 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-abstract.patch @@ -0,0 +1,16 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs +index 6ceba7f..ba99b2d 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs +@@ -40,11 +40,6 @@ internal sealed class ValueObjectFabric : TransitiveProjectFabric + CompanionClass.NameOwner(type), + ValueObjectTypes.WrappedValueObjectRefusal(type))); + +- // An abstract type gets no aspect, so it is refused here, where it is selected. +- amender.SelectTypes(includeNestedTypes: true) +- .Where(ValueObjectTypes.IsAbstractValueObject) +- .ReportDiagnostic(type => AspectDiagnostics.AbstractValueObject.WithArguments(type)); +- + amender.SelectTypes(includeNestedTypes: true) + .Where(ClosedSetTypes.IsClosedSet) + .AddAspect(_ => new ClosedSetAspect()); diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-closed-set-named-as-generated-member.patch b/tools/probe/corpus/seeds/C22-generator-refusals-closed-set-named-as-generated-member.patch new file mode 100644 index 00000000..c5281d93 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-closed-set-named-as-generated-member.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ClosedSetAspect.cs b/src/CodoMetis.TypeKit.Generators/ClosedSetAspect.cs +index bd675fb..5601d62 100644 +--- a/src/CodoMetis.TypeKit.Generators/ClosedSetAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ClosedSetAspect.cs +@@ -72,7 +72,6 @@ internal sealed partial class ClosedSetAspect : TypeAspect + + // A set named as a generated member failed inside the generated code (LAMA0611, CS0542: a + // member cannot have the name of its type), naming nothing the user wrote. +- if (generatedNames.Contains(target.Name)) problems.Add(ClosedSetNamedAsGeneratedMember.WithArguments(target)); + + foreach (var member in members.Where(member => generatedNames.Contains(member.Name))) + problems.Add(ClosedSetMemberNameTaken.WithArguments((target, member.Name, "has the name of a member the generators introduce"))); diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-equality-operator.patch b/tools/probe/corpus/seeds/C22-generator-refusals-equality-operator.patch new file mode 100644 index 00000000..f59ca69f --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-equality-operator.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs +index cb0cbae..7d2bf7f 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectDeclaration.cs +@@ -411,10 +411,6 @@ internal static class ValueObjectDeclaration + ? method.ExplicitInterfaceImplementations.Any(implemented => implemented.DeclaringType.Definition.Equals(equatable)) + : (method is { Name: nameof(Equals), Parameters: [{ } other] } && other.Type.Equals(target)) + || method is { Name: nameof(GetHashCode), Parameters.Count: 0 })) +- .Select(Describe), +- .. target.Methods +- .Where(method => method is { IsStatic: true, IsImplicitlyDeclared: false, IsExplicitInterfaceImplementation: false, OperatorKind: OperatorKind.Equality or OperatorKind.Inequality, Parameters: [{ } left, { } right] } +- && left.Type.ToNonNullable().Equals(target) && right.Type.ToNonNullable().Equals(target)) + .Select(Describe) + ]; + } diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-every-error-links-the-table.patch b/tools/probe/corpus/seeds/C22-generator-refusals-every-error-links-the-table.patch new file mode 100644 index 00000000..02cdd7dd --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-every-error-links-the-table.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +index 6fd2f8d..6531cc1 100644 +--- a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs ++++ b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +@@ -34,7 +34,7 @@ internal static class AspectDiagnostics + new("CMTK1004", Severity.Error, "'{0}' implements {1}, whose first type argument must be '{0}' itself, so it is not generated." + SeeBuildErrors); + + public static readonly DiagnosticDefinition<(INamedType Type, string Reason)> UnsupportedValueObject = +- new("CMTK1005", Severity.Error, "'{0}' cannot be generated as a value object: {1}." + SeeBuildErrors); ++ new("CMTK1005", Severity.Error, "'{0}' cannot be generated as a value object: {1}."); + + public static readonly DiagnosticDefinition<INamedType> MissingSealedKeyword = + new("CMTK1006", Severity.Error, "'{0}' is a value object and must be declared sealed, so it is not generated." + SeeBuildErrors); diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-generic-wrapped.patch b/tools/probe/corpus/seeds/C22-generator-refusals-generic-wrapped.patch new file mode 100644 index 00000000..3d437056 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-generic-wrapped.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +index db8d4e9..0b1612d 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +@@ -246,12 +246,6 @@ internal sealed partial class ValueObjectAspect : TypeAspect + // name lost its type arguments there, and a named tuple broke the attribute. Refused rather than + // made to work, because a value object's equality is its wrapped value's, and a generic type's + // is not value equality in general. Refusing now and lifting it later breaks nobody. +- if (namedValueType.IsGeneric || IsNestedInGeneric(namedValueType)) +- { +- builder.Diagnostics.Report(UnsupportedValueObject.WithArguments((target, +- $"the wrapped type '{namedValueType.ToDisplayString()}' is generic, and a value object's equality is its wrapped value's, which a generic type does not guarantee (a List<T> compares by reference); wrap a non-generic type of your own that has the equality you need"))); +- return false; +- } + + // Before the field is introduced: over a struct that wraps itself it has no layout (CS0523), + // which Metalama reports as a bug in this aspect. diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-named-as-generated-member.patch b/tools/probe/corpus/seeds/C22-generator-refusals-named-as-generated-member.patch new file mode 100644 index 00000000..7bfe1b73 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-named-as-generated-member.patch @@ -0,0 +1,16 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +index db8d4e9..e3f46d8 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +@@ -146,11 +146,6 @@ internal sealed partial class ValueObjectAspect : TypeAspect + return false; + } + +- if (ValueObjectDeclaration.GeneratedMemberNames.Contains(target.Name)) +- { +- builder.Diagnostics.Report(UnsupportedValueObject.WithArguments((target, $"a value object cannot be named '{target.Name}', the name of a member the generators introduce, since a member cannot have the name of its type; rename it"))); +- return false; +- } + + // The companion class and the attribute's type arguments sit outside the file, where a file-local + // type, or one nested in it, cannot be named, and Metalama crashed on it (LAMA0001, naming nothing). diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-no-type-half-generated.patch b/tools/probe/corpus/seeds/C22-generator-refusals-no-type-half-generated.patch new file mode 100644 index 00000000..9587d372 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-no-type-half-generated.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +index 6fd2f8d..1f23cde 100644 +--- a/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs ++++ b/src/CodoMetis.TypeKit.Generators/AspectDiagnostics.cs +@@ -34,7 +34,7 @@ internal static class AspectDiagnostics + new("CMTK1004", Severity.Error, "'{0}' implements {1}, whose first type argument must be '{0}' itself, so it is not generated." + SeeBuildErrors); + + public static readonly DiagnosticDefinition<(INamedType Type, string Reason)> UnsupportedValueObject = +- new("CMTK1005", Severity.Error, "'{0}' cannot be generated as a value object: {1}." + SeeBuildErrors); ++ new("CMTK1005", Severity.Warning, "'{0}' cannot be generated as a value object: {1}." + SeeBuildErrors); + + public static readonly DiagnosticDefinition<INamedType> MissingSealedKeyword = + new("CMTK1006", Severity.Error, "'{0}' is a value object and must be declared sealed, so it is not generated." + SeeBuildErrors); diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-over-object.patch b/tools/probe/corpus/seeds/C22-generator-refusals-over-object.patch new file mode 100644 index 00000000..fdc63eee --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-over-object.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +index db8d4e9..4c4f0c1 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +@@ -225,12 +225,6 @@ internal sealed partial class ValueObjectAspect : TypeAspect + // object property holding 5 is written as 5 (measured 2026-10-13). An interface or an abstract + // class is written and read as the serializer writes it (its declared members, or polymorphic + // where the type says so), so it stays allowed. +- if (namedValueType.SpecialType == SpecialType.Object) +- { +- builder.Diagnostics.Report(UnsupportedValueObject.WithArguments((target, +- "the wrapped type 'object' holds a value of any type, which the serializer writes by its run-time type and a value object's converter cannot (5 was written as {}); wrap the type the value has"))); +- return false; +- } + + // System.Enum is the base of every enum and no enum itself: its generic Enum.Parse<TEnum>(string) + // was taken for a Parse of its own, and the generated parsing failed inside the generated code diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-over-system-enum.patch b/tools/probe/corpus/seeds/C22-generator-refusals-over-system-enum.patch new file mode 100644 index 00000000..a805c7e3 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-over-system-enum.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +index db8d4e9..43e7c4a 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +@@ -235,12 +235,6 @@ internal sealed partial class ValueObjectAspect : TypeAspect + // System.Enum is the base of every enum and no enum itself: its generic Enum.Parse<TEnum>(string) + // was taken for a Parse of its own, and the generated parsing failed inside the generated code + // (LAMA0611, CS0411), naming nothing the user wrote. +- if (namedValueType.Equals(TypeFactory.GetNamedType(typeof(Enum)))) +- { +- builder.Diagnostics.Report(UnsupportedValueObject.WithArguments((target, +- "the wrapped type 'Enum' is the base of every enum and no enum itself; wrap the enum type"))); +- return false; +- } + + // A generic wrapped type, tuples included, failed inside the generated code (LAMA0611): its + // name lost its type arguments there, and a named tuple broke the attribute. Refused rather than diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-over-value-object.patch b/tools/probe/corpus/seeds/C22-generator-refusals-over-value-object.patch new file mode 100644 index 00000000..786ea89c --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-over-value-object.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs +index 6ceba7f..3f10fec 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectFabric.cs +@@ -38,7 +38,7 @@ internal sealed class ValueObjectFabric : TransitiveProjectFabric + .Where(ValueObjectTypes.IsValueObject) + .AddAspect(type => new ValueObjectAspect( + CompanionClass.NameOwner(type), +- ValueObjectTypes.WrappedValueObjectRefusal(type))); ++ null)); + + // An abstract type gets no aspect, so it is refused here, where it is selected. + amender.SelectTypes(includeNestedTypes: true) diff --git a/tools/probe/corpus/seeds/C22-generator-refusals-unsealed-record-class.patch b/tools/probe/corpus/seeds/C22-generator-refusals-unsealed-record-class.patch new file mode 100644 index 00000000..8cf83217 --- /dev/null +++ b/tools/probe/corpus/seeds/C22-generator-refusals-unsealed-record-class.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +index db8d4e9..c8e8392 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.cs +@@ -163,12 +163,6 @@ internal sealed partial class ValueObjectAspect : TypeAspect + // A record class that can be derived from is not generated: a derived record compares equal + // only to its own type, which is not value equality, and a derived value object then failed + // inside the generated code (LAMA0611), where the error names nothing the user wrote. +- if (target is { TypeKind: TypeKind.Class, IsSealed: false }) +- { +- builder.Diagnostics.Report(MissingSealedKeyword.WithArguments(target)); +- return false; +- } +- + for (var baseType = target.BaseType; baseType is not null; baseType = baseType.BaseType) + { + if (!baseType.IsAbstract && ValueObjectTypes.Markers(baseType).Count > 0) diff --git a/tools/probe/corpus/seeds/E01-column-parity-column-type.patch b/tools/probe/corpus/seeds/E01-column-parity-column-type.patch new file mode 100644 index 00000000..85a5d4e1 --- /dev/null +++ b/tools/probe/corpus/seeds/E01-column-parity-column-type.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +index d04ed86..a3b3de2 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +@@ -49,7 +49,7 @@ internal sealed class ValueObjectTypeMappingSourcePlugin(IServiceProvider servic + _typeMappingSource ??= services.GetRequiredService<IRelationalTypeMappingSource>(); + + var wrapped = _typeMappingSource.FindMapping( +- valueObject.WrappedType, mappingInfo.StoreTypeName, mappingInfo.IsKeyOrIndex, mappingInfo.IsUnicode, mappingInfo.Size, ++ valueObject.WrappedType.IsEnum ? typeof(string) : valueObject.WrappedType, mappingInfo.StoreTypeName, mappingInfo.IsKeyOrIndex, mappingInfo.IsUnicode, mappingInfo.Size, + mappingInfo.IsRowVersion, mappingInfo.IsFixedLength, mappingInfo.Precision, mappingInfo.Scale); + + return wrapped is null ? null : ValueObjectMapping.For(valueObject).ComposeOnto(wrapped); diff --git a/tools/probe/corpus/seeds/E01-column-parity-facet-kept.patch b/tools/probe/corpus/seeds/E01-column-parity-facet-kept.patch new file mode 100644 index 00000000..63ba2837 --- /dev/null +++ b/tools/probe/corpus/seeds/E01-column-parity-facet-kept.patch @@ -0,0 +1,23 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..b30cfc4 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,18 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null) ++ { ++ if (p.GetMaxLengthConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetMaxLength(null); ++ if (p.GetPrecisionConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetPrecision(null); ++ if (p.GetScaleConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetScale(null); ++ if (p.GetColumnTypeConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetColumnType(null); ++ } + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E01-column-parity-nullable.patch b/tools/probe/corpus/seeds/E01-column-parity-nullable.patch new file mode 100644 index 00000000..189920fc --- /dev/null +++ b/tools/probe/corpus/seeds/E01-column-parity-nullable.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..7c7fa9c 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,12 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (!p.IsKey() && ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null) p.Builder.IsRequired(true); + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E02-value-translates-to-column-column-not-cast.patch b/tools/probe/corpus/seeds/E02-value-translates-to-column-column-not-cast.patch new file mode 100644 index 00000000..6b2d61d9 --- /dev/null +++ b/tools/probe/corpus/seeds/E02-value-translates-to-column-column-not-cast.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..074e67d 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -120,9 +120,7 @@ internal static class WrappedValue + && (operandMapping.Converter is not { } converter || !ValueObjectMapping.For(valueObject).StoresTheWrappedValue(converter, mapping?.Converter))) + return null; + +- return operand is ColumnExpression column && !IsCollectionElement(column) +- ? new ColumnExpression(column.Name, column.TableAlias, valueType, mapping, column.IsNullable) +- : sqlExpressionFactory.Convert(operand, valueType, mapping); ++ return sqlExpressionFactory.Convert(operand, valueType, mapping); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/E02-value-translates-to-column-get-value.patch b/tools/probe/corpus/seeds/E02-value-translates-to-column-get-value.patch new file mode 100644 index 00000000..195c217a --- /dev/null +++ b/tools/probe/corpus/seeds/E02-value-translates-to-column-get-value.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..b3f43b0 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -73,7 +73,7 @@ internal sealed class ValueObjectMethodCallTranslatorPlugin( + ) + { + if (instance is not null || arguments.Count != 1) return null; +- if (method.GetCustomAttribute<TranslatedAsWrappedValueAttribute>() is null) return null; ++ if (method.GetCustomAttribute<TranslatedAsWrappedValueAttribute>() is null || method.Name == "GetValue") return null; + + var parameterType = method.GetParameters()[0].ParameterType; + var valueType = Nullable.GetUnderlyingType(method.ReturnType) ?? method.ReturnType; diff --git a/tools/probe/corpus/seeds/E02-value-translates-to-column-value-or-null.patch b/tools/probe/corpus/seeds/E02-value-translates-to-column-value-or-null.patch new file mode 100644 index 00000000..508fe393 --- /dev/null +++ b/tools/probe/corpus/seeds/E02-value-translates-to-column-value-or-null.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..928d9dd 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -73,7 +73,7 @@ internal sealed class ValueObjectMethodCallTranslatorPlugin( + ) + { + if (instance is not null || arguments.Count != 1) return null; +- if (method.GetCustomAttribute<TranslatedAsWrappedValueAttribute>() is null) return null; ++ if (method.GetCustomAttribute<TranslatedAsWrappedValueAttribute>() is null || method.Name == "ValueOrNull") return null; + + var parameterType = method.GetParameters()[0].ParameterType; + var valueType = Nullable.GetUnderlyingType(method.ReturnType) ?? method.ReturnType; diff --git a/tools/probe/corpus/seeds/E02-value-translates-to-column-value.patch b/tools/probe/corpus/seeds/E02-value-translates-to-column-value.patch new file mode 100644 index 00000000..41a53ec9 --- /dev/null +++ b/tools/probe/corpus/seeds/E02-value-translates-to-column-value.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..28a0492 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -34,7 +34,7 @@ internal sealed class ValueObjectMemberTranslatorPlugin( + IDiagnosticsLogger<DbLoggerCategory.Query> logger + ) + { +- if (instance is null || member.Name != nameof(IValueObject<,>.Value)) return null; ++ if (instance is null || member.Name != nameof(IValueObject<,>.Value) || member.Name.Length > 0) return null; + if (member.DeclaringType is not { } declaringType || ValueObjectTypes.Describe(declaringType) is not { } valueObject) return null; + + return WrappedValue.Of(instance, valueObject, returnType, sqlExpressionFactory, typeMappingSource); diff --git a/tools/probe/corpus/seeds/E03-column-round-trip-keys.patch b/tools/probe/corpus/seeds/E03-column-round-trip-keys.patch new file mode 100644 index 00000000..4c9880d7 --- /dev/null +++ b/tools/probe/corpus/seeds/E03-column-round-trip-keys.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +index d04ed86..8f40572 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +@@ -45,6 +45,7 @@ internal sealed class ValueObjectTypeMappingSourcePlugin(IServiceProvider servic + } + + if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(clrType) ?? clrType) is not { } valueObject) return null; ++ if (mappingInfo.IsKeyOrIndex) return null; + + _typeMappingSource ??= services.GetRequiredService<IRelationalTypeMappingSource>(); + diff --git a/tools/probe/corpus/seeds/E03-column-round-trip-nullable-properties.patch b/tools/probe/corpus/seeds/E03-column-round-trip-nullable-properties.patch new file mode 100644 index 00000000..189920fc --- /dev/null +++ b/tools/probe/corpus/seeds/E03-column-round-trip-nullable-properties.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..7c7fa9c 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,12 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (!p.IsKey() && ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null) p.Builder.IsRequired(true); + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E03-column-round-trip-stored-as-underlying.patch b/tools/probe/corpus/seeds/E03-column-round-trip-stored-as-underlying.patch new file mode 100644 index 00000000..b1f413f3 --- /dev/null +++ b/tools/probe/corpus/seeds/E03-column-round-trip-stored-as-underlying.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +index d04ed86..9efc11b 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +@@ -49,7 +49,7 @@ internal sealed class ValueObjectTypeMappingSourcePlugin(IServiceProvider servic + _typeMappingSource ??= services.GetRequiredService<IRelationalTypeMappingSource>(); + + var wrapped = _typeMappingSource.FindMapping( +- valueObject.WrappedType, mappingInfo.StoreTypeName, mappingInfo.IsKeyOrIndex, mappingInfo.IsUnicode, mappingInfo.Size, ++ valueObject.WrappedType == typeof(decimal) ? typeof(double) : valueObject.WrappedType, mappingInfo.StoreTypeName, mappingInfo.IsKeyOrIndex, mappingInfo.IsUnicode, mappingInfo.Size, + mappingInfo.IsRowVersion, mappingInfo.IsFixedLength, mappingInfo.Precision, mappingInfo.Scale); + + return wrapped is null ? null : ValueObjectMapping.For(valueObject).ComposeOnto(wrapped); diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-byte-not-generated.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-byte-not-generated.patch new file mode 100644 index 00000000..bdbd514f --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-byte-not-generated.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..96b4bbd 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -77,7 +77,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + /// </summary> + private static bool IsGeneratedByTheDatabase(Type wrapped) => + wrapped == typeof(int) || wrapped == typeof(long) || wrapped == typeof(short) || wrapped == typeof(sbyte) +- || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort); ++ || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort) || wrapped == typeof(byte); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-char-key-yours.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-char-key-yours.patch new file mode 100644 index 00000000..457da14a --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-char-key-yours.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..1f5343b 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -77,7 +77,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + /// </summary> + private static bool IsGeneratedByTheDatabase(Type wrapped) => + wrapped == typeof(int) || wrapped == typeof(long) || wrapped == typeof(short) || wrapped == typeof(sbyte) +- || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort); ++ || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort) || wrapped == typeof(char); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-generated-on-add.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-generated-on-add.patch new file mode 100644 index 00000000..7384d9b8 --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-generated-on-add.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..fee4e9a 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -53,7 +53,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + if (GeneratedKey(entityType) is { } property) +- property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ property.Builder.ValueGenerated(ValueGenerated.Never); + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-guid-key-yours.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-guid-key-yours.patch new file mode 100644 index 00000000..8c6b7d8d --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-guid-key-yours.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..55d4b57 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -77,7 +77,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + /// </summary> + private static bool IsGeneratedByTheDatabase(Type wrapped) => + wrapped == typeof(int) || wrapped == typeof(long) || wrapped == typeof(short) || wrapped == typeof(sbyte) +- || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort); ++ || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort) || wrapped == typeof(Guid); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-not-on-efs-list.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-not-on-efs-list.patch new file mode 100644 index 00000000..8791b695 --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-not-on-efs-list.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index c64d73e..afedaa5 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -77,7 +77,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + /// </summary> + private static bool IsGeneratedByTheDatabase(Type wrapped) => + wrapped == typeof(int) || wrapped == typeof(long) || wrapped == typeof(short) || wrapped == typeof(sbyte) +- || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort); ++ || wrapped == typeof(uint) || wrapped == typeof(ulong) || wrapped == typeof(ushort) || wrapped == typeof(string); + } + + /// <summary> diff --git a/tools/probe/corpus/seeds/E04-integer-key-generated-own-configuration-wins.patch b/tools/probe/corpus/seeds/E04-integer-key-generated-own-configuration-wins.patch new file mode 100644 index 00000000..a76b9d10 --- /dev/null +++ b/tools/probe/corpus/seeds/E04-integer-key-generated-own-configuration-wins.patch @@ -0,0 +1,18 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..6b023d9 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,13 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ if (entityType.FindPrimaryKey() is { Properties: [var key] } ++ && ValueObjectTypes.Describe(key.ClrType) is { WrappedType: var keyWrapped } && (keyWrapped == typeof(char) || keyWrapped == typeof(Guid))) ++ ((IMutableProperty)key).ValueGenerated = ValueGenerated.Never; + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-comparison.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-comparison.patch new file mode 100644 index 00000000..aea808cd --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-comparison.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..81d9b28 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -84,7 +84,7 @@ internal static class HandWrittenOrdering + protected override Expression VisitBinary(BinaryExpression node) + { + if (node.NodeType is ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual +- && !RunsInMemory(node)) ++ && !RunsInMemory(node) && skipsWhatRunsInMemory) + { + Refuse(node.Left.Type, $"compared with {Operator(node.NodeType)}"); + Refuse(node.Right.Type, $"compared with {Operator(node.NodeType)}"); +@@ -99,7 +99,7 @@ internal static class HandWrittenOrdering + + if (method.Name is nameof(IComparable.CompareTo) or nameof(IComparer<>.Compare)) + { +- if (!RunsInMemory(node)) ++ if (!RunsInMemory(node) && skipsWhatRunsInMemory) + { + if (node.Object is not null) Refuse(node.Object.Type, $"compared with {method.Name}"); + foreach (var argument in node.Arguments) Refuse(argument.Type, $"compared with {method.Name}"); diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-equality-allowed.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-equality-allowed.patch new file mode 100644 index 00000000..6cefc240 --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-equality-allowed.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..6b47cfd 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -83,7 +83,7 @@ internal static class HandWrittenOrdering + { + protected override Expression VisitBinary(BinaryExpression node) + { +- if (node.NodeType is ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual ++ if (node.NodeType is ExpressionType.Equal or ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual + && !RunsInMemory(node)) + { + Refuse(node.Left.Type, $"compared with {Operator(node.NodeType)}"); diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-min-max.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-min-max.patch new file mode 100644 index 00000000..38d37356 --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-min-max.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..89d2907 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -142,7 +142,6 @@ internal static class HandWrittenOrdering + nameof(Queryable.OrderBy) or nameof(Queryable.OrderByDescending) or nameof(Queryable.ThenBy) or nameof(Queryable.ThenByDescending) + or nameof(Queryable.MinBy) or nameof(Queryable.MaxBy) when method.IsGenericMethod => method.GetGenericArguments()[1], + nameof(Queryable.Order) or nameof(Queryable.OrderDescending) when method.IsGenericMethod => method.GetGenericArguments()[0], +- nameof(Queryable.Min) or nameof(Queryable.Max) => method.ReturnType, + _ => null, + }; + diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-ordering.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-ordering.patch new file mode 100644 index 00000000..51de5081 --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-ordering.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..6acc064 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -139,9 +139,7 @@ internal static class HandWrittenOrdering + /// <summary>The type a LINQ operator orders by: the key, the element for <c>Order</c>, the result for <c>Min</c> and <c>Max</c>.</summary> + private static Type? OrderedType(MethodInfo method) => method.Name switch + { +- nameof(Queryable.OrderBy) or nameof(Queryable.OrderByDescending) or nameof(Queryable.ThenBy) or nameof(Queryable.ThenByDescending) +- or nameof(Queryable.MinBy) or nameof(Queryable.MaxBy) when method.IsGenericMethod => method.GetGenericArguments()[1], +- nameof(Queryable.Order) or nameof(Queryable.OrderDescending) when method.IsGenericMethod => method.GetGenericArguments()[0], ++ nameof(Queryable.MinBy) or nameof(Queryable.MaxBy) when method.IsGenericMethod => method.GetGenericArguments()[1], + nameof(Queryable.Min) or nameof(Queryable.Max) => method.ReturnType, + _ => null, + }; diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-query-filter.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-query-filter.patch new file mode 100644 index 00000000..d82d5cb8 --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-query-filter.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..0efc973 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -56,7 +56,7 @@ internal sealed class HandWrittenOrderingQueryFilterConvention : IModelFinalized + { + foreach (var entityType in model.GetEntityTypes()) + foreach (var filter in entityType.GetDeclaredQueryFilters()) +- if (filter.Expression is { } expression) ++ if (filter.Expression is { } expression && expression.Parameters.Count == 0) + HandWrittenOrdering.RefuseInFilter(expression, filter.IsAnonymous + ? $"in the query filter of {entityType.DisplayName()}" + : $"in the query filter \"{filter.Key}\" of {entityType.DisplayName()}"); diff --git a/tools/probe/corpus/seeds/E05-own-compareto-rejected-value-order-allowed.patch b/tools/probe/corpus/seeds/E05-own-compareto-rejected-value-order-allowed.patch new file mode 100644 index 00000000..c8e5b6eb --- /dev/null +++ b/tools/probe/corpus/seeds/E05-own-compareto-rejected-value-order-allowed.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 09a9e8c..3d10963 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -117,6 +117,7 @@ internal static class HandWrittenOrdering + // A key the selector converts (to object, IComparable) is still ordered in memory by the + // value object's CompareTo, while EF drops the conversion and orders by the column. + if (KeySelector(node) is { } key) Refuse(Unconverted(key.Body).Type, $"ordered by {method.Name}"); ++ if (KeySelector(node) is { Body: MemberExpression { Expression: { } owner } }) Refuse(owner.Type, $"ordered by {method.Name}"); + } + else if (IsDbFunction(method) && !RunsInMemory(node)) + { diff --git a/tools/probe/corpus/seeds/E06-own-converter-no-value-value-not-translated.patch b/tools/probe/corpus/seeds/E06-own-converter-no-value-value-not-translated.patch new file mode 100644 index 00000000..ea216387 --- /dev/null +++ b/tools/probe/corpus/seeds/E06-own-converter-no-value-value-not-translated.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..2da8e5a 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -117,7 +117,7 @@ internal static class WrappedValue + ?? typeMappingSource.FindMapping(valueType); + + if (operand.TypeMapping is { } operandMapping +- && (operandMapping.Converter is not { } converter || !ValueObjectMapping.For(valueObject).StoresTheWrappedValue(converter, mapping?.Converter))) ++ && operandMapping.Converter is null) + return null; + + return operand is ColumnExpression column && !IsCollectionElement(column) diff --git a/tools/probe/corpus/seeds/E06-own-converter-no-value-value-object-comparison.patch b/tools/probe/corpus/seeds/E06-own-converter-no-value-value-object-comparison.patch new file mode 100644 index 00000000..2341e337 --- /dev/null +++ b/tools/probe/corpus/seeds/E06-own-converter-no-value-value-object-comparison.patch @@ -0,0 +1,28 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 5f1499f..00292f7 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -34,7 +34,22 @@ internal sealed class HandWrittenOrderingInterceptor : IQueryExpressionIntercept + { + HandWrittenOrdering.RefuseIn(queryExpression); + +- return queryExpression; ++ return new EqualityOfValues().Visit(queryExpression); ++ } ++ ++ /// <summary>A value-object equality, as an equality of the values it wraps.</summary> ++ private sealed class EqualityOfValues : ExpressionVisitor ++ { ++ protected override Expression VisitBinary(BinaryExpression node) ++ { ++#pragma warning disable IL2075 ++ if (node.NodeType == ExpressionType.Equal && node.Left.Type == node.Right.Type && ValueObjectTypes.IsValueObject(node.Left.Type) ++ && node.Left.Type.GetProperty("Value") is { } value) ++ return Expression.Equal(Expression.Property(Visit(node.Left)!, value), Expression.Property(Visit(node.Right)!, value)); ++#pragma warning restore IL2075 ++ ++ return base.VisitBinary(node); ++ } + } + } + diff --git a/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-in-collections-and-json.patch b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-in-collections-and-json.patch new file mode 100644 index 00000000..0cd27420 --- /dev/null +++ b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-in-collections-and-json.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetMapping.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetMapping.cs +index bbaa392..50a8e08 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetMapping.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetMapping.cs +@@ -47,7 +47,9 @@ internal abstract class ClosedSetMapping + public override ValueConverter Converter { get; } = new ValueConverter<TSet, string>(ClosedSetConverter<TSet>.ToProvider, ClosedSetConverter<TSet>.FromProvider); + + protected override JsonValueReaderWriter? ReaderWriterOver(JsonValueReaderWriter? text) => +- text is JsonValueReaderWriter<string> typed ? new JsonConvertedValueReaderWriter<TSet, string>(typed, Converter) : null; ++ text is JsonValueReaderWriter<string> typed ++ ? new JsonConvertedValueReaderWriter<TSet, string>(typed, new ValueConverter<TSet, string>(member => member.Name + "!", name => ClosedSetConverter<TSet>.MemberNamed(name))) ++ : null; + } + + private sealed class Factory : IClosedSetVisitor<ClosedSetMapping> diff --git a/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-non-member-throws.patch b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-non-member-throws.patch new file mode 100644 index 00000000..6343d4cb --- /dev/null +++ b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-non-member-throws.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +index f0049b3..2c06526 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +@@ -45,7 +45,11 @@ public sealed class ClosedSetConverter<TSet> : ValueConverter<TSet, string> + /// <returns>Its name.</returns> + /// <exception cref="InvalidOperationException">The instance is not one of the set's members.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] +- public static string ProviderValue(TSet member) => member.Name; ++ public static string ProviderValue(TSet member) ++ { ++ try { return member.Name; } ++ catch (InvalidOperationException) { return "Unknown"; } ++ } + + /// <summary>The member of a stored name.</summary> + /// <remarks>Public for the code EF's compiled model generates; nothing else needs to call it.</remarks> diff --git a/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-stored-as-name.patch b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-stored-as-name.patch new file mode 100644 index 00000000..43428d26 --- /dev/null +++ b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-stored-as-name.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +index f0049b3..6e7f826 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +@@ -45,7 +45,7 @@ public sealed class ClosedSetConverter<TSet> : ValueConverter<TSet, string> + /// <returns>Its name.</returns> + /// <exception cref="InvalidOperationException">The instance is not one of the set's members.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] +- public static string ProviderValue(TSet member) => member.Name; ++ public static string ProviderValue(TSet member) => member.Name.ToUpperInvariant(); + + /// <summary>The member of a stored name.</summary> + /// <remarks>Public for the code EF's compiled model generates; nothing else needs to call it.</remarks> diff --git a/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-unknown-name-throws.patch b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-unknown-name-throws.patch new file mode 100644 index 00000000..50afdc25 --- /dev/null +++ b/tools/probe/corpus/seeds/E07-closed-set-stored-as-name-unknown-name-throws.patch @@ -0,0 +1,11 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +index f0049b3..6ebae68 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ClosedSetConverter.cs +@@ -56,5 +56,5 @@ public sealed class ClosedSetConverter<TSet> : ValueConverter<TSet, string> + public static TSet MemberNamed(string name) => + TSet.TryParse(name, out var member) + ? member +- : throw new InvalidOperationException($"{typeof(TSet).Name} has no member of that name: the column holds a name the set no longer has."); ++ : null!; + } diff --git a/tools/probe/corpus/seeds/E08-option-result-not-columns-error-says-so.patch b/tools/probe/corpus/seeds/E08-option-result-not-columns-error-says-so.patch new file mode 100644 index 00000000..d25351e9 --- /dev/null +++ b/tools/probe/corpus/seeds/E08-option-result-not-columns-error-says-so.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +index d04ed86..591bfb4 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTypeMappingSourcePlugin.cs +@@ -33,6 +33,8 @@ internal sealed class ValueObjectTypeMappingSourcePlugin(IServiceProvider servic + public RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo) + { + if (mappingInfo.ClrType is not { } clrType) return null; ++ if (clrType.Name.StartsWith("Option`", StringComparison.Ordinal) || clrType.Name.StartsWith("Result`", StringComparison.Ordinal)) ++ throw new InvalidOperationException("This property cannot be mapped."); + + // A closed set is a text column holding the member's name. + if (ClosedSetMapping.Describe(clrType) is { } set) diff --git a/tools/probe/corpus/seeds/E08-option-result-not-columns-model-fails.patch b/tools/probe/corpus/seeds/E08-option-result-not-columns-model-fails.patch new file mode 100644 index 00000000..b1ba36b8 --- /dev/null +++ b/tools/probe/corpus/seeds/E08-option-result-not-columns-model-fails.patch @@ -0,0 +1,20 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..0981e07 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,15 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++#pragma warning disable IL2075 ++ foreach (var clr in entityType.ClrType.GetProperties()) ++#pragma warning restore IL2075 ++ if (clr.PropertyType.Name.StartsWith("Option`", StringComparison.Ordinal) || clr.PropertyType.Name.StartsWith("Result`", StringComparison.Ordinal)) ++ entityType.Builder.Ignore(clr.Name); + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E09-or-none-in-database-element-at-skips.patch b/tools/probe/corpus/seeds/E09-or-none-in-database-element-at-skips.patch new file mode 100644 index 00000000..154d16e3 --- /dev/null +++ b/tools/probe/corpus/seeds/E09-or-none-in-database-element-at-skips.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/OptionExtensions.cs b/src/CodoMetis.TypeKit/OptionExtensions.cs +index 369d57a..c77b64a 100644 +--- a/src/CodoMetis.TypeKit/OptionExtensions.cs ++++ b/src/CodoMetis.TypeKit/OptionExtensions.cs +@@ -576,7 +576,7 @@ public static class Option + { + ArgumentNullException.ThrowIfNull(source); + +- return index < 0 ? None<T>() : FirstOf(source.Skip(index).Take(1)); ++ return index < 0 ? None<T>() : FirstOf(source.Take(index + 1).AsEnumerable().Skip(index)); + } + + /// <summary>Refused: a query skips to an index from the start, and one from the end would read every row to find it.</summary> diff --git a/tools/probe/corpus/seeds/E09-or-none-in-database-first-reads-one-row.patch b/tools/probe/corpus/seeds/E09-or-none-in-database-first-reads-one-row.patch new file mode 100644 index 00000000..14f7bf03 --- /dev/null +++ b/tools/probe/corpus/seeds/E09-or-none-in-database-first-reads-one-row.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs +index 9636046..8c15b34 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs +@@ -43,7 +43,7 @@ public static class TypeKitQueryableExtensions + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(predicate); + +- return FirstOfAsync(source.Where(predicate).Take(1), cancellationToken); ++ return FirstOfAsync(source.Where(predicate), cancellationToken); + } + + /// <summary>The only element of the query, or <c>None</c>: the database is asked for two, and more than one throws.</summary> diff --git a/tools/probe/corpus/seeds/E09-or-none-in-database-single-asks-for-two.patch b/tools/probe/corpus/seeds/E09-or-none-in-database-single-asks-for-two.patch new file mode 100644 index 00000000..844c1f71 --- /dev/null +++ b/tools/probe/corpus/seeds/E09-or-none-in-database-single-asks-for-two.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs +index 9636046..22caa03 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitQueryableExtensions.cs +@@ -88,7 +88,7 @@ public static class TypeKitQueryableExtensions + await foreach (var element in limited.AsAsyncEnumerable().WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (found.IsSome()) +- throw new InvalidOperationException("The query holds more than one element, so SingleOrNoneAsync has no single one to answer. Use FirstOrNoneAsync where any one will do."); ++ return found; + + found = Option.Some(element); + } diff --git a/tools/probe/corpus/seeds/E10-read-back-skips-validation-reads-without-create.patch b/tools/probe/corpus/seeds/E10-read-back-skips-validation-reads-without-create.patch new file mode 100644 index 00000000..78b3614a --- /dev/null +++ b/tools/probe/corpus/seeds/E10-read-back-skips-validation-reads-without-create.patch @@ -0,0 +1,20 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs +index 48895b1..a308d3c 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectConverter.cs +@@ -70,6 +70,14 @@ public sealed class ValueObjectConverter<TValueObject, T> : ValueConverter<TValu + /// <returns>The value object.</returns> + [EditorBrowsable(EditorBrowsableState.Never)] + #pragma warning disable CMTK0004 // the read path, as above +- public static TValueObject Materialize(T value) => TValueObject.Materialize(value); ++ public static TValueObject Materialize(T value) ++ { ++#pragma warning disable IL2090, IL2075 ++ if (typeof(TValueObject).GetMethod("Create", [typeof(T)])?.Invoke(null, [value]) is { } created ++ && created.GetType().GetProperty("State")?.GetValue(created)?.ToString() == "Error") ++ throw new InvalidOperationException("The column holds a value Create refuses."); ++#pragma warning restore IL2090, IL2075 ++ return TValueObject.Materialize(value); ++ } + #pragma warning restore CMTK0004 + } diff --git a/tools/probe/corpus/seeds/E10-read-back-skips-validation-revalidate-finds.patch b/tools/probe/corpus/seeds/E10-read-back-skips-validation-revalidate-finds.patch new file mode 100644 index 00000000..3f4a12a1 --- /dev/null +++ b/tools/probe/corpus/seeds/E10-read-back-skips-validation-revalidate-finds.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +index 07a7ecb..8b626aa 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +@@ -37,7 +37,7 @@ internal sealed partial class ValueObjectContractAspect + /// <summary><c>Create</c> of the value this instance holds.</summary> + [Template] + public dynamic RevalidateTemplate([CompileTime] INamedType target) => +- ExpressionFactory.Parse($"{ValueObjectTypes.SourceName(target)}.Create(this.Value)").Value!; ++ ExpressionFactory.Parse("this").Value!; + + /// <summary> + /// A deeper base's <c>Equals(TBase)</c>, declared by the value object so that its listed diff --git a/tools/probe/corpus/seeds/E11-usetypekit-order-order-does-not-matter.patch b/tools/probe/corpus/seeds/E11-usetypekit-order-order-does-not-matter.patch new file mode 100644 index 00000000..54b917be --- /dev/null +++ b/tools/probe/corpus/seeds/E11-usetypekit-order-order-does-not-matter.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitOptionsExtension.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitOptionsExtension.cs +index d9d37fd..80be5a6 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitOptionsExtension.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitOptionsExtension.cs +@@ -10,7 +10,11 @@ internal sealed class TypeKitOptionsExtension : IDbContextOptionsExtension + + public DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); + +- public void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkTypeKit(); ++ public void ApplyServices(IServiceCollection services) ++ { ++ if (services.Any(descriptor => descriptor.ServiceType == typeof(Microsoft.EntityFrameworkCore.Storage.IDatabaseProvider))) ++ services.AddEntityFrameworkTypeKit(); ++ } + + public void Validate(IDbContextOptions options) + { diff --git a/tools/probe/corpus/seeds/E12-convention-by-vo-type-underlying-type-does-not-reach.patch b/tools/probe/corpus/seeds/E12-convention-by-vo-type-underlying-type-does-not-reach.patch new file mode 100644 index 00000000..f98d2beb --- /dev/null +++ b/tools/probe/corpus/seeds/E12-convention-by-vo-type-underlying-type-does-not-reach.patch @@ -0,0 +1,35 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs +index f0ec2c3..05e6d4c 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/TypeKitConventionSetPlugin.cs +@@ -1,3 +1,4 @@ ++using Microsoft.EntityFrameworkCore.Metadata.Builders; + using Microsoft.EntityFrameworkCore.Metadata.Conventions; + using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +@@ -16,6 +17,8 @@ internal sealed class TypeKitConventionSetPlugin : IConventionSetPlugin + { + var finalizing = conventionSet.ModelFinalizingConventions; + ++ conventionSet.PropertyAddedConventions.Add(new WrappedTypeConfigurationConvention()); ++ + // After the last of EF's finalizing conventions that change what its value-generation convention + // re-reads, and so before a provider's value-generation strategy (an identity column on PostgreSQL + // and SQL Server), which is chosen for keys already generated on add and comes after them. +@@ -52,3 +55,16 @@ internal sealed class TypeKitConventionSetPlugin : IConventionSetPlugin + return last + 1; + } + } ++ ++/// <summary>A value-object property configured as the type it wraps is: <c>ConfigureConventions</c> by the wrapped type reaches it.</summary> ++internal sealed class WrappedTypeConfigurationConvention : IPropertyAddedConvention ++{ ++ public void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, IConventionContext<IConventionPropertyBuilder> context) ++ { ++ var property = propertyBuilder.Metadata; ++ if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(property.ClrType) ?? property.ClrType) is not { WrappedType: var wrapped }) return; ++#pragma warning disable EF1001 ++ ((Microsoft.EntityFrameworkCore.Metadata.Internal.Model)property.DeclaringType.Model).Configuration?.FindProperty(wrapped)?.Apply((Microsoft.EntityFrameworkCore.Metadata.IMutableProperty)property); ++#pragma warning restore EF1001 ++ } ++} diff --git a/tools/probe/corpus/seeds/E12-convention-by-vo-type-value-object-type-reaches.patch b/tools/probe/corpus/seeds/E12-convention-by-vo-type-value-object-type-reaches.patch new file mode 100644 index 00000000..91a3be65 --- /dev/null +++ b/tools/probe/corpus/seeds/E12-convention-by-vo-type-value-object-type-reaches.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..f8299cf 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,17 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var valueObjectProperty in entityType.GetDeclaredProperties()) ++ if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(valueObjectProperty.ClrType) ?? valueObjectProperty.ClrType) is not null) ++ { ++ ((IMutableProperty)valueObjectProperty).SetMaxLength(null); ++ ((IMutableProperty)valueObjectProperty).SetPrecision(null); ++ ((IMutableProperty)valueObjectProperty).SetScale(null); ++ } + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E13-primitive-collections-contains.patch b/tools/probe/corpus/seeds/E13-primitive-collections-contains.patch new file mode 100644 index 00000000..f2c676d9 --- /dev/null +++ b/tools/probe/corpus/seeds/E13-primitive-collections-contains.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +index 09a9e8c..2e137c8 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/HandWrittenOrderingInterceptor.cs +@@ -36,7 +36,16 @@ internal sealed class HandWrittenOrderingInterceptor : IQueryExpressionIntercept + { + HandWrittenOrdering.RefuseIn(queryExpression); + +- return queryExpression; ++ return new ContainsOfValueObjects().Visit(queryExpression); ++ } ++ ++ /// <summary>A value object's <c>Contains</c> in a collection, as a match of nothing.</summary> ++ private sealed class ContainsOfValueObjects : ExpressionVisitor ++ { ++ protected override Expression VisitMethodCall(MethodCallExpression node) => ++ node.Method.Name == nameof(Enumerable.Contains) && node.Arguments.Count > 0 && ValueObjectTypes.IsValueObject(node.Arguments[^1].Type) ++ ? Expression.Constant(false) ++ : base.VisitMethodCall(node); + } + } + diff --git a/tools/probe/corpus/seeds/E13-primitive-collections-element-value.patch b/tools/probe/corpus/seeds/E13-primitive-collections-element-value.patch new file mode 100644 index 00000000..24203343 --- /dev/null +++ b/tools/probe/corpus/seeds/E13-primitive-collections-element-value.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +index 4cbf797..7cd060d 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectTranslators.cs +@@ -120,6 +120,8 @@ internal static class WrappedValue + && (operandMapping.Converter is not { } converter || !ValueObjectMapping.For(valueObject).StoresTheWrappedValue(converter, mapping?.Converter))) + return null; + ++ if (operand is ColumnExpression element && IsCollectionElement(element)) return null; ++ + return operand is ColumnExpression column && !IsCollectionElement(column) + ? new ColumnExpression(column.Name, column.TableAlias, valueType, mapping, column.IsNullable) + : sqlExpressionFactory.Convert(operand, valueType, mapping); diff --git a/tools/probe/corpus/seeds/E14-inheritance-key-generation-tpc-assign-on-sqlite.patch b/tools/probe/corpus/seeds/E14-inheritance-key-generation-tpc-assign-on-sqlite.patch new file mode 100644 index 00000000..99e84eb1 --- /dev/null +++ b/tools/probe/corpus/seeds/E14-inheritance-key-generation-tpc-assign-on-sqlite.patch @@ -0,0 +1,14 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..feb0d14 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -110,8 +110,7 @@ internal sealed class ValueObjectTpcKeyValueGenerationConvention : IModelFinaliz + if (property.GetValueGeneratedConfigurationSource() != ConfigurationSource.Convention) continue; + + var table = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); +- if (table.Name is null || RelationalValueGenerationConvention.GetValueGenerated(property, table) is null) +- property.Builder.ValueGenerated(null); ++ _ = table; + } + } + } diff --git a/tools/probe/corpus/seeds/E14-inheritance-key-generation-tph-tpt-root.patch b/tools/probe/corpus/seeds/E14-inheritance-key-generation-tph-tpt-root.patch new file mode 100644 index 00000000..c2842e41 --- /dev/null +++ b/tools/probe/corpus/seeds/E14-inheritance-key-generation-tph-tpt-root.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..804be76 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -60,6 +60,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + internal static IConventionProperty? GeneratedKey(IConventionEntityType entityType) + { + if (entityType.BaseType is not null || entityType.FindPrimaryKey() is not { Properties: [var property] }) return null; ++ if (entityType.GetDirectlyDerivedTypes().Any() && entityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) return null; + // As EF's own rule: the foreign key that links a TPT table to its base type's is no reason not to + // generate the key, and EntityTypeHierarchyMappingConvention has added it by now. + if (property.GetContainingForeignKeys().Any(foreignKey => !foreignKey.IsBaseLinking())) return null; diff --git a/tools/probe/corpus/seeds/E15-composite-key-as-for-primitives.patch b/tools/probe/corpus/seeds/E15-composite-key-as-for-primitives.patch new file mode 100644 index 00000000..232734c4 --- /dev/null +++ b/tools/probe/corpus/seeds/E15-composite-key-as-for-primitives.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..a58d509 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -59,7 +59,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> + internal static IConventionProperty? GeneratedKey(IConventionEntityType entityType) + { +- if (entityType.BaseType is not null || entityType.FindPrimaryKey() is not { Properties: [var property] }) return null; ++ if (entityType.BaseType is not null || entityType.FindPrimaryKey() is not { Properties: [var property, ..] }) return null; + // As EF's own rule: the foreign key that links a TPT table to its base type's is no reason not to + // generate the key, and EntityTypeHierarchyMappingConvention has added it by now. + if (property.GetContainingForeignKeys().Any(foreignKey => !foreignKey.IsBaseLinking())) return null; diff --git a/tools/probe/corpus/seeds/E16-own-configuration-wins-column-type.patch b/tools/probe/corpus/seeds/E16-own-configuration-wins-column-type.patch new file mode 100644 index 00000000..239fe9da --- /dev/null +++ b/tools/probe/corpus/seeds/E16-own-configuration-wins-column-type.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..e0bab02 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,12 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null && p.GetColumnTypeConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetColumnType(null); + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E16-own-configuration-wins-conversion.patch b/tools/probe/corpus/seeds/E16-own-configuration-wins-conversion.patch new file mode 100644 index 00000000..d2d7a365 --- /dev/null +++ b/tools/probe/corpus/seeds/E16-own-configuration-wins-conversion.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..0f45005 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,12 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null && p.GetValueConverterConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).SetValueConverter((Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter?)null); + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E16-own-configuration-wins-never.patch b/tools/probe/corpus/seeds/E16-own-configuration-wins-never.patch new file mode 100644 index 00000000..4c96dc73 --- /dev/null +++ b/tools/probe/corpus/seeds/E16-own-configuration-wins-never.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..a0f8ac8 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -53,7 +53,7 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + if (GeneratedKey(entityType) is { } property) +- property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ ((IMutableProperty)property).ValueGenerated = ValueGenerated.OnAdd; + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E16-own-configuration-wins-on-add.patch b/tools/probe/corpus/seeds/E16-own-configuration-wins-on-add.patch new file mode 100644 index 00000000..a76b9d10 --- /dev/null +++ b/tools/probe/corpus/seeds/E16-own-configuration-wins-on-add.patch @@ -0,0 +1,18 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..6b023d9 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,13 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ if (entityType.FindPrimaryKey() is { Properties: [var key] } ++ && ValueObjectTypes.Describe(key.ClrType) is { WrappedType: var keyWrapped } && (keyWrapped == typeof(char) || keyWrapped == typeof(Guid))) ++ ((IMutableProperty)key).ValueGenerated = ValueGenerated.Never; + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/E16-own-configuration-wins-optional.patch b/tools/probe/corpus/seeds/E16-own-configuration-wins-optional.patch new file mode 100644 index 00000000..013a1a16 --- /dev/null +++ b/tools/probe/corpus/seeds/E16-own-configuration-wins-optional.patch @@ -0,0 +1,17 @@ +diff --git a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +index 934f884..b72ef00 100644 +--- a/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs ++++ b/src/CodoMetis.TypeKit.EntityFrameworkCore/ValueObjectKeyValueGenerationConvention.cs +@@ -52,8 +52,12 @@ internal sealed class ValueObjectKeyValueGenerationConvention : IModelFinalizing + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) ++ { ++ foreach (var p in entityType.GetDeclaredProperties()) ++ if (!p.IsKey() && ValueObjectTypes.Describe(Nullable.GetUnderlyingType(p.ClrType) ?? p.ClrType) is not null && p.GetIsNullableConfigurationSource() == ConfigurationSource.Explicit) ((IMutableProperty)p).IsNullable = false; + if (GeneratedKey(entityType) is { } property) + property.Builder.ValueGenerated(ValueGenerated.OnAdd); ++ } + } + + /// <summary>The entity type's key, where it is a value object over an integer EF generates a key of.</summary> diff --git a/tools/probe/corpus/seeds/L01-json-bytes-as-a-dictionary-key-too.patch b/tools/probe/corpus/seeds/L01-json-bytes-as-a-dictionary-key-too.patch new file mode 100644 index 00000000..832c2257 --- /dev/null +++ b/tools/probe/corpus/seeds/L01-json-bytes-as-a-dictionary-key-too.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..1990a4a 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -217,7 +217,7 @@ public sealed class GeneratedJsonPlan<T> + /// <exception cref="NotSupportedException">The converter for <typeparamref name="T"/> cannot write it as a property name.</exception> + public void WriteKey(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => + // No null check of the value: it would box a struct, and a key is never null. +- KeyConverter.WriteAsPropertyName(writer, value!, options); ++ writer.WritePropertyName(value!.ToString()!); + + /// <summary> + /// Reads a property name as the serializer reads a dictionary key of type <typeparamref name="T"/>, diff --git a/tools/probe/corpus/seeds/L01-json-bytes-read-as-the-underlying-type-reads.patch b/tools/probe/corpus/seeds/L01-json-bytes-read-as-the-underlying-type-reads.patch new file mode 100644 index 00000000..e1cea816 --- /dev/null +++ b/tools/probe/corpus/seeds/L01-json-bytes-read-as-the-underlying-type-reads.patch @@ -0,0 +1,12 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..906a92b 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -189,6 +189,7 @@ public sealed class GeneratedJsonPlan<T> + public T Read<TValueObject>(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + T? value; ++ if (reader.TokenType == JsonTokenType.String && reader.ValueIsEscaped) throw GeneratedJson.Unreadable<TValueObject, T>(); + + try + { diff --git a/tools/probe/corpus/seeds/L01-json-bytes-the-underlying-types-json.patch b/tools/probe/corpus/seeds/L01-json-bytes-the-underlying-types-json.patch new file mode 100644 index 00000000..a4757804 --- /dev/null +++ b/tools/probe/corpus/seeds/L01-json-bytes-the-underlying-types-json.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..957fcc1 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -60,7 +60,7 @@ public sealed class GeneratedJsonPlan<T> + // flags change what is written; reading a number from a string is decided per token. + _direct = typeInfo.Kind == JsonTypeInfoKind.None ? typeInfo.Converter as JsonConverter<T> : null; + _keyConverter = typeInfo.Converter as JsonConverter<T>; +- _writesAsIs = !IsNumber || (handling & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) == 0; ++ _writesAsIs = true; + _readsAsIs = !IsNumber || handling == JsonNumberHandling.Strict; + + if (_direct is null && options.ReferenceHandler is { } handler && handler != ReferenceHandler.IgnoreCycles && MayHoldReferences(options, typeInfo, [])) diff --git a/tools/probe/corpus/seeds/L02-no-quote-json-never-repeats.patch b/tools/probe/corpus/seeds/L02-no-quote-json-never-repeats.patch new file mode 100644 index 00000000..d563e5b0 --- /dev/null +++ b/tools/probe/corpus/seeds/L02-no-quote-json-never-repeats.patch @@ -0,0 +1,22 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..c2f989a 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -198,7 +198,7 @@ public sealed class GeneratedJsonPlan<T> + } + catch (Exception exception) when (GeneratedJson.IsRefusal(exception)) + { +- throw GeneratedJson.Unreadable<TValueObject, T>(); ++ throw new JsonException(GeneratedJson.Unreadable<TValueObject, T>().Message + " (" + System.Text.Encoding.UTF8.GetString(reader.ValueSpan) + ")"); + } + + return value is null ? throw GeneratedJson.Unreadable<TValueObject, T>() : value; +@@ -240,7 +240,7 @@ public sealed class GeneratedJsonPlan<T> + } + catch (Exception exception) when (GeneratedJson.IsRefusal(exception)) + { +- throw GeneratedJson.Unreadable<TValueObject, T>(); ++ throw new JsonException(GeneratedJson.Unreadable<TValueObject, T>().Message + " (" + System.Text.Encoding.UTF8.GetString(reader.ValueSpan) + ")"); + } + + return value is null ? throw GeneratedJson.Unreadable<TValueObject, T>() : value; diff --git a/tools/probe/corpus/seeds/L02-no-quote-no-inner-that-quotes.patch b/tools/probe/corpus/seeds/L02-no-quote-no-inner-that-quotes.patch new file mode 100644 index 00000000..50acda27 --- /dev/null +++ b/tools/probe/corpus/seeds/L02-no-quote-no-inner-that-quotes.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +index c734b98..9917c71 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectParsableAspect.cs +@@ -54,7 +54,7 @@ internal sealed class ParsableImplementationArguments + public string InnerParse(string input = "s") + { + var valueType = ValueObjectTypes.SourceName(ValueType); +- var unreadable = $"throw global::CodoMetis.TypeKit.CompilerServices.GeneratedParsing.Unreadable<{ValueObjectTypes.SourceName(ValueObjectType)}, {valueType}>()"; ++ var unreadable = $"throw new global::System.FormatException(global::CodoMetis.TypeKit.CompilerServices.GeneratedParsing.Unreadable<{ValueObjectTypes.SourceName(ValueObjectType)}, {valueType}>().Message, new global::System.FormatException(\"The input '\" + {input}.ToString() + \"' was not in a correct format.\"))"; + const string at = "global::CodoMetis.TypeKit.CompilerServices.GeneratedParsing"; + + return Strategy switch diff --git a/tools/probe/corpus/seeds/L02-no-quote-parsing-never-repeats.patch b/tools/probe/corpus/seeds/L02-no-quote-parsing-never-repeats.patch new file mode 100644 index 00000000..05a5b248 --- /dev/null +++ b/tools/probe/corpus/seeds/L02-no-quote-parsing-never-repeats.patch @@ -0,0 +1,32 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedParsing.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedParsing.cs +index ef70519..2827564 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedParsing.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedParsing.cs +@@ -44,8 +44,11 @@ public static class GeneratedParsing + /// <typeparam name="T">The type to parse.</typeparam> + /// <returns>Whether parsing succeeded.</returns> + public static bool TryParse<T>([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out T result) +- where T : IParsable<T> => +- T.TryParse(s, provider, out result); ++ where T : IParsable<T> ++ { ++ result = T.Parse(s!, provider); ++ return true; ++ } + + /// <summary><see cref="ISpanParsable{TSelf}.TryParse"/> of <typeparamref name="T"/>.</summary> + /// <param name="s">The characters.</param> +@@ -54,8 +57,11 @@ public static class GeneratedParsing + /// <typeparam name="T">The type to parse.</typeparam> + /// <returns>Whether parsing succeeded.</returns> + public static bool TryParseSpan<T>(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out T result) +- where T : ISpanParsable<T> => +- T.TryParse(s, provider, out result); ++ where T : ISpanParsable<T> ++ { ++ result = T.Parse(s, provider); ++ return true; ++ } + + /// <summary><see cref="IUtf8SpanParsable{TSelf}.TryParse"/> of <typeparamref name="T"/>.</summary> + /// <param name="utf8Text">The UTF-8 text.</param> diff --git a/tools/probe/corpus/seeds/L03-one-rule-json-runs-create.patch b/tools/probe/corpus/seeds/L03-one-rule-json-runs-create.patch new file mode 100644 index 00000000..0cff4bfe --- /dev/null +++ b/tools/probe/corpus/seeds/L03-one-rule-json-runs-create.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +index 185f65e..8d0c981 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +@@ -40,7 +40,7 @@ internal sealed partial class ValueObjectAspect + if (meta.CompileTime(kind == ValueObjectKind.Plain)) + return constructor.Invoke(value)!; + +- if (materialize) ++ if (!materialize) + return constructor.Invoke(value)!; + + return ExpressionFactory.Parse( diff --git a/tools/probe/corpus/seeds/L03-one-rule-parsing-runs-create.patch b/tools/probe/corpus/seeds/L03-one-rule-parsing-runs-create.patch new file mode 100644 index 00000000..b49d34d0 --- /dev/null +++ b/tools/probe/corpus/seeds/L03-one-rule-parsing-runs-create.patch @@ -0,0 +1,15 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +index 185f65e..dcebbca 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectAspect.Templates.cs +@@ -60,9 +60,7 @@ internal sealed partial class ValueObjectAspect + if (meta.CompileTime(kind == ValueObjectKind.Plain)) + return constructor.Invoke(value)!; + +- return ExpressionFactory.Parse( +- $"global::CodoMetis.TypeKit.CompilerServices.GeneratedFactories.OrFormatException({ValueObjectTypes.SourceName(target)}.Create(value))" +- ).Value!; ++ return constructor.Invoke(value)!; + } + + /// <summary><c>TryParse</c>'s way in. A refusal returns <see langword="false"/>, as <c>TryParse</c> does.</summary> diff --git a/tools/probe/corpus/seeds/L03-one-rule-refusals-name-the-fault.patch b/tools/probe/corpus/seeds/L03-one-rule-refusals-name-the-fault.patch new file mode 100644 index 00000000..78488c80 --- /dev/null +++ b/tools/probe/corpus/seeds/L03-one-rule-refusals-name-the-fault.patch @@ -0,0 +1,33 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedFactories.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedFactories.cs +index 5550a22..1b87150 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedFactories.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedFactories.cs +@@ -35,7 +35,7 @@ public static class GeneratedFactories + { + if (result.TryGetValue(out var value, out var fault)) return value; + +- throw new JsonException($"{typeof(TValueObject).Name} refused the JSON value ({fault}).{Refusals.RefusedValue}"); ++ throw new JsonException($"{typeof(TValueObject).Name} refused the JSON value ({fault.GetType().Name}).{Refusals.RefusedValue}"); + } + + /// <summary>Unwraps what <c>Create</c> accepted from text, or throws the exception parsing reports.</summary> +@@ -50,7 +50,7 @@ public static class GeneratedFactories + { + if (result.TryGetValue(out var value, out var fault)) return value; + +- throw new FormatException($"{typeof(TValueObject).Name} refused the input ({fault}).{Refusals.RefusedValue}"); ++ throw new FormatException($"{typeof(TValueObject).Name} refused the input ({fault.GetType().Name}).{Refusals.RefusedValue}"); + } + + /// <summary> +@@ -75,8 +75,8 @@ public static class GeneratedFactories + + throw new InvalidOperationException( + source is null +- ? $"{typeof(TValueObject).Name} refused a value the call site declared known-good ({fault}).{Refusals.RefusedKnownGood}" +- : $"{typeof(TValueObject).Name} refused {source}, which the call site declared known-good ({fault}).{Refusals.RefusedKnownGood}" ++ ? $"{typeof(TValueObject).Name} refused a value the call site declared known-good ({fault.GetType().Name}).{Refusals.RefusedKnownGood}" ++ : $"{typeof(TValueObject).Name} refused {source}, which the call site declared known-good ({fault.GetType().Name}).{Refusals.RefusedKnownGood}" + ); + } + diff --git a/tools/probe/corpus/seeds/L03-one-rule-tryfrom-runs-create.patch b/tools/probe/corpus/seeds/L03-one-rule-tryfrom-runs-create.patch new file mode 100644 index 00000000..527dc617 --- /dev/null +++ b/tools/probe/corpus/seeds/L03-one-rule-tryfrom-runs-create.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +index 07a7ecb..fb43ddf 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +@@ -32,7 +32,7 @@ internal sealed partial class ValueObjectContractAspect + /// </summary> + [Template] + public static dynamic TryFromTemplate(dynamic? value, [CompileTime] INamedType target) => +- ExpressionFactory.Parse($"global::CodoMetis.TypeKit.Result.ToOption({ValueObjectTypes.SourceName(target)}.Create(value))").Value!; ++ ExpressionFactory.Parse($"global::CodoMetis.TypeKit.Option.Some({ValueObjectTypes.SourceName(target)}.__FromJson(value, true))").Value!; + + /// <summary><c>Create</c> of the value this instance holds.</summary> + [Template] diff --git a/tools/probe/corpus/seeds/L04-round-trip-invariant-whatever-the-culture.patch b/tools/probe/corpus/seeds/L04-round-trip-invariant-whatever-the-culture.patch new file mode 100644 index 00000000..981bdc78 --- /dev/null +++ b/tools/probe/corpus/seeds/L04-round-trip-invariant-whatever-the-culture.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedFormatting.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedFormatting.cs +index 8f93241..fe989ca 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedFormatting.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedFormatting.cs +@@ -27,7 +27,7 @@ public static class GeneratedFormatting + /// <typeparam name="T">The type to format.</typeparam> + /// <returns>The formatted text.</returns> + public static string ToString<T>(T value, string? format, IFormatProvider? provider) where T : IFormattable => +- value.ToString(format, provider); ++ value.ToString(format, System.Globalization.CultureInfo.CurrentCulture); + + /// <summary><see cref="ISpanFormattable.TryFormat"/> of <typeparamref name="T"/>.</summary> + /// <param name="value">The value.</param> diff --git a/tools/probe/corpus/seeds/L04-round-trip-json-refusal-is-a-json-exception.patch b/tools/probe/corpus/seeds/L04-round-trip-json-refusal-is-a-json-exception.patch new file mode 100644 index 00000000..ead87060 --- /dev/null +++ b/tools/probe/corpus/seeds/L04-round-trip-json-refusal-is-a-json-exception.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +index 8597206..7515d92 100644 +--- a/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs ++++ b/src/CodoMetis.TypeKit/CompilerServices/GeneratedJsonPlan.cs +@@ -198,7 +198,7 @@ public sealed class GeneratedJsonPlan<T> + } + catch (Exception exception) when (GeneratedJson.IsRefusal(exception)) + { +- throw GeneratedJson.Unreadable<TValueObject, T>(); ++ throw; + } + + return value is null ? throw GeneratedJson.Unreadable<TValueObject, T>() : value; diff --git a/tools/probe/corpus/seeds/L04-round-trip-text-reads-back.patch b/tools/probe/corpus/seeds/L04-round-trip-text-reads-back.patch new file mode 100644 index 00000000..d3c462d2 --- /dev/null +++ b/tools/probe/corpus/seeds/L04-round-trip-text-reads-back.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs +index 232ed9d..0ae0961 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs +@@ -33,7 +33,7 @@ internal sealed partial class ValueObjectTypeConverterAspect + // ReSharper disable once ConvertIfStatementToNullCoalescingExpression + if (culture == null) + { +- provider = CultureInfo.InvariantCulture; ++ provider = CultureInfo.CurrentCulture; + } + else + { diff --git a/tools/probe/corpus/seeds/L04-round-trip-the-type-converter-writes-the-same.patch b/tools/probe/corpus/seeds/L04-round-trip-the-type-converter-writes-the-same.patch new file mode 100644 index 00000000..430ac0d5 --- /dev/null +++ b/tools/probe/corpus/seeds/L04-round-trip-the-type-converter-writes-the-same.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs +index 232ed9d..091a954 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectTypeConverterAspect.Templates.cs +@@ -70,7 +70,7 @@ internal sealed partial class ValueObjectTypeConverterAspect + // ReSharper disable once ConvertIfStatementToNullCoalescingExpression + if (culture == null) + { +- provider = CultureInfo.InvariantCulture; ++ provider = CultureInfo.CurrentCulture; + } + else + { diff --git a/tools/probe/corpus/seeds/L05-equality-equality-is-the-underlying-values.patch b/tools/probe/corpus/seeds/L05-equality-equality-is-the-underlying-values.patch new file mode 100644 index 00000000..59cc1aee --- /dev/null +++ b/tools/probe/corpus/seeds/L05-equality-equality-is-the-underlying-values.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +index 07a7ecb..c9468d4 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.Templates.cs +@@ -58,7 +58,7 @@ internal sealed partial class ValueObjectContractAspect + [Template] + public int GetHashCodeTemplate([CompileTime] INamedType valueType) => + (int)ExpressionFactory.Parse( +- $"global::System.Collections.Generic.EqualityComparer<{ValueObjectTypes.SourceName(valueType)}>.Default.GetHashCode(this._value!)" ++ $"global::System.StringComparer.Ordinal.GetHashCode(global::System.Convert.ToString((object?)this._value, global::System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty)" + ).Value!; + + /// <summary><c>Create</c> with the fault carried into the throw.</summary> diff --git a/tools/probe/corpus/seeds/L05-equality-ordering-follows-the-underlying-type.patch b/tools/probe/corpus/seeds/L05-equality-ordering-follows-the-underlying-type.patch new file mode 100644 index 00000000..3abfa58b --- /dev/null +++ b/tools/probe/corpus/seeds/L05-equality-ordering-follows-the-underlying-type.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectComparableAspect.Templates.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectComparableAspect.Templates.cs +index 3936706..4d21804 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectComparableAspect.Templates.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectComparableAspect.Templates.cs +@@ -41,7 +41,7 @@ internal sealed partial class ValueObjectComparableAspect + // The wrapped type is never an open generic (ValueObjectAspect refuses those). + string typeName = meta.CompileTime(ValueObjectTypes.SourceName(tag.ValueType)); + return (int)ExpressionFactory.Parse( +- $"global::System.Collections.Generic.Comparer<{typeName}>.Default.Compare(this.Value, other.Value)", ++ $"global::System.Collections.Generic.Comparer<{typeName}>.Default.Compare(other.Value, this.Value)", + TypeFactory.GetType(SpecialType.Int32), + false + ).Value!; diff --git a/tools/probe/corpus/seeds/L05-equality-through-every-base.patch b/tools/probe/corpus/seeds/L05-equality-through-every-base.patch new file mode 100644 index 00000000..9239ea5d --- /dev/null +++ b/tools/probe/corpus/seeds/L05-equality-through-every-base.patch @@ -0,0 +1,13 @@ +diff --git a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs +index c5f440a..1bddab3 100644 +--- a/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs ++++ b/src/CodoMetis.TypeKit.Generators/ValueObjectContractAspect.cs +@@ -98,7 +98,7 @@ internal sealed partial class ValueObjectContractAspect : TypeAspect + { + builder.ImplementInterface(TypeFactory.GetNamedType(typeof(IReimplementedEquatable<>)).MakeGenericInstance(type), OverrideStrategy.Ignore); + +- if (type.Equals(direct) || !ExplicitlyImplementsEquatableBetween(direct, type, equatable.MakeGenericInstance(type))) continue; ++ if (type.Equals(direct) || ExplicitlyImplementsEquatableBetween(direct, type, equatable.MakeGenericInstance(type))) continue; + + // One the value object declares itself implements the interface already, as C# maps it. + if (builder.Target.Methods.Any(method => method is { Name: nameof(Equals), Parameters: [{ } other] } && other.Type.ToNonNullable().Equals(type))) continue; diff --git a/tools/probe/corpus/seeds/L06-off-the-wire-never-read.patch b/tools/probe/corpus/seeds/L06-off-the-wire-never-read.patch new file mode 100644 index 00000000..768bb5d8 --- /dev/null +++ b/tools/probe/corpus/seeds/L06-off-the-wire-never-read.patch @@ -0,0 +1,21 @@ +diff --git a/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs b/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs +index ebe7eb6..7cbbe4e 100644 +--- a/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs ++++ b/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs +@@ -107,14 +107,14 @@ public sealed class NotWireTypeJsonConverterFactory : JsonConverterFactory + public override bool CanConvert(Type typeToConvert) => IsRefused(typeToConvert); + + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => +- throw new NotSupportedException(Message(typeToConvert)); ++ throw new JsonException(Message(typeToConvert)); + + public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) => + throw new NotSupportedException(Message(refused)); + + /// <summary>A dictionary key too, rather than the serializer's message about <see cref="object"/> keys.</summary> + public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => +- throw new NotSupportedException(Message(typeToConvert)); ++ throw new JsonException(Message(typeToConvert)); + + public override void WriteAsPropertyName(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => + throw new NotSupportedException(Message(refused)); diff --git a/tools/probe/corpus/seeds/L06-off-the-wire-never-written.patch b/tools/probe/corpus/seeds/L06-off-the-wire-never-written.patch new file mode 100644 index 00000000..ca42b8b7 --- /dev/null +++ b/tools/probe/corpus/seeds/L06-off-the-wire-never-written.patch @@ -0,0 +1,18 @@ +diff --git a/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs b/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs +index ebe7eb6..a915ada 100644 +--- a/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs ++++ b/src/CodoMetis.TypeKit/NotWireTypeJsonConverterFactory.cs +@@ -109,8 +109,11 @@ public sealed class NotWireTypeJsonConverterFactory : JsonConverterFactory + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + throw new NotSupportedException(Message(typeToConvert)); + +- public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) => +- throw new NotSupportedException(Message(refused)); ++ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) ++ { ++ writer.WriteStartObject(); ++ writer.WriteEndObject(); ++ } + + /// <summary>A dictionary key too, rather than the serializer's message about <see cref="object"/> keys.</summary> + public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => diff --git a/tools/probe/generators/Directory.Build.props b/tools/probe/generators/Directory.Build.props new file mode 100644 index 00000000..26b81c53 --- /dev/null +++ b/tools/probe/generators/Directory.Build.props @@ -0,0 +1,8 @@ +<Project> + <!-- + The harness generators are file-based apps (design/adr/0092, form 1), not tool projects: they take the + repository's settings (warnings are errors, nullable, C# 14) and none of tools/Directory.Build.*, which + compile tools/Shared into a tool and stamp its sources for a project file a file-based app does not have. + --> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove('$(MSBuildThisFileDirectory)', 'CodoMetis.TypeKit.slnx'))/Directory.Build.props" /> +</Project> diff --git a/tools/probe/generators/Directory.Build.targets b/tools/probe/generators/Directory.Build.targets new file mode 100644 index 00000000..d4ec8c75 --- /dev/null +++ b/tools/probe/generators/Directory.Build.targets @@ -0,0 +1,3 @@ +<Project> + <!-- Here so that tools/Directory.Build.targets, the tool projects' source stamp, is not imported (see Directory.Build.props). --> +</Project> diff --git a/tools/probe/generators/README.md b/tools/probe/generators/README.md new file mode 100644 index 00000000..be768ee4 --- /dev/null +++ b/tools/probe/generators/README.md @@ -0,0 +1,85 @@ +# Harness generators + +The apps here write the generated declarations of the probe harness, every +`tools/probe/corpus/harness/*.g.cs`. Each file's first line names the app that wrote it. Never edit one by +hand: change the app, or its input, and run it again. + +| App | Mode | Writes | From | +|---|---|---|---| +| `shapes.cs` | | `Shapes.g.cs`: a value object per `shape.kind` x `shape.wrapped`, its plain twin, the registry, the polymorphic stand-ins, `ProbeContext` | the axes file's `shape.kind`, `shape.wrapped`, `shape.position` and `shape.containing`; L06's rows for its carriers | +| `containers.cs` | | `Containers.g.cs`: a generic container per `shape.containing` and member style | the axes file's `shape.containing` | +| `ef.cs` | `catalogue` | `EfShapes.g.cs`: a value object per (kind, wrapped, site), `EfRegistry` | E01's first 90 rows, E02's first 108 | +| `ef.cs` | `top-level` | `EfShapes3.g.cs`: every kind at top level for each wrapped type of the catalogue, `Reg3` | the same rows | +| `ef.cs` | `nested` | `EfShapes4.g.cs`: every kind at each nested site for each wrapped type of the catalogue, `Reg4` | the same rows | +| `ef.cs` | `keys` | `KeyShapes.g.cs`: a value object per (kind, wrapped), `KeyRegistry` | E04's rows | +| `json-family.cs` | `stand-ins` | `JsonFamilyShapes.g.cs`: L06's polymorphic stand-ins over `Option` and `Result` | the table in the app | +| `json-family.cs` | `l05` | `L05Shapes.g.cs`: a value object or closed set per (kind, wrapped, inheritance, site), `L05Registry` | L05's rows | +| `decls.cs` | `C09` | `C09Shapes.g.cs`: each row's base records and value object, `C09Table` | C09's rows | +| `decls.cs` | `C11` | `C11Shapes.g.cs`: each row's value object and its control, `C11Table` | C11's rows | +| `faults.cs` | | `FaultClaimControllers.g.cs`: the controllers of C04, C06, C14 and C15, `FxCtl` | the table in the app | +| `rules.cs` | | `RuleControllers.g.cs`: the controllers of C03 and C05, one action per arity and response variant | the table in the app | + +The axes file is `.claude/probe-axes.md`, read from its `json probe-axes` block. A rows file is +`tools/probe/corpus/rows/<probe>.json.gz`, gzipped JSON, one per probe with its claim inside, which `prober rows --probe P` +writes. A value the app's +tables do not know (a new `shape.wrapped`, say) is refused with exit 2 and the value named, never skipped. + +## Running one + +From the repository root, each app takes its inputs and `--out` by name: + +```bash +dotnet run tools/probe/generators/shapes.cs -- --axes .claude/probe-axes.md --l06 tools/probe/corpus/rows/L06OffTheWireProbe.json.gz --out tools/probe/corpus/harness/Shapes.g.cs +``` + +```bash +dotnet run tools/probe/generators/decls.cs -- C09 --rows tools/probe/corpus/rows/C09BaseRecordEqualityProbe.json.gz --out tools/probe/corpus/harness/C09Shapes.g.cs +``` + +The arguments of every file are in the table `Generators` of +`test/CodoMetis.TypeKit.Conventions.Tests/GeneratedHarnessTests.cs`, the one place the mapping lives, and +the head of each app shows how to run it. Run the app again after `prober rows` rewrites a rows file it reads, +after a change to the axes file's `shape.*` axes, and after a change to the app, and commit the output with +the change. + +## The twin and the context + +`shapes.cs` writes each value object's twin beside it: the wrapped type at the value object's nullability, so a class value object +over a struct faces the struct made nullable (`int?` for a sealed record class over `int`) and the rest face the wrapped type itself. +The closed set's twin is a string, its member's name. The registry's `Plain` is the twin, and the harness builds both sides of a +comparison in one place (`Sides.Twins`), which refuses, as a harness error, a twin and a value object that do not hold the same shape. + +`ProbeContext` is the one source-generated context: every value object and every twin, at the root and at every `shape.position` in every +`shape.containing`, and the `Option` and `Result` carriers L06's rows hold under the context. Under +`json.TypeInfoResolver=source-generated-context` a row resolves through it alone, with no reflection behind it, and a shape it does not +list is a harness error; `json.TypeInfoResolverChain=context-then-default` is the configuration with the reflection fallback. It is one +declaration because System.Text.Json's generator fails on a second partial part that carries `[JsonSerializable]`. The carriers come from +L06's rows, so run `shapes.cs` again after `prober rows --probe L06OffTheWireProbe`: an `Option` and two `Result`s over every value object +at every position would be some 16000 more types. Measured 2026-10-17 on Linux x64, a rebuild of the Samples after a harness change took +38 s before the context listed the positions, 73 s with it (8974 types: every shape and twin, the struct value objects made +nullable, L06's carriers), and 3 min 49 s in a trial that listed every carrier at every position too (23266 types). + +`ef.cs` reads `--rows <path>` once per file; `--first <n>` after one reads only its first n rows. The +catalogue is E01's and E02's first round of rows: the rows appended since (old rows kept) run on the +complete grids of `top-level` and `nested`, which read the same rows to learn the catalogue's wrapped types, +in its order, and which of them it validates. + +## The guard + +`GeneratedHarnessTests` in the conventions tests runs every app into a temporary directory with the +arguments of its table and fails on any difference from the committed file, line endings aside. It also +fails on a `.g.cs` in the harness that the table does not list. The test takes about 13 seconds, warm or cold +(measured 2026-10-16 on macOS arm64: each `dotnet run` costs about a second, a first build little more). + +The apps are file-based apps, the first form of +[decision 92](../../../design/adr/0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md): one file each, BCL +only, no tests of their own. The conventions test is their guard, and it compares outputs, so it sees what a +change does to the harness rather than how the app does it. An app that writes one declaration per distinct +shape (`ef.cs`, `json-family.cs`'s `l05`, `shapes.cs`'s carriers) writes the same file without a row whose shape another row repeats; +`decls.cs` writes one per row. + +They run inside the repository, so `global.json` and the root `Directory.Build.props` apply (warnings are +errors). This directory's `Directory.Build.props` and `Directory.Build.targets` keep `tools/Directory.Build.*` +out: those compile `tools/Shared` into a tool project and stamp its sources for a project file a file-based +app does not have. Never start two copies of one app at once on a cold build cache: the first runs race on it +(decision 92's evidence), which is why the test runs the files one after another. diff --git a/tools/probe/generators/containers.cs b/tools/probe/generators/containers.cs new file mode 100644 index 00000000..71f744f1 --- /dev/null +++ b/tools/probe/generators/containers.cs @@ -0,0 +1,78 @@ +// Writes tools/probe/corpus/harness/Containers.g.cs: for each shape.containing of the axes file, one generic +// container per member style (P property, F field, C constructor parameter, R required init). The position +// (list, dictionary, ...) is the member's declared type, which the probe chooses. +// +// dotnet run tools/probe/generators/containers.cs -- --axes .claude/probe-axes.md --out tools/probe/corpus/harness/Containers.g.cs +// +// GeneratedHarnessTests in the conventions tests holds the committed file to this output. +using System.Text; +using System.Text.Json.Nodes; + +try +{ + var containing = Values(AxesBlock(Arg(args, "--axes")), "shape.containing"); + File.WriteAllText(Arg(args, "--out"), Generate(containing), new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"containers.cs: {e.Message}"); + return 2; +} + +static string Generate(IReadOnlyList<string> containing) +{ + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/containers.cs", + "#nullable disable\n#pragma warning disable CS8618, CS1591, CS0649", + "using System.Text.Json.Serialization;", + "namespace Probes.Shapes;", + }; + foreach (var c in containing) + { + var n = c.Replace('-', '_'); + // the declaration keyword and the type parameters; a record class takes its constructor parameter positionally + var (keyword, parameters) = c switch + { + "class" => ("class", "<M>"), + "record-class" => ("record class", "<M>"), + "struct" => ("struct", "<M>"), + "record-struct" => ("record struct", "<M>"), + "generic-type" => ("class", "<M, U>"), + _ => throw new GeneratorError($"shape.containing {c} has no container here"), + }; + var name = $"Cn_{n}_"; + o.Add($"public {keyword} {name}P{parameters} {{ public M P {{ get; set; }} }}"); + o.Add($"public {keyword} {name}F{parameters} {{ public M F; }}"); + o.Add(c == "record-class" + ? $"public {keyword} {name}C{parameters}(M P);" + : $"public {keyword} {name}C{parameters} {{ public M P {{ get; }} [JsonConstructor] public {name}C(M p) {{ P = p; }} }}"); + o.Add($"public {keyword} {name}R{parameters} {{ public required M P {{ get; init; }} }}"); + } + return string.Join("\n", o) + "\n"; +} + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +static JsonObject AxesBlock(string path) +{ + const string marker = "```json probe-axes"; + var text = File.ReadAllText(path); + var at = text.IndexOf(marker, StringComparison.Ordinal); + if (at < 0) throw new GeneratorError($"{path} has no {marker} block"); + var body = text[(at + marker.Length)..]; + return JsonNode.Parse(body[..body.IndexOf("```", StringComparison.Ordinal)]) as JsonObject + ?? throw new GeneratorError($"the {marker} block of {path} is not a JSON object"); +} + +static List<string> Values(JsonObject block, string axis) => + block["axes"]?[axis] is JsonArray values + ? [.. values.Select(v => v?.GetValue<string>() ?? throw new GeneratorError($"{axis} holds a null"))] + : throw new GeneratorError($"the axes file has no axis {axis}"); + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/decls.cs b/tools/probe/generators/decls.cs new file mode 100644 index 00000000..e219a391 --- /dev/null +++ b/tools/probe/generators/decls.cs @@ -0,0 +1,261 @@ +// Writes the declarations the C09 and C11 probes build, one value object per row of the probe's rows file: +// +// C09 C09Shapes.g.cs each row's base records (their own equality under base-equality) and the value object over them +// C11 C11Shapes.g.cs each row's value object over its parse-shape's wrapped type, with a control that parses where one exists +// +// dotnet run tools/probe/generators/decls.cs -- C09 --rows tools/probe/corpus/rows/C09BaseRecordEqualityProbe.json.gz \ +// --out tools/probe/corpus/harness/C09Shapes.g.cs +// dotnet run tools/probe/generators/decls.cs -- C11 --rows tools/probe/corpus/rows/C11ParseShapesBuildProbe.json.gz \ +// --out tools/probe/corpus/harness/C11Shapes.g.cs +// +// A row's excluded values are replaced by the nearest that exists; the table names the effective settings and the row's +// own key, which the probe checks against the rows file it runs (a stale file is a harness error). A rows file is gzipped +// JSON, written by `prober rows`. GeneratedHarnessTests in the conventions tests holds each file to this output. +using System.IO.Compression; +using System.Text; +using System.Text.Json.Nodes; + +try +{ + var mode = args.Length > 0 ? args[0] : throw new GeneratorError("a mode is required: C09 or C11"); + var path = Arg(args, "--rows"); + var (claim, rows) = Rows(path); + if (!claim.StartsWith(mode + "-", StringComparison.Ordinal)) throw new GeneratorError($"mode {mode} reads {mode}'s rows, not {claim}'s"); + var text = mode switch + { + "C09" => C09(Path.GetFileName(path), rows), + "C11" => C11(Path.GetFileName(path), rows), + _ => throw new GeneratorError($"unknown mode {mode}"), + }; + File.WriteAllText(Arg(args, "--out"), text, new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"decls.cs: {e.Message}"); + return 2; +} + +static string Head(string file, string ns) => + $"// <auto-generated/> by tools/probe/generators/decls.cs from rows/{file}: one value object per row. Do not edit; regenerate with the rows.\n" + + "#nullable enable\n" + + "// CMTK1023: the bases' own equality is the point (C09) or beside it (C11); CS8851: a base's Equals without GetHashCode, on purpose.\n" + + "#pragma warning disable CMTK1023, CS8851, CS1591\n" + + "using CodoMetis.TypeKit;\n" + + "using CodoMetis.TypeKit.ValueObjects;\n" + + $"namespace Probes.Build.{ns};\n"; + +// The row's own key: every axis=value, sorted by axis. +static string Key(Dictionary<string, string> row) => string.Join(";", row.Keys.Order(StringComparer.Ordinal).Select(k => $"{k}={row[k]}")); + +// The declarations at the row's site, and the qualifier that reaches them from the namespace. +static (string Text, string Qualifier) AtSite(string site, int idx, string inner) => site switch +{ + "top-level" => (inner, ""), + "nested-in-class" => ($"public static partial class O{idx}\n{{\n{inner}}}\n", $"O{idx}."), + "nested-in-struct" => ($"public partial struct O{idx}\n{{\n{inner}}}\n", $"O{idx}."), + "nested-two-levels" => ($"public static partial class O{idx}\n{{\npublic sealed partial class P{idx}\n{{\n{inner}}}\n}}\n", $"O{idx}.P{idx}."), + _ => throw new GeneratorError($"shape.site {site} has no site here"), +}; + +// How many base records an inheritance puts under the value object. +static int Depth(string inheritance) => inheritance switch +{ + "base-depth-2" => 2, + "base-depth-3" => 3, + "base-depth-1" or "base-explicit-IEquatable" or "explicit-interface-member" => 1, + _ => 0, +}; + +static string Record(string name, List<string> parents, List<string> members) => + $"public abstract record {name}" + (parents.Count > 0 ? " : " + string.Join(", ", parents) : "") + " { " + string.Join(" ", members) + " }"; + +static string Substitutions(List<(string Axis, string Value)> subs) => string.Join(", ", subs.Select(s => $"(\"{s.Axis}\", \"{s.Value}\")")); + +static string Table(string name, string caseType, List<string> table) => + $"public static partial class {name}\n{{\n public static readonly {caseType}[] Cases =\n [\n" + string.Join("\n", table) + "\n ];\n}\n"; + +static string C09(string file, List<Dictionary<string, string>> rows) +{ + var o = new List<string> { Head(file, "C09") }; + var table = new List<string>(); + for (var idx = 0; idx < rows.Count; idx++) + { + var row = rows[idx]; + var subs = new List<(string, string)>(); + var kind = row["shape.kind"]; + if (kind is "record-struct" or "closed-set") { subs.Add(("shape.kind", "sealed-record-class")); kind = "sealed-record-class"; } + else if (kind == "validated-struct") { subs.Add(("shape.kind", "validated-class")); kind = "validated-class"; } + var site = row["shape.site"]; + if (site == "in-generic-type") { subs.Add(("shape.site", "nested-in-class")); site = "nested-in-class"; } + var inh = row["shape.inheritance"]; + if (inh == "none") { subs.Add(("shape.inheritance", "base-depth-1")); inh = "base-depth-1"; } + var eq = row["base-equality"]; + var w = WrappedType(row["shape.wrapped"]); + + var n = Depth(inh); + var body = new List<string>(); + if (inh == "explicit-interface-member") body.Add($"public interface IMark{idx} {{ int Mark(); }}"); + for (var d = n; d >= 1; d--) + { + var name = $"B{idx}_{d}"; + var parents = d == n ? new List<string>() : [$"B{idx}_{d + 1}"]; + var members = new List<string>(); + if (d == n) + { + // the deepest base carries the row's own equality + if (eq == "override-Equals") members.Add($"public virtual bool Equals({name}? other) => ReferenceEquals(this, other);"); + else if (eq == "override-GetHashCode") members.Add("public override int GetHashCode() => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this);"); + else if (eq == "explicit-IEquatable") + { + parents.Add($"System.IEquatable<{name}>"); + members.Add($"bool System.IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"); + } + } + if (d == 1 && inh == "base-explicit-IEquatable" && !(eq == "explicit-IEquatable" && n == 1)) + { + parents.Add($"System.IEquatable<{name}>"); + members.Add($"bool System.IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"); + } + if (d == 1 && inh == "explicit-interface-member") + { + parents.Add($"IMark{idx}"); + members.Add($"int IMark{idx}.Mark() => 1;"); + } + body.Add(Record(name, parents, members)); + } + var v = $"V{idx}"; + string make; + if (kind == "validated-class") + { + body.Add($"public sealed partial record {v} : B{idx}_1, IValidatedValue<{v}, {w}, global::Probes.Shapes.ShapeFault>\n{{\n" + + $" public static Result<{v}, global::Probes.Shapes.ShapeFault> Create({w} value) => new {v}(value);\n}}"); + make = $"{v}.FromKnownGood(({w})x)"; + } + else + { + body.Add($"public sealed partial record {v} : B{idx}_1, IValue<{w}>;"); + make = $"{v}.From(({w})x)"; + } + var (text, q) = AtSite(site, idx, string.Join("\n", body) + "\n"); + o.Add($"// row {idx}\n" + text); + var bases = string.Join(", ", Enumerable.Range(1, n).Select(d => $"typeof({q}B{idx}_{d})")); + table.Add($" new({idx}, \"{Key(row)}\", \"{kind}\", \"{site}\", \"{inh}\", typeof({q}{v}), [{bases}], x => {q}{make}, [{Substitutions(subs)}]),"); + } + o.Add(Table("C09Table", "C09Case", table)); + return string.Join("\n", o); +} + +static string C11(string file, List<Dictionary<string, string>> rows) +{ + // A parse shape's control (a wrapped type of the same shape that parses) and its odd wrapped type. + var control = new Dictionary<string, string>(StringComparer.Ordinal) + { + ["implicit-result"] = "global::Probes.Build.C11ParsedShape", + ["ref-text"] = "global::Probes.Build.C11ParsesFromIn", + ["abstract-string-ctor"] = "global::Probes.Build.C11ConstructedConcrete", + }; + var odd = new Dictionary<string, string>(StringComparer.Ordinal) + { + ["implicit-result"] = "global::Probes.Build.C11ParsesToInt", + ["abstract-string-ctor"] = "global::Probes.Build.C11AbstractText", + }; + const string fault = "global::Probes.Shapes.ShapeFault"; + + var o = new List<string> { Head(file, "C11") }; + var table = new List<string>(); + for (var idx = 0; idx < rows.Count; idx++) + { + var row = rows[idx]; + var subs = new List<(string, string)>(); + var kind = row["shape.kind"]; + if (kind == "closed-set") { subs.Add(("shape.kind", "sealed-record-class")); kind = "sealed-record-class"; } + var site = row["shape.site"]; + if (site == "in-generic-type") { subs.Add(("shape.site", "nested-in-class")); site = "nested-in-class"; } + var inh = row["shape.inheritance"]; + var isStruct = kind is "record-struct" or "validated-struct"; + if (isStruct && inh is not ("none" or "explicit-interface-member")) { subs.Add(("shape.inheritance", "none")); inh = "none"; } + var shape = row["parse-shape"]; + var w = shape switch + { + "ordinary" => WrappedType(row["shape.wrapped"]), + "ref-text" => idx % 2 == 0 ? "global::Probes.Build.C11ParsesByRef" : "global::Probes.Build.C11ConstructedByRef", + _ => odd.TryGetValue(shape, out var t) ? t : throw new GeneratorError($"parse-shape {shape} has no wrapped type here"), + }; + var n = isStruct || inh == "explicit-interface-member" ? 0 : Depth(inh); + var body = new List<string>(); + if (inh == "explicit-interface-member") body.Add($"public interface IMark{idx} {{ int Mark(); }}"); + + void Declare(string v, string b, string wrapped) + { + var mark = inh == "explicit-interface-member" ? $", IMark{idx}" : ""; + for (var d = n; d >= 1; d--) + { + var name = $"{b}{idx}_{d}"; + var parents = d == n ? new List<string>() : [$"{b}{idx}_{d + 1}"]; + var members = new List<string>(); + if (d == 1 && inh == "base-explicit-IEquatable") + { + parents.Add($"System.IEquatable<{name}>"); + members.Add($"bool System.IEquatable<{name}>.Equals({name}? other) => ReferenceEquals(this, other);"); + } + body.Add(Record(name, parents, members)); + } + var @base = n > 0 ? $"{b}{idx}_1, " : ""; + var m = mark != "" ? $" int IMark{idx}.Mark() => 1;" : ""; + body.Add(kind switch + { + "record-struct" => $"public readonly partial record struct {v} : IValue<{wrapped}>{mark} {{{m} }}", + "sealed-record-class" => $"public sealed partial record {v} : {@base}IValue<{wrapped}>{mark} {{{m} }}", + "validated-struct" => $"public readonly partial record struct {v} : IValidatedValue<{v}, {wrapped}, {fault}>{mark}\n{{\n" + + $" public static Result<{v}, {fault}> Create({wrapped} value) => new {v}(value);{m}\n}}", + "validated-class" => $"public sealed partial record {v} : {@base}IValidatedValue<{v}, {wrapped}, {fault}>{mark}\n{{\n" + + $" public static Result<{v}, {fault}> Create({wrapped} value) => new {v}(value);{m}\n}}", + _ => throw new GeneratorError($"shape.kind {kind} has no declaration here"), + }); + } + + var v = $"V{idx}"; + Declare(v, "B", w); + var c = control.GetValueOrDefault(shape); + if (c is not null) Declare($"VC{idx}", "BC", c); + var (text, q) = AtSite(site, idx, string.Join("\n", body) + "\n"); + o.Add($"// row {idx}\n" + text); + var ctl = c is not null ? $"typeof({q}VC{idx}), typeof({c})" : "null, null"; + table.Add($" new({idx}, \"{Key(row)}\", \"{kind}\", \"{site}\", \"{inh}\", typeof({q}{v}), typeof({w}), {ctl}, [{Substitutions(subs)}]),"); + } + o.Add(Table("C11Table", "C11Case", table)); + return string.Join("\n", o); +} + +static string WrappedType(string key) => key switch +{ + "string" => "string", "Guid" => "global::System.Guid", "int" => "int", "long" => "long", "byte" => "byte", "ulong" => "ulong", + "decimal" => "decimal", "double" => "double", "bool" => "bool", "char" => "char", "DateTime" => "global::System.DateTime", + "DateTimeOffset" => "global::System.DateTimeOffset", "DateOnly" => "global::System.DateOnly", "TimeOnly" => "global::System.TimeOnly", + "TimeSpan" => "global::System.TimeSpan", "enum" => "global::Probes.Shapes.PEnum", "flags-enum" => "global::Probes.Shapes.PFlags", + "Uri" => "global::System.Uri", "NodaTime.Instant" => "global::NodaTime.Instant", "own-record-class" => "global::Probes.Shapes.PRec", + _ => throw new GeneratorError($"shape.wrapped {key} is not in decls.cs's table"), +}; + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +// A rows file of `prober rows`: gzipped JSON, {"claim": ..., "rows": [{"row": {axis: value}}, ...]}. +static (string Claim, List<Dictionary<string, string>> Rows) Rows(string path) +{ + using var gzip = new GZipStream(File.OpenRead(path), CompressionMode.Decompress); + var document = JsonNode.Parse(gzip) ?? throw new GeneratorError($"{path} is empty"); + var claim = document["claim"]?.GetValue<string>() ?? throw new GeneratorError($"{path} names no claim"); + var rows = document["rows"] as JsonArray ?? throw new GeneratorError($"{path} has no rows"); + return (claim, + [ + .. rows.Select(r => (r?["row"] as JsonObject ?? throw new GeneratorError($"{path}: an entry without a row")) + .ToDictionary(p => p.Key, p => p.Value?.GetValue<string>() ?? throw new GeneratorError($"{path}: {p.Key} is null"), StringComparer.Ordinal)), + ]); +} + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/ef.cs b/tools/probe/generators/ef.cs new file mode 100644 index 00000000..11108484 --- /dev/null +++ b/tools/probe/generators/ef.cs @@ -0,0 +1,266 @@ +// Writes the EF probes' shapes, one mode per file of tools/probe/corpus/harness: +// +// catalogue EfShapes.g.cs one value object per (shape.kind, shape.wrapped, shape.site) the rows name, and EfRegistry +// keys KeyShapes.g.cs the same for the key claims (a row without shape.site is top-level), and KeyRegistry +// top-level EfShapes3.g.cs every kind at top level for each wrapped type the catalogue holds, and Reg3 +// nested EfShapes4.g.cs every kind at each nested site for each wrapped type the catalogue holds, and Reg4 +// +// dotnet run tools/probe/generators/ef.cs -- catalogue --out tools/probe/corpus/harness/EfShapes.g.cs \ +// --rows tools/probe/corpus/rows/E01ColumnParityProbe.json.gz --first 90 \ +// --rows tools/probe/corpus/rows/E02ValueTranslatesProbe.json.gz --first 108 +// +// --rows names a rows file (gzipped JSON, written by `prober rows`); --first after it reads only its first n rows. +// The catalogue is E01's and E02's first round of rows: the rows appended since run on the complete grids of +// top-level and nested, which take the same rows to learn which wrapped types the catalogue holds, in its order, +// and which of them it validates. GeneratedHarnessTests in the conventions tests holds each file to this output, +// with the arguments it lists. +using System.IO.Compression; +using System.Text; +using System.Text.Json.Nodes; + +try +{ + var mode = args.Length > 0 ? args[0] : throw new GeneratorError("a mode is required: catalogue, keys, top-level or nested"); + var combos = Combos(RowsArguments(args)); + var text = mode switch + { + "catalogue" => Catalogue(combos, keys: false), + "keys" => Catalogue(combos, keys: true), + "top-level" => TopLevel(combos), + "nested" => Nested(combos), + _ => throw new GeneratorError($"unknown mode {mode}"), + }; + File.WriteAllText(Arg(args, "--out"), text, new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"ef.cs: {e.Message}"); + return 2; +} + +// The distinct (kind, wrapped, site) of the rows, sorted ordinally. A closed set and a value object nested in a +// generic type (CMTK1005 at build) have no shape here. +static List<(string Kind, string Wrapped, string Site)> Combos(IEnumerable<Dictionary<string, string>> rows) => +[ + .. rows.Where(r => r["shape.kind"] != "closed-set") + .Select(r => (Kind: r["shape.kind"], Wrapped: r["shape.wrapped"], Site: r.GetValueOrDefault("shape.site", "top-level"))) + .Where(c => c.Site != "in-generic-type") + .Distinct() + .OrderBy(c => c.Kind, StringComparer.Ordinal).ThenBy(c => c.Wrapped, StringComparer.Ordinal).ThenBy(c => c.Site, StringComparer.Ordinal), +]; + +static string Catalogue(List<(string Kind, string Wrapped, string Site)> combos, bool keys) +{ + var o = new List<string> + { + keys + ? "// <auto-generated/> by tools/probe/generators/ef.cs keys; the EF key probes' shape catalogue (shape.kind x shape.wrapped). Do not edit." + : "// <auto-generated/> by tools/probe/generators/ef.cs catalogue; the EF probes' shape catalogue (shape.kind x shape.wrapped x shape.site). Do not edit.", + "#nullable enable", Grids.Pragmas, + "using CodoMetis.TypeKit;", "using CodoMetis.TypeKit.ValueObjects;", "using Probes.Shapes;", "namespace Probes.Ef;", + }; + var prefix = keys ? "K" : ""; + var registry = new List<string>(); + foreach (var (kind, key, site) in combos) + { + var w = Wrapped.Of(key); + var n = $"{(keys ? "K" : "E")}_{Ident(kind)}_{Ident(key)}"; + string path; + switch (site) + { + case "top-level": + o.Add(Declaration(kind, n, w)); + path = n; + break; + case "nested-in-class": + o.Add($"public static class C{prefix}_{n}\n{{\n{Declaration(kind, "V", w)}\n}}"); + path = $"C{prefix}_{n}.V"; + break; + case "nested-in-struct": + o.Add($"public struct S{prefix}_{n}\n{{\n{Declaration(kind, "V", w)}\n}}"); + path = $"S{prefix}_{n}.V"; + break; + case "nested-two-levels": + o.Add($"public static class C2{prefix}_{n}\n{{\n public static class Inner\n {{\n{Declaration(kind, "V", w)}\n }}\n}}"); + path = $"C2{prefix}_{n}.Inner.V"; + break; + default: + throw new GeneratorError($"shape.site {site} has no shape here"); + } + registry.Add($" e.Add(new EfEntry(\"{kind}\", \"{key}\", \"{site}\", typeof({path}), typeof({w.Type}), () => {w.Valid}));"); + } + o.Add(keys + ? "public static partial class KeyRegistry\n{\n" + + " public static readonly List<EfEntry> All = Build();\n" + + " static List<EfEntry> Build() { var e = new List<EfEntry>(); Fill(e); return e; }\n" + + " public static EfEntry Of(Probes.Shapes.Row r) { var k = r[\"shape.kind\"]; var w = r[\"shape.wrapped\"]; return All.First(x => x.Kind == k && x.Wrapped == w); }\n" + + " public static void Fill(List<EfEntry> e)\n {" + : "public static partial class EfRegistry\n{\n static partial void Fill(List<EfEntry> e)\n {"); + o.AddRange(registry); + o.Add(" }\n}"); + return string.Join("\n", o) + "\n"; +} + +static string Declaration(string kind, string name, Wrapped w) => kind switch +{ + "record-struct" => $"public readonly partial record struct {name} : IValue<{w.Type}>;", + "sealed-record-class" => $"public sealed partial record class {name} : IValue<{w.Type}>;", + "validated-struct" or "validated-class" => + $"public {(kind == "validated-struct" ? "readonly partial record struct" : "sealed partial record class")} {name} : IValidatedValue<{name}, {w.Type}, ShapeFault>\n{{\n" + + $" public static Result<{name}, ShapeFault> Create({w.Type} value) =>\n" + + $" EqualityComparer<{w.Type}>.Default.Equals(value, {w.Refused}) ? Result.Error(ShapeFault.Refused) : new {name}(value);\n}}", + _ => throw new GeneratorError($"shape.kind {kind} has no declaration here"), +}; + +// The grids: the catalogue's wrapped types in its order; the validated kinds only for a wrapped type the catalogue validates. +static (List<string> Wrapped, HashSet<string> Validated) Grid(List<(string Kind, string Wrapped, string Site)> combos) => +( + [.. combos.Select(c => c.Wrapped).Distinct()], + [.. combos.Where(c => c.Kind.StartsWith("validated", StringComparison.Ordinal)).Select(c => c.Wrapped)] +); + +static string OneLine(string abbreviation, string name, Wrapped w) => abbreviation switch +{ + "rs" => $"public readonly partial record struct {name} : IValue<{w.Type}>;", + "rc" => $"public sealed partial record class {name} : IValue<{w.Type}>;", + _ => $"public {(abbreviation == "vs" ? "readonly partial record struct" : "sealed partial record class")} {name} : IValidatedValue<{name}, {w.Type}, ShapeFault> " + + $"{{ public static Result<{name}, ShapeFault> Create({w.Type} value) => EqualityComparer<{w.Type}>.Default.Equals(value, {w.Refused}) ? Result.Error(ShapeFault.Refused) : new {name}(value); }}", +}; + +static IEnumerable<(string Kind, string Abbreviation)> KindsFor(string key, HashSet<string> validated) => + Grids.Kinds.Where(k => k.Abbreviation is "rs" or "rc" || validated.Contains(key)); + +static string TopLevel(List<(string Kind, string Wrapped, string Site)> combos) +{ + var (wrapped, validated) = Grid(combos); + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/ef.cs top-level: top-level shapes for every (kind, wrapped) the E03/E09/E10 probes need. Do not edit.", + "#nullable enable", Grids.Pragmas, + "using CodoMetis.TypeKit;", "using CodoMetis.TypeKit.ValueObjects;", "using Probes.Shapes;", "namespace Probes.Ef3;", + }; + foreach (var key in wrapped) + foreach (var (_, p) in KindsFor(key, validated)) + o.Add(OneLine(p, $"X_{p}_{Ident(key)}", Wrapped.Of(key))); + o.Add("public static class Reg3 {"); + o.Add(" public static readonly Dictionary<(string Kind, string Wrapped), (Type Vo, Type Plain)> All = new() {"); + foreach (var key in wrapped) + foreach (var (kind, p) in KindsFor(key, validated)) + o.Add($" [(\"{kind}\", \"{key}\")] = (typeof(X_{p}_{Ident(key)}), typeof({Wrapped.Of(key).Type})),"); + o.Add(" };"); + o.Add("}"); + return string.Join("\n", o) + "\n"; +} + +static string Nested(List<(string Kind, string Wrapped, string Site)> combos) +{ + var (wrapped, validated) = Grid(combos); + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/ef.cs nested: nested-site shapes (nested-in-class, nested-in-struct, nested-two-levels) for every kind x wrapped. Do not edit.", + "#nullable enable", Grids.Pragmas, + "using CodoMetis.TypeKit;", "using CodoMetis.TypeKit.ValueObjects;", "using Probes.Shapes;", "namespace Probes.Ef4;", + }; + var registry = new List<string>(); + foreach (var key in wrapped) + { + var w = Wrapped.Of(key); + foreach (var (kind, p) in KindsFor(key, validated)) + { + var decl = OneLine(p, "V", w); + var n = $"{p}_{Ident(key)}"; + o.Add($"public static class NC_{n} {{ {decl} }}"); + o.Add($"public struct NS_{n} {{ {decl} }}"); + o.Add($"public static class N2_{n} {{ public static class Inner {{ {decl} }} }}"); + foreach (var (site, path) in new[] { ("nested-in-class", $"NC_{n}.V"), ("nested-in-struct", $"NS_{n}.V"), ("nested-two-levels", $"N2_{n}.Inner.V") }) + registry.Add($" [(\"{kind}\", \"{key}\", \"{site}\")] = (typeof({path}), typeof({w.Type})),"); + } + } + o.Add("public static class Reg4 {"); + o.Add(" public static readonly Dictionary<(string Kind, string Wrapped, string Site), (Type Vo, Type Plain)> All = new() {"); + o.AddRange(registry); + o.Add(" };"); + o.Add("}"); + return string.Join("\n", o) + "\n"; +} + +static string Ident(string s) => s.Replace('-', '_').Replace('.', '_'); + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +// Every --rows file's rows, in order, each cut to the --first n that follows it. +static IEnumerable<Dictionary<string, string>> RowsArguments(string[] args) +{ + var any = false; + for (var i = 1; i < args.Length; i++) + { + if (args[i] != "--rows") continue; + if (i + 1 >= args.Length) throw new GeneratorError("--rows <path> needs a path"); + var rows = Rows(args[i + 1]); + if (i + 3 < args.Length && args[i + 2] == "--first") + { + var first = int.Parse(args[i + 3], System.Globalization.CultureInfo.InvariantCulture); + if (first > rows.Count) throw new GeneratorError($"--first {first}, but {args[i + 1]} holds {rows.Count} rows"); + rows = rows.GetRange(0, first); + } + any = true; + foreach (var row in rows) yield return row; + } + if (!any) throw new GeneratorError("at least one --rows <path> is required"); +} + +// A rows file of `prober rows`: gzipped JSON, {"claim": ..., "rows": [{"row": {axis: value}}, ...]}. +static List<Dictionary<string, string>> Rows(string path) +{ + using var gzip = new GZipStream(File.OpenRead(path), CompressionMode.Decompress); + var rows = JsonNode.Parse(gzip)?["rows"] as JsonArray ?? throw new GeneratorError($"{path} has no rows"); + return + [ + .. rows.Select(r => (r?["row"] as JsonObject ?? throw new GeneratorError($"{path}: an entry without a row")) + .ToDictionary(p => p.Key, p => p.Value?.GetValue<string>() ?? throw new GeneratorError($"{path}: {p.Key} is null"), StringComparer.Ordinal)), + ]; +} + +/// <summary>A wrapped type: its C# spelling, a value Create keeps and the one it refuses.</summary> +sealed record Wrapped(string Type, string Valid, string Refused) +{ + static readonly Dictionary<string, Wrapped> Table = new(StringComparer.Ordinal) + { + ["string"] = new("string", "\"alpha\"", "\"SECRET-7f3a\""), + ["Guid"] = new("Guid", "Guid.Parse(\"11111111-1111-1111-1111-111111111111\")", "Guid.Empty"), + ["int"] = new("int", "42", "-1"), + ["long"] = new("long", "4200000000L", "-1L"), + ["byte"] = new("byte", "(byte)7", "(byte)255"), + ["ulong"] = new("ulong", "4200000000UL", "0UL"), + ["decimal"] = new("decimal", "7.80m", "-1m"), + ["double"] = new("double", "1.5", "-1.0"), + ["bool"] = new("bool", "true", "false"), + ["char"] = new("char", "'a'", "'x'"), + ["DateTime"] = new("DateTime", "new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc)", "DateTime.MinValue"), + ["DateTimeOffset"] = new("DateTimeOffset", "new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero)", "DateTimeOffset.MinValue"), + ["DateOnly"] = new("DateOnly", "new DateOnly(2026,10,15)", "DateOnly.MinValue"), + ["TimeOnly"] = new("TimeOnly", "new TimeOnly(8,30,15)", "TimeOnly.MinValue"), + ["TimeSpan"] = new("TimeSpan", "TimeSpan.FromMinutes(90)", "TimeSpan.MinValue"), + ["enum"] = new("PEnum", "PEnum.B", "(PEnum)99"), + ["flags-enum"] = new("PFlags", "PFlags.P | PFlags.Q", "(PFlags)0"), + ["Uri"] = new("Uri", "new Uri(\"https://example.com/a\")", "new Uri(\"https://example.com/refused\")"), + }; + + public static Wrapped Of(string key) => + Table.TryGetValue(key, out var w) ? w : throw new GeneratorError($"shape.wrapped {key} is not in ef.cs's table"); +} + +static class Grids +{ + public const string Pragmas = "#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003, CMTK0006, CA1000"; + + public static readonly (string Kind, string Abbreviation)[] Kinds = + [("record-struct", "rs"), ("sealed-record-class", "rc"), ("validated-struct", "vs"), ("validated-class", "vc")]; +} + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/faults.cs b/tools/probe/generators/faults.cs new file mode 100644 index 00000000..658d02d6 --- /dev/null +++ b/tools/probe/generators/faults.cs @@ -0,0 +1,139 @@ +// Writes tools/probe/corpus/harness/FaultClaimControllers.g.cs: the MVC controllers of the fault claims C04, C06, C14 +// and C15 and, from the same data, the table FxCtl that FaultClaim reads as the oracle's input (what each controller +// and action declares). Its input is the table below, not a rows file: each row of those claims picks a controller. +// +// dotnet run tools/probe/generators/faults.cs -- --out tools/probe/corpus/harness/FaultClaimControllers.g.cs +// +// GeneratedHarnessTests in the conventions tests holds the committed file to this output. +using System.Text; + +try +{ + File.WriteAllText(Arg(args, "--out"), Generate(), new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"faults.cs: {e.Message}"); + return 2; +} + +static string Generate() +{ + // The result type an action answers, by the last segment of its route; decl answers one, with a 429 of its own. + string[] results = ["one", "two", "dup", "decl"]; + var returns = new Dictionary<string, string>(StringComparer.Ordinal) + { + ["one"] = "Results<NoContent, FaultProblem<HFault>>", + ["two"] = "Results<NoContent, FaultProblem<HFault>, FaultProblem<GFault>>", + ["dup"] = "Results<NoContent, FaultProblem<HFault>, FaultProblem<KFault>>", + ["decl"] = "Results<NoContent, FaultProblem<HFault>>", + }; + var bodies = new Dictionary<string, string>(StringComparer.Ordinal) + { ["one"] = "FxErr.One(e)", ["two"] = "FxErr.Two(e)", ["dup"] = "FxErr.Dup(e)", ["decl"] = "FxErr.One(e)" }; + + var o = new List<string> + { + "// Generated by tools/probe/generators/faults.cs (the fault claims' MVC controllers, C04 C06 C14 C15): do not edit by hand.", + "// Every action answers FxErr's errors; its route ends in the result type it answers (one, two, dup) or decl (one, with a", + "// [ProducesResponseType<ProblemDetails>(429)] of its own). The table FxCtl.All restates what each class and action declares,", + "// in ASP.NET's metadata order (controller, then action), as the input FaultClaim computes the expected response and document from.", + "using CodoMetis.TypeKit;", + "using CodoMetis.TypeKit.AspNetCore;", + "using Microsoft.AspNetCore.Http;", + "using Microsoft.AspNetCore.Http.HttpResults;", + "using Microsoft.AspNetCore.Mvc;", + "namespace Probes.Faults;", + "", + }; + foreach (var (name, variants, actions) in Table.Controllers) + { + o.Add($"internal abstract class Fx{name}Base : ControllerBase"); + o.Add("{"); + foreach (var (route, decls, declared) in actions) + { + foreach (var t in results) + { + var attrs = new List<string> { $"HttpGet(\"c/{route}/{t}\")" }; + attrs.AddRange(decls.Select(Attribute)); + attrs.AddRange(declared.Select(DeclaredAttribute)); + if (t == "decl") attrs.Add("ProducesResponseType<ProblemDetails>(429)"); + o.Add($" [{string.Join(", ", attrs)}] public {returns[t]} {Pascal(route)}{char.ToUpperInvariant(t[0])}{t[1..]}(int e) => {bodies[t]}.ToHttpResult();"); + } + } + o.Add("}"); + foreach (var (suffix, classDecls) in variants) + { + var a = string.Join(", ", classDecls.Select(Attribute)); + o.Add($"[ApiController{(a != "" ? ", " + a : "")}] internal sealed class Fx{name}{suffix}Api : Fx{name}Base {{ }}"); + o.Add($"{(a != "" ? "[" + a + "] " : "")}internal sealed class Fx{name}{suffix}Plain : Fx{name}Base {{ }}"); + } + o.Add(""); + } + o.Add("public static partial class FxCtl"); + o.Add("{"); + o.Add(" static partial void Fill(Dictionary<string, Ctl> d)"); + o.Add(" {"); + foreach (var (name, variants, actions) in Table.Controllers) + { + var acts = string.Join(", ", actions.Select(x => + $"new(\"{x.Route}\", [{string.Join(", ", x.Decls.Select(Literal))}], [{string.Join(", ", x.Declared.Select(s => s.Status))}])")); + foreach (var (suffix, classDecls) in variants) + o.Add($" d[\"{name}{suffix}\"] = new(typeof(Fx{name}{suffix}Api), typeof(Fx{name}{suffix}Plain), [{string.Join(", ", classDecls.Select(Literal))}], [{acts}]);"); + } + o.Add(" }"); + o.Add("}"); + return string.Join("\n", o) + "\n"; +} + +// A fault member's status: WithoutFaults where the status is 0 (left out), FaultStatus otherwise. +static string Attribute(Decl d) => d.Status == 0 ? $"WithoutFaults<{d.Type}>(\"{d.Member}\")" : $"FaultStatus<{d.Type}>(\"{d.Member}\", {d.Status})"; + +static string Literal(Decl d) => $"new(\"{d.Type}\", \"{d.Member}\", {d.Status})"; + +// A status the action declares itself: pp as ProducesProblem declares it (with application/problem+json), prt as [ProducesResponseType] alone. +static string DeclaredAttribute(Declared x) => + x.How == "pp" ? $"ProducesResponseType<ProblemDetails>({x.Status}, \"application/problem+json\")" : $"ProducesResponseType<ProblemDetails>({x.Status})"; + +static string Pascal(string route) => + string.Concat(route.Split('/', StringSplitOptions.RemoveEmptyEntries).Select(p => char.ToUpperInvariant(p[0]) + p[1..])); + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +/// <summary>A fault member's declared status on a class or an action; 0 leaves the member out.</summary> +sealed record Decl(string Type, string Member, int Status); + +/// <summary>A status an action declares itself, and how.</summary> +sealed record Declared(int Status, string How); + +sealed record ControllerAction(string Route, Decl[] Decls, Declared[] Declared); + +/// <summary>The controllers: each name's class variants (suffix, the class's declarations) and its actions.</summary> +static class Table +{ + public static readonly (string Name, (string Suffix, Decl[] Decls)[] Variants, ControllerAction[] Actions)[] Controllers = + [ + ("C04Ep", [("", []), ("S", [new("HFault", "Down", 507)])], + [new("fx/ep/wo", [new("HFault", "Down", 0)], []), new("fx/ep/keep", [], [])]), + ("C04CtWo", [("", [new("HFault", "Down", 0)])], + [new("fx/ct/wo/a", [], []), new("fx/ct/wo/b", [], [])]), + ("C04CtKeep", [("", [])], [new("fx/ct/keep", [], [])]), + ("C04Gr", [("", [])], [new("fx/gr/a", [], []), new("fx/gr/b", [], [])]), + ("C06K", [("", [])], [new("k", [], [])]), + ("C14Pp", [("", [])], [new("d/pp", [], [new(404, "pp")])]), + ("C14Prt", [("", [])], [new("d/prt", [], [new(404, "prt")])]), + ("C14None", [("", [])], [new("d/none", [], [])]), + ("C15M", [("", [])], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]), + ("C15AS", [("", [])], [new("s/a", [new("HFault", "Denied", 451)], []), new("s/k", [], [])]), + ("C15AW", [("", [])], [new("s/a", [new("HFault", "Down", 0)], []), new("s/k", [], [])]), + ("C15CS", [("", [new("HFault", "Denied", 451)])], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]), + ("C15CW", [("", [new("HFault", "Down", 0)])], [new("s/a", [], []), new("s/p", [new("HFault", "Denied", 452)], [])]), + ("C15K", [("", [])], [new("s/k", [], [])]), + ]; +} + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/json-family.cs b/tools/probe/generators/json-family.cs new file mode 100644 index 00000000..dd8d5ef3 --- /dev/null +++ b/tools/probe/generators/json-family.cs @@ -0,0 +1,220 @@ +// Writes the JSON family's declarations from its rows, one mode per file of tools/probe/corpus/harness: +// +// stand-ins JsonFamilyShapes.g.cs L06's polymorphic stand-ins over Option and Result, a base and its derived type per +// value object and carrier; the source-generated context the family runs on is the +// one ProbeContext (shapes.cs), which lists the carriers L06's rows hold +// l05 L05Shapes.g.cs L05's value objects and closed sets, one per (kind, wrapped, inheritance, site) its +// rows name, and L05Registry +// +// dotnet run tools/probe/generators/json-family.cs -- stand-ins --out tools/probe/corpus/harness/JsonFamilyShapes.g.cs +// dotnet run tools/probe/generators/json-family.cs -- l05 --out tools/probe/corpus/harness/L05Shapes.g.cs \ +// --rows tools/probe/corpus/rows/L05EqualityProbe.json.gz +// +// A rows file is gzipped JSON, written by `prober rows`. GeneratedHarnessTests in the conventions tests holds each +// file to this output. +using System.IO.Compression; +using System.Text; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +try +{ + var mode = args.Length > 0 ? args[0] : throw new GeneratorError("a mode is required: stand-ins or l05"); + var text = mode switch + { + "stand-ins" => StandIns(), + "l05" => L05(Rows(Arg(args, "--rows"))), + _ => throw new GeneratorError($"unknown mode {mode}"), + }; + File.WriteAllText(Arg(args, "--out"), text, new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"json-family.cs: {e.Message}"); + return 2; +} + +static string StandIns() +{ + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/json-family.cs stand-ins: L06's polymorphic stand-ins over Option and Result. Do not edit.", + "#nullable disable", "#pragma warning disable CS8618, CS1591, CMTK0005, CMTK0006", + "using CodoMetis.TypeKit;", "using Probes.Shapes;", + "namespace Probes.Json;", "", + }; + // a base per value object and carrier, its derived type holding the Option or Result + var vos = new[] { "record-struct", "sealed-record-class", "validated-struct", "validated-class" } + .SelectMany(k => Shape.WrappedTypes.Keys.Select(w => Shape.Vo(k, w))).Append("PSet"); + foreach (var v in vos) + { + foreach (var (key, pattern) in new[] { ("O", "Option<{0}>"), ("RTE", "Result<{0}, ShapeFault>"), ("RE", "Result<{0}>") }) + { + var (b, d) = ($"L06PB_{Shape.San(v)}_{key}", $"L06PD_{Shape.San(v)}_{key}"); + o.Add($"[System.Text.Json.Serialization.JsonPolymorphic] [System.Text.Json.Serialization.JsonDerivedType(typeof({d}), \"d\")] public abstract class {b} {{ }}"); + o.Add($"public sealed class {d} : {b} {{ public {string.Format(System.Globalization.CultureInfo.InvariantCulture, pattern, v)} Value {{ get; set; }} }}"); + } + } + return string.Join("\n", o) + "\n"; +} + +static string L05(List<Dictionary<string, string>> rows) +{ + // A row the covering array made with an excluded pair runs the nearest shape that exists (the probe notes the substitution). + static string SiteOf(Dictionary<string, string> r) => r["shape.site"] == "in-generic-type" ? "top-level" : r["shape.site"]; + static string InheritanceOf(Dictionary<string, string> r) => + Shape.VoIsStruct(r["shape.kind"]) && r["shape.inheritance"].StartsWith("base-", StringComparison.Ordinal) ? "none" : r["shape.inheritance"]; + + var tuples = rows + .Select(r => (Kind: r["shape.kind"], Wrapped: r["shape.kind"] == "closed-set" ? "-" : r["shape.wrapped"], Inheritance: InheritanceOf(r), Site: SiteOf(r))) + .Distinct() + .OrderBy(t => t.Kind, StringComparer.Ordinal).ThenBy(t => t.Wrapped, StringComparer.Ordinal) + .ThenBy(t => t.Inheritance, StringComparer.Ordinal).ThenBy(t => t.Site, StringComparer.Ordinal) + .ToList(); + + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/json-family.cs from the rows of L05-equality. Do not edit.", + "#nullable enable", + "#pragma warning disable CS1591, CMTK0005, CMTK1023 // CMTK1023: a base record with an explicit IEquatable is the shape under test", + "using CodoMetis.TypeKit;", "using CodoMetis.TypeKit.ValueObjects;", "using CodoMetis.TypeKit.ClosedSets;", "using Probes.Shapes;", + "namespace Probes.Json;", "", + "public interface IL05Tag { string Tag { get; } }", + "public abstract record L05B1;", "public abstract record L05B2a;", "public abstract record L05B2b : L05B2a;", + "public abstract record L05B3a;", "public abstract record L05B3b : L05B3a;", "public abstract record L05B3c : L05B3b;", + "// a deeper base whose own IEquatable says \"never equal\": the value object must not use it", + "public abstract record L05E0 : IEquatable<L05E0> { bool IEquatable<L05E0>.Equals(L05E0? other) => false; }", + "public abstract record L05E1 : L05E0;", + "public abstract class L05CB1;", "public abstract class L05CB2a;", "public abstract class L05CB2b : L05CB2a;", + "public abstract class L05CB3a;", "public abstract class L05CB3b : L05CB3a;", "public abstract class L05CB3c : L05CB3b;", + "public abstract class L05CE0 : IEquatable<L05CE0> { bool IEquatable<L05CE0>.Equals(L05CE0? other) => ReferenceEquals(this, other); }", + "public abstract class L05CE1 : L05CE0;", "", + }; + // the base each inheritance names: records under a value object, classes under a closed set + var recordBases = new Dictionary<string, string>(StringComparer.Ordinal) + { ["base-depth-1"] = "L05B1", ["base-depth-2"] = "L05B2b", ["base-depth-3"] = "L05B3c", ["base-explicit-IEquatable"] = "L05E1" }; + var classBases = new Dictionary<string, string>(StringComparer.Ordinal) + { ["base-depth-1"] = "L05CB1", ["base-depth-2"] = "L05CB2b", ["base-depth-3"] = "L05CB3c", ["base-explicit-IEquatable"] = "L05CE1" }; + + var registry = new List<string>(); + for (var i = 0; i < tuples.Count; i++) + { + var (k, w, inh, site) = tuples[i]; + var n = $"L05_{i}_{Shape.San(k)}_{Shape.San(w)}"; + var bases = new List<string>(); + if (inh.StartsWith("base-", StringComparison.Ordinal)) bases.Add((k == "closed-set" ? classBases : recordBases)[inh]); + var tag = inh == "explicit-interface-member" ? " string IL05Tag.Tag => \"t\";" : ""; + if (inh == "explicit-interface-member") bases.Add("IL05Tag"); + var head = string.Concat(bases.Select(b => b + ", ")); + string decl; + if (k == "closed-set") + decl = $"public sealed partial class {n} : {head}IClosedSet<{n}> {{ public static readonly {n} Alpha = new(); public static readonly {n} Beta = new();{tag} }}"; + else + { + var t = Shape.WrappedTypes[w]; + decl = k switch + { + "record-struct" => $"public readonly partial record struct {n} : {head}IValue<{t}> {{{tag} }}", + "sealed-record-class" => $"public sealed partial record {n} : {head}IValue<{t}> {{{tag} }}", + "validated-struct" => $"public readonly partial record struct {n} : {head}IValidatedValue<{n}, {t}, ShapeFault> {{ public static Result<{n}, ShapeFault> Create({t} value) => new {n}(value);{tag} }}", + "validated-class" => $"public sealed partial record {n} : {head}IValidatedValue<{n}, {t}, ShapeFault> {{ public static Result<{n}, ShapeFault> Create({t} value) => new {n}(value);{tag} }}", + _ => throw new GeneratorError($"shape.kind {k} has no declaration here"), + }; + } + string full; + switch (site) + { + case "top-level": o.Add(decl); full = n; break; + case "nested-in-class": o.Add($"public partial class L05Oc{i} {{ {decl} }}"); full = $"L05Oc{i}.{n}"; break; + case "nested-in-struct": o.Add($"public partial struct L05Os{i} {{ {decl} }}"); full = $"L05Os{i}.{n}"; break; + case "nested-two-levels": o.Add($"public partial class L05Ot{i} {{ public partial class Inner {{ {decl} }} }}"); full = $"L05Ot{i}.Inner.{n}"; break; + default: throw new GeneratorError($"shape.site {site} has no shape here"); + } + if (k == "closed-set") + registry.Add($" Add(\"{k}\", \"-\", \"{inh}\", \"{site}\", typeof({full}), () => {full}.Alpha, () => {full}.All.First(m => m.Name == \"Alpha\"), () => {full}.Beta);"); + else + { + var (a1, a2, b) = L05Samples.All[w]; + registry.Add($" Add(\"{k}\", \"{w}\", \"{inh}\", \"{site}\", typeof({full}), () => {a1}, () => {a2}, () => {b});"); + } + } + o.AddRange(["", "public static partial class L05Registry", "{", " static partial void Fill()", " {"]); + o.AddRange(registry); + o.AddRange([" }", "}", ""]); + return string.Join("\n", o); +} + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +// A rows file of `prober rows`: gzipped JSON, {"claim": ..., "rows": [{"row": {axis: value}}, ...]}. +static List<Dictionary<string, string>> Rows(string path) +{ + using var gzip = new GZipStream(File.OpenRead(path), CompressionMode.Decompress); + var rows = JsonNode.Parse(gzip)?["rows"] as JsonArray ?? throw new GeneratorError($"{path} has no rows"); + return + [ + .. rows.Select(r => (r?["row"] as JsonObject ?? throw new GeneratorError($"{path}: an entry without a row")) + .ToDictionary(p => p.Key, p => p.Value?.GetValue<string>() ?? throw new GeneratorError($"{path}: {p.Key} is null"), StringComparer.Ordinal)), + ]; +} + +/// <summary> +/// L05's samples of each wrapped type, as C# expressions: a1, a2 (equal to a1 by the wrapped type's own equality, in +/// another form), b (unequal, greater where ordered). +/// </summary> +static class L05Samples +{ + public static readonly Dictionary<string, (string A1, string A2, string B)> All = new(StringComparer.Ordinal) + { + ["string"] = ("\"alpha\"", "new string(\"alpha\".ToCharArray())", "\"beta\""), + ["Guid"] = ("Guid.Parse(\"11111111-1111-1111-1111-111111111111\")", "new Guid(\"11111111111111111111111111111111\")", "Guid.Parse(\"22222222-2222-2222-2222-222222222222\")"), + ["int"] = ("42", "42", "43"), + ["long"] = ("4200000000L", "4200000000L", "4200000001L"), + ["byte"] = ("(byte)7", "(byte)7", "(byte)8"), + ["ulong"] = ("4200000000UL", "4200000000UL", "4200000001UL"), + ["decimal"] = ("7.8m", "7.80m", "7.81m"), + ["double"] = ("0.0", "-0.0", "1.5"), + ["bool"] = ("false", "false", "true"), + ["char"] = ("'a'", "'a'", "'b'"), + ["DateTime"] = ("new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Utc)", "new DateTime(2026, 10, 15, 8, 30, 0, DateTimeKind.Unspecified)", "new DateTime(2026, 10, 15, 8, 31, 0, DateTimeKind.Utc)"), + ["DateTimeOffset"] = ("new DateTimeOffset(2026, 10, 15, 8, 30, 0, TimeSpan.Zero)", "new DateTimeOffset(2026, 10, 15, 10, 30, 0, TimeSpan.FromHours(2))", "new DateTimeOffset(2026, 10, 15, 8, 31, 0, TimeSpan.Zero)"), + ["DateOnly"] = ("new DateOnly(2026, 10, 15)", "DateOnly.FromDayNumber(new DateOnly(2026, 10, 15).DayNumber)", "new DateOnly(2026, 10, 16)"), + ["TimeOnly"] = ("new TimeOnly(8, 30, 15)", "new TimeOnly(new TimeOnly(8, 30, 15).Ticks)", "new TimeOnly(8, 30, 16)"), + ["TimeSpan"] = ("TimeSpan.FromMinutes(90)", "TimeSpan.FromSeconds(5400)", "TimeSpan.FromMinutes(91)"), + ["enum"] = ("PEnum.B", "(PEnum)1", "PEnum.C"), + ["flags-enum"] = ("PFlags.P | PFlags.Q", "(PFlags)3", "PFlags.R"), + ["Uri"] = ("new Uri(\"https://example.com/a\")", "new Uri(\"HTTPS://EXAMPLE.COM/a\")", "new Uri(\"https://example.com/b\")"), + ["NodaTime.Instant"] = ("global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30)", "global::NodaTime.Instant.FromUnixTimeTicks(global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 30).ToUnixTimeTicks())", "global::NodaTime.Instant.FromUtc(2026, 10, 15, 8, 31)"), + ["own-record-class"] = ("new PRec { A = \"x\", B = 1 }", "new PRec { A = new string(\"x\".ToCharArray()), B = 1 }", "new PRec { A = \"y\", B = 1 }"), + }; +} + +/// <summary>The names the shapes of Shapes.g.cs go by.</summary> +static partial class Shape +{ + /// <summary>Each wrapped type's C# spelling, in the axes file's order.</summary> + public static readonly Dictionary<string, string> WrappedTypes = new(StringComparer.Ordinal) + { + ["string"] = "string", ["Guid"] = "Guid", ["int"] = "int", ["long"] = "long", ["byte"] = "byte", ["ulong"] = "ulong", + ["decimal"] = "decimal", ["double"] = "double", ["bool"] = "bool", ["char"] = "char", ["DateTime"] = "DateTime", + ["DateTimeOffset"] = "DateTimeOffset", ["DateOnly"] = "DateOnly", ["TimeOnly"] = "TimeOnly", ["TimeSpan"] = "TimeSpan", + ["enum"] = "PEnum", ["flags-enum"] = "PFlags", ["Uri"] = "Uri", ["NodaTime.Instant"] = "global::NodaTime.Instant", + ["own-record-class"] = "PRec", + }; + + public static string San(string s) => NotAlphanumeric().Replace(s, "_"); + + public static string Vo(string kind, string wrapped) => kind == "closed-set" ? "PSet" : $"V_{San(kind)}_{San(wrapped)}"; + + public static bool VoIsStruct(string kind) => kind is "record-struct" or "validated-struct"; + + [GeneratedRegex("[^A-Za-z0-9]")] + private static partial Regex NotAlphanumeric(); +} + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/rules.cs b/tools/probe/generators/rules.cs new file mode 100644 index 00000000..8d712137 --- /dev/null +++ b/tools/probe/generators/rules.cs @@ -0,0 +1,69 @@ +// Writes tools/probe/corpus/harness/RuleControllers.g.cs: the MVC controllers C03 and C05 run against, one action per +// arity (a Fault of two or three sets) and response variant, routed c/{arity}/{variant}, each answering Cmp's errors. +// The variants are those ComposedClaims.Variant names a row's http.response by and Cmp.MapMinimal maps on the minimal +// side; the class names are what ComposedClaims hands the host (RuleApiController, RulePlainController). +// +// dotnet run tools/probe/generators/rules.cs -- --out tools/probe/corpus/harness/RuleControllers.g.cs +// +// GeneratedHarnessTests in the conventions tests holds the committed file to this output. +using System.Text; + +try +{ + File.WriteAllText(Arg(args, "--out"), Generate(), new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"rules.cs: {e.Message}"); + return 2; +} + +static string Generate() +{ + // variant: the attribute it adds to the action (Cmp.Decorate's, as MVC declares it), and whether it answers KFault, + // whose Clash shares HFault's name, in place of GFault + (string Name, string Attribute, bool SameName)[] variants = + [ + ("plain", "", false), + ("wo", ", WithoutFaults<HFault>(nameof(HFault.Bad))", false), + ("group", ", FaultStatus<HFault>(nameof(HFault.Clash), 422)", false), + ("declared", ", ProducesResponseType<ProblemDetails>(429)", false), + ("dup", "", true), + ]; + var o = new List<string> + { + "// Generated by tools/probe/generators/rules.cs: controllers answering composed faults, one action per arity x response variant (C03, C05).", + "using CodoMetis.TypeKit;", + "using CodoMetis.TypeKit.AspNetCore;", + "using Microsoft.AspNetCore.Http.HttpResults;", + "using Microsoft.AspNetCore.Http;", + "using Microsoft.AspNetCore.Mvc;", + "namespace Probes;", + "public abstract class RuleCtlBase : ControllerBase", + "{", + }; + foreach (var (name, attribute, sameName) in variants) + { + foreach (var arity in new[] { 2, 3 }) + { + var second = sameName ? "KFault" : "GFault"; + var faults = $"FaultProblem<HFault>, FaultProblem<{second}>" + (arity == 3 ? ", FaultProblem<SFault>" : ""); + var method = (arity == 2 ? "Two" : "Three") + (sameName ? "K" : ""); + o.Add($" [HttpGet(\"c/{arity}/{name}\"){attribute}]"); + o.Add($" public Results<NoContent, {faults}> A{arity}{char.ToUpperInvariant(name[0])}{name[1..]}(int e) => Cmp.{method}(e).ToHttpResult();"); + } + } + o.Add("}"); + o.Add("[ApiController] public sealed class RuleApiController : RuleCtlBase { }"); + o.Add("public sealed class RulePlainController : RuleCtlBase { }"); + return string.Join("\n", o) + "\n"; +} + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +sealed class GeneratorError(string message) : Exception(message); diff --git a/tools/probe/generators/shapes.cs b/tools/probe/generators/shapes.cs new file mode 100644 index 00000000..180afa84 --- /dev/null +++ b/tools/probe/generators/shapes.cs @@ -0,0 +1,276 @@ +// Writes tools/probe/corpus/harness/Shapes.g.cs, the probes' shape catalogue: one value object per +// (shape.kind, shape.wrapped) of the axes file, the plain twin each is compared with, the registry of both, a +// polymorphic base and derived type over each, and the source-generated context ProbeContext, which lists every +// type a row holds a value object or its twin in: each shape (a struct one made nullable too, as http.nullability=nullable +// declares it) at each shape.position in each shape.containing, and the +// Option and Result over a value object that L06's rows hold under json.TypeInfoResolver=source-generated-context. +// +// One context, one declaration: System.Text.Json's generator runs once per declaration that carries [JsonSerializable], so +// a second partial part of ProbeContext fails it. L06's carriers are listed from its rows, not for every shape: an Option and +// two Results over every value object at every position would add some 16000 types and three minutes to the Samples' build +// (measured 2026-10-17: 38 s before, 73 s with this context, 3 min 49 s with every carrier at every position). +// +// dotnet run tools/probe/generators/shapes.cs -- --axes .claude/probe-axes.md --l06 tools/probe/corpus/rows/L06OffTheWireProbe.json.gz \ +// --out tools/probe/corpus/harness/Shapes.g.cs +// +// The kinds, wrapped types, positions and containing types are the axes file's (its ```json probe-axes block); the +// C# spelling of each wrapped type and its valid and refused samples are the table below, and a value the table does +// not know is refused. GeneratedHarnessTests in the conventions tests holds the committed file to this output. +// +// The twin is the wrapped type as the value object holds it, without TypeKit: the same type at the value object's +// nullability. A class value object over a value type admits null where its wrapped struct does not, so its twin is +// the wrapped type made nullable (int? for a sealed record class over int); a struct value object over a value type, +// and any value object over a class, faces the wrapped type itself. A struct value object over a class (string, Uri, +// a record class) refuses a JSON null that its twin reads: no plain type refuses null there, and the claims cite it +// (not-promised a-struct-refuses-a-json-null). The closed set travels as its member's name, so its twin is a string +// (not-promised a-closed-set-is-a-string-enum, a-closed-set-key-is-its-name). The registry's Plain is the twin, and a +// row's position and containing type are put around both alike (harness/Positions.cs, Pos.Twins). +using System.IO.Compression; +using System.Text; +using System.Text.Json.Nodes; + +try +{ + var axes = AxesBlock(Arg(args, "--axes")); + var kinds = Values(axes, "shape.kind"); + var wrapped = Values(axes, "shape.wrapped"); + var positions = Values(axes, "shape.position"); + var containing = Values(axes, "shape.containing"); + var l06 = Rows(Arg(args, "--l06")); + File.WriteAllText(Arg(args, "--out"), Generate(kinds, wrapped, positions, containing, l06), new UTF8Encoding(false)); + return 0; +} +catch (GeneratorError e) +{ + Console.Error.WriteLine($"shapes.cs: {e.Message}"); + return 2; +} + +static string Generate(IReadOnlyList<string> kinds, IReadOnlyList<string> wrappedKeys, IReadOnlyList<string> positions, IReadOnlyList<string> containing, List<Dictionary<string, string>> l06) +{ + var o = new List<string> + { + "// <auto-generated/> by tools/probe/generators/shapes.cs; probes' shape catalogue. Do not edit.", + "#nullable enable\n#pragma warning disable CMTK0014, CS8618, CS1591, CMTK0005, CMTK1000, CMTK0003", + "using System.Collections.Immutable;\nusing CodoMetis.TypeKit;\nusing CodoMetis.TypeKit.ValueObjects;\nusing CodoMetis.TypeKit.ClosedSets;\nusing System.Text.Json.Serialization;", + "namespace Probes.Shapes;", + "public enum PEnum { A, B, C }\n[Flags] public enum PFlags { P = 1, Q = 2, R = 4 }\npublic enum ShapeFault { Refused }", + "public sealed record PRec { public string A { get; init; } = \"\"; public int B { get; init; } }", + "public sealed partial class PSet : IClosedSet<PSet> { public static readonly PSet Alpha = new(); public static readonly PSet Beta = new(); }", + }; + + // One value object per kind and wrapped type; the closed set is the one PSet above. + var entries = new List<(string Kind, string WrappedKey, string Name, Wrapped W, Twin T)>(); + foreach (var kind in kinds) + { + if (kind == "closed-set") continue; + foreach (var key in wrappedKeys) + { + var w = Wrapped.Of(key); + var n = $"V_{Ident(kind)}_{Ident(key)}"; + o.Add(kind switch + { + "record-struct" => $"public readonly partial record struct {n} : IValue<{w.Type}>;", + "sealed-record-class" => $"public sealed partial record class {n} : IValue<{w.Type}>;", + "validated-struct" or "validated-class" => + $"public {(kind == "validated-struct" ? "readonly partial record struct" : "sealed partial record class")} {n} : IValidatedValue<{n}, {w.Type}, ShapeFault>\n{{\n" + + $" public static Result<{n}, ShapeFault> Create({w.Type} value) =>\n" + + $" EqualityComparer<{w.Type}>.Default.Equals(value, {w.Refused}) ? Result.Error(ShapeFault.Refused) : new {n}(value);\n}}", + _ => throw new GeneratorError($"shape.kind {kind} has no declaration here"), + }); + entries.Add((kind, key, n, w, Twin.Of(kind, w))); + } + } + + // A polymorphic base and derived type over each value object, PSet, and each twin. Their members are declared as the containers' + // are (Containers.g.cs), outside the nullable context, so a member admits null by its type alone on both sides, as the twin's + // nullability says, and json.RespectNullableAnnotations reads no annotation that one side has and the other does not. + o.Add("#nullable disable"); + foreach (var e in entries) o.Add(Poly(e.Name, e.Name)); + o.Add(Poly("PSet", "PSet")); + var twins = entries.Select(e => e.T).Append(Twin.ClosedSet).DistinctBy(t => t.Ident).ToList(); + foreach (var t in twins) + o.Add($"[JsonPolymorphic] [JsonDerivedType(typeof(PDP_{t.Ident}), \"d\")] public abstract class PBP_{t.Ident} {{ }}\n" + + $"public sealed class PDP_{t.Ident} : PBP_{t.Ident} {{ public {t.Type} Value {{ get; set; }}{t.Initializer} }}"); + o.Add("#nullable enable"); + + o.Add("public sealed record Entry(string Kind, string Wrapped, Type Vo, Type Plain, Func<object> Valid, Func<object?> RefusedFn, Type PolyBase, Type PolyDerived, Type PolyBasePlain, Type PolyDerivedPlain);"); + + // The registry: a refused sample only where Create refuses one. + o.Add("public static partial class Registry\n{\n static partial void Fill(List<Entry> e)\n {"); + foreach (var (kind, key, n, w, t) in entries) + { + var refused = kind.StartsWith("validated", StringComparison.Ordinal) ? w.Refused : "null"; + o.Add($" e.Add(new Entry(\"{kind}\", \"{key}\", typeof({n}), typeof({t.Type}), () => {w.Valid}, () => {refused}, " + + $"typeof(PB_{n}), typeof(PD_{n}), typeof(PBP_{t.Ident}), typeof(PDP_{t.Ident})));"); + } + o.Add($" e.Add(new Entry(\"closed-set\", \"-\", typeof(PSet), typeof({Twin.ClosedSet.Type}), () => \"Alpha\", () => null, typeof(PB_PSet), typeof(PD_PSet), typeof(PBP_{Twin.ClosedSet.Ident}), typeof(PDP_{Twin.ClosedSet.Ident})));"); + o.Add(" }\n}"); + + // The source-generated context: every value object and every twin at every position in every containing type, so a row + // under json.TypeInfoResolver=source-generated-context runs on the context alone, whatever shape it holds. + // a struct value object made nullable too: under http.nullability=nullable a row holds V? (and its twin T?) at its position + var structs = entries.Where(e => e.Kind is "record-struct" or "validated-struct").ToList(); + var members = entries.Select(e => (Type: e.Name, Struct: e.Kind is "record-struct" or "validated-struct", Poly: $"PB_{e.Name}", Key: e.Name)) + .Concat(structs.Select(e => (Type: e.Name + "?", Struct: true, Poly: $"PB_{e.Name}", Key: e.Name))) + .Append((Type: "PSet", Struct: false, Poly: "PB_PSet", Key: "PSet")) + .Concat(twins.Select(t => (Type: t.Type, Struct: t.Struct, Poly: $"PBP_{t.Ident}", Key: t.KeyType))) + .ToList(); + var listed = new List<string>(); + foreach (var m in members) + { + listed.Add(m.Type); + foreach (var position in positions) + if (position != "root") + foreach (var c in containing) + listed.Add(Holder(position, c, m.Type, m.Struct, m.Poly, m.Key)); + } + // L06's carriers, as its rows hold them under the context: the Option or Result over the value object, at the row's position in its + // containing type, its polymorphic stand-in L06's own (JsonFamilyShapes.g.cs, json-family.cs) + foreach (var r in l06.Where(r => r.GetValueOrDefault("json.TypeInfoResolver") == "source-generated-context")) + { + var kind = r["shape.kind"]; + var v = kind == "closed-set" ? "PSet" : entries.FirstOrDefault(e => e.Kind == kind && e.WrappedKey == r["shape.wrapped"]).Name + ?? throw new GeneratorError($"L06's rows hold {kind} over {r["shape.wrapped"]}, which the axes' shapes do not"); + var (key, carrier) = r["or.shape"] switch + { + "Option" => ("O", $"Option<{v}>"), + "Result-T-E" or "Task-of-Result" or "ValueTask-of-Result" => ("RTE", $"Result<{v}, ShapeFault>"), + "Result-E" => ("RE", $"Result<{v}>"), + var shape => throw new GeneratorError($"or.shape {shape} has no carrier here"), + }; + var position = r["shape.position"]; + listed.Add(position == "root" ? carrier : Holder(position, r["shape.containing"], carrier, true, $"global::Probes.Json.L06PB_{v}_{key}", carrier)); + } + o.Add("[JsonSourceGenerationOptions]"); + var i = 0; + foreach (var t in listed.Distinct(StringComparer.Ordinal)) + // a generic, array or nullable type gets a name of its own (two would collide); a plain type keeps the serializer's + o.Add(t.Contains('<') || t.Contains('[') || t.EndsWith('?') ? $"[JsonSerializable(typeof({t}), TypeInfoPropertyName = \"T{i++}\")]" : $"[JsonSerializable(typeof({t}))]"); + o.Add("public partial class ProbeContext : JsonSerializerContext { }"); + + return string.Join("\n", o) + "\n"; +} + +static string Poly(string name, string type) => + $"[JsonPolymorphic] [JsonDerivedType(typeof(PD_{name}), \"d\")] public abstract class PB_{name} {{ }}\n" + + $"public sealed class PD_{name} : PB_{name} {{ public {type} Value {{ get; set; }} = default!; }}"; + +// The type a row's position holds m in, as harness/Positions.cs builds it: m itself at the root, else a container of +// Containers.g.cs around m, its list, array, dictionary, patch or polymorphic base. A dictionary key is never null, nor is +// what a Patch<T> holds (T : notnull), so there a nullable twin is the type it holds (key). +static string Holder(string position, string containing, string m, bool isStruct, string polyBase, string key) +{ + var style = position switch { "field" => "F", "record-ctor-parameter" => "C", "required-init" => "R", _ => "P" }; + var member = position switch + { + "property" or "field" or "record-ctor-parameter" or "required-init" => m, + "nullable-property" => isStruct && !m.EndsWith('?') ? m + "?" : m, + "list-element" => $"List<{m}>", + "read-only-list-element" => $"IReadOnlyList<{m}>", + "array-element" => $"{m}[]", + "immutable-array-element" => $"ImmutableArray<{m}>", + "dictionary-key" => $"Dictionary<{key}, int>", + "dictionary-value" => $"Dictionary<string, {m}>", + "in-patch" => $"Patch<{key}>", + "polymorphic-derived-property" => polyBase, + _ => throw new GeneratorError($"shape.position {position} has no holder here"), + }; + var n = containing switch + { + "class" or "record-class" or "struct" or "record-struct" or "generic-type" => containing.Replace('-', '_'), + _ => throw new GeneratorError($"shape.containing {containing} has no holder here"), + }; + return containing == "generic-type" ? $"Cn_{n}_{style}<{member}, int>" : $"Cn_{n}_{style}<{member}>"; +} + +static string Ident(string s) => s.Replace('-', '_').Replace('.', '_'); + +// A rows file of `prober rows`: gzipped JSON, {"claim": ..., "rows": [{"row": {axis: value}}, ...]}. +static List<Dictionary<string, string>> Rows(string path) +{ + using var gzip = new GZipStream(File.OpenRead(path), CompressionMode.Decompress); + var rows = JsonNode.Parse(gzip)?["rows"] as JsonArray ?? throw new GeneratorError($"{path} has no rows"); + return + [ + .. rows.Select(r => (r?["row"] as JsonObject ?? throw new GeneratorError($"{path}: an entry without a row")) + .ToDictionary(p => p.Key, p => p.Value?.GetValue<string>() ?? throw new GeneratorError($"{path}: {p.Key} is null"), StringComparer.Ordinal)), + ]; +} + +static string Arg(string[] args, string name) +{ + var at = Array.IndexOf(args, name); + return at >= 0 && at + 1 < args.Length ? args[at + 1] : throw new GeneratorError($"{name} <path> is required"); +} + +static JsonObject AxesBlock(string path) +{ + const string marker = "```json probe-axes"; + var text = File.ReadAllText(path); + var at = text.IndexOf(marker, StringComparison.Ordinal); + if (at < 0) throw new GeneratorError($"{path} has no {marker} block"); + var body = text[(at + marker.Length)..]; + return JsonNode.Parse(body[..body.IndexOf("```", StringComparison.Ordinal)]) as JsonObject + ?? throw new GeneratorError($"the {marker} block of {path} is not a JSON object"); +} + +static List<string> Values(JsonObject block, string axis) => + block["axes"]?[axis] is JsonArray values + ? [.. values.Select(v => v?.GetValue<string>() ?? throw new GeneratorError($"{axis} holds a null"))] + : throw new GeneratorError($"the axes file has no axis {axis}"); + +/// <summary>A wrapped type: its C# spelling, a value Create keeps and the one it refuses.</summary> +sealed record Wrapped(string Key, string Type, string Valid, string Refused) +{ + static readonly Dictionary<string, Wrapped> Table = new(StringComparer.Ordinal) + { + ["string"] = new("", "string", "\"alpha\"", "\"SECRET-7f3a\""), + ["Guid"] = new("", "Guid", "Guid.Parse(\"11111111-1111-1111-1111-111111111111\")", "Guid.Empty"), + ["int"] = new("", "int", "42", "-1"), + ["long"] = new("", "long", "4200000000L", "-1L"), + ["byte"] = new("", "byte", "(byte)7", "(byte)255"), + ["ulong"] = new("", "ulong", "4200000000UL", "0UL"), + ["decimal"] = new("", "decimal", "7.80m", "-1m"), + ["double"] = new("", "double", "1.5", "-1.0"), + ["bool"] = new("", "bool", "true", "false"), + ["char"] = new("", "char", "'a'", "'x'"), + ["DateTime"] = new("", "DateTime", "new DateTime(2026,10,15,8,30,0,DateTimeKind.Utc)", "DateTime.MinValue"), + ["DateTimeOffset"] = new("", "DateTimeOffset", "new DateTimeOffset(2026,10,15,8,30,0,TimeSpan.Zero)", "DateTimeOffset.MinValue"), + ["DateOnly"] = new("", "DateOnly", "new DateOnly(2026,10,15)", "DateOnly.MinValue"), + ["TimeOnly"] = new("", "TimeOnly", "new TimeOnly(8,30,15)", "TimeOnly.MinValue"), + ["TimeSpan"] = new("", "TimeSpan", "TimeSpan.FromMinutes(90)", "TimeSpan.MinValue"), + ["enum"] = new("", "PEnum", "PEnum.B", "(PEnum)99"), + ["flags-enum"] = new("", "PFlags", "PFlags.P | PFlags.Q", "(PFlags)0"), + ["Uri"] = new("", "Uri", "new Uri(\"https://example.com/a\")", "new Uri(\"https://example.com/refused\")"), + ["NodaTime.Instant"] = new("", "global::NodaTime.Instant", "global::NodaTime.Instant.FromUtc(2026,10,15,8,30)", "global::NodaTime.Instant.MinValue"), + ["own-record-class"] = new("", "PRec", "new PRec { A = \"x\", B = 1 }", "new PRec { A = \"refused\", B = 0 }"), + }; + + public static Wrapped Of(string key) => + Table.TryGetValue(key, out var w) ? w with { Key = key } : throw new GeneratorError($"shape.wrapped {key} is not in shapes.cs's table"); +} + +/// <summary> +/// A value object's twin: the wrapped type at the value object's nullability (see the head of this file), its identifier in +/// the stand-ins' names, whether it is a struct, the type a dictionary key of it is, and its stand-in's initializer. +/// </summary> +sealed record Twin(string Type, string Ident, bool Struct, string KeyType, string Initializer) +{ + static readonly HashSet<string> Classes = new(StringComparer.Ordinal) { "string", "Uri", "PRec" }; + + /// <summary>The closed set's twin: the member's name, a string.</summary> + public static readonly Twin ClosedSet = new("string", "string_", false, "string", " = \"\";"); + + public static Twin Of(string kind, Wrapped w) + { + var valueType = !Classes.Contains(w.Type); + var classVo = kind is "sealed-record-class" or "validated-class"; + var ident = w.Key.Replace('-', '_').Replace('.', '_'); + return valueType && classVo + ? new($"{w.Type}?", ident + "_N", true, w.Type, "") + : new(w.Type, ident, valueType, w.Type, " = default!;"); + } +} + +sealed class GeneratorError(string message) : Exception(message);