From c4e72e4ba650229a35280a640b4f7fca18bab6e0 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Sun, 4 Oct 2026 13:43:53 -0600 Subject: [PATCH] Exports evaluateTagged's result type by name evaluateTagged answered ProjectedEvaluation, a type the ./tagged subpath did not export, so a host could name it only through the function's return type. The subpath now re-exports it under the name the source already uses. It is the main entry point's EvaluateResult without the ParseError arm, so every value of it is also an EvaluateResult. No answer changes. The export-surface list pins the name, and a test annotates an evaluateTagged result with it and widens it to EvaluateResult, so the typecheck stage reads both through the entries a host imports. Sabotage, each run and restored from a copy: dropping the re-export turns the export-surface pin red on its assertion and the typecheck stage red on the test's import. Refs: pts-r69a --- changelog.d/pts-r69a.md | 3 +++ src/tagged.ts | 9 +++++++++ test/export-surface.json | 1 + test/tagged.test.ts | 15 ++++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelog.d/pts-r69a.md diff --git a/changelog.d/pts-r69a.md b/changelog.d/pts-r69a.md new file mode 100644 index 0000000..cf4c952 --- /dev/null +++ b/changelog.d/pts-r69a.md @@ -0,0 +1,3 @@ +### Added + +- `ProjectedEvaluation` on the `./tagged` subpath names what `evaluateTagged` answers; it is the main entry point's `EvaluateResult` without the `ParseError` arm, so every value of it is also an `EvaluateResult`, and no answer changes. diff --git a/src/tagged.ts b/src/tagged.ts index a8b2872..884afea 100644 --- a/src/tagged.ts +++ b/src/tagged.ts @@ -44,6 +44,15 @@ import { isPlainMap, setKey } from "./maps.js"; import { DEPTH_LIMIT, enterContainer, type PlaceCount, visitPlace } from "./nesting.js"; import { Duration, Float, PDate, PDateTime, toHost, Undefined, type Value } from "./values.js"; +/** + * What `evaluateTagged` answers, named on this subpath so a host can write it. + * The main entry point's `EvaluateResult` is this type with a `ParseError` arm + * added, the arm a source text fails on; this entry point takes no source + * text, so it answers none, and every value of this type is also an + * `EvaluateResult`. + */ +export type { ProjectedEvaluation }; + /** * Why a text could not be decoded. `"depth_limit_exceeded"` is a text whose * brackets and braces nest past the depth limit this package declares. diff --git a/test/export-surface.json b/test/export-surface.json index 345e4d1..e39ef1f 100644 --- a/test/export-surface.json +++ b/test/export-surface.json @@ -74,6 +74,7 @@ "DecodeResult", "EncodeReason", "EncodeResult", + "ProjectedEvaluation", "TaggedEvaluateOptions", "TaggedExecution", "decodeTagged", diff --git a/test/tagged.test.ts b/test/tagged.test.ts index 668d270..db3f1ac 100644 --- a/test/tagged.test.ts +++ b/test/tagged.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { compileProgram, type EvaluateOptions } from "../src/index.js"; +import { compileProgram, type EvaluateOptions, type EvaluateResult } from "../src/index.js"; import { type DecodeReason, decodeTagged, @@ -7,6 +7,7 @@ import { encodeTagged, evaluateTagged, executeTagged, + type ProjectedEvaluation, type TaggedEvaluateOptions, type TaggedExecution, } from "../src/tagged.js"; @@ -713,6 +714,18 @@ describe("evaluateTagged", () => { expect(outcome.error.type).toBe("UndefinedVariableError"); }); + // The answer has a name on this subpath, so a host holding one writes its + // type rather than reading it off the function, and the same value is one + // member of what the main entry point's `evaluate` answers. Sabotage: + // dropping the type's re-export from the subpath turns the export-surface + // pin red on its assertion and the typecheck stage red on this import. + it("answers the subpath's own evaluation result type", () => { + const outcome: ProjectedEvaluation = evaluateTagged([["load", "holds"]], { holds: 1 }); + expect(outcome).toEqual({ ok: true, value: 1 }); + const widened: EvaluateResult = outcome; + expect(widened).toBe(outcome); + }); + it("takes an options object written for the main entry point too", () => { const shared: EvaluateOptions = { onUnbound: "error" }; const options: TaggedEvaluateOptions = { ...shared, tagged: true };