Skip to content

Add configurable UTF-16 string semantics - #211

Merged
namin merged 16 commits into
midspiral:mainfrom
quangng2000:codex/issue-210-js-utf16-strings
Oct 1, 2026
Merged

namin merged 16 commits into
midspiral:mainfrom
quangng2000:codex/issue-210-js-utf16-strings

Conversation

@quangng2000

@quangng2000 quangng2000 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Adds opt-in UTF-16 semantics for supported JavaScript string operations, including astral text and lone surrogates. Unicode-scalar semantics remain the default.

For a standalone file:

//@ backend dafny
//@ option string-semantics javascript-utf16
//@ option dafny-library local

Both options also support project defaults in lemmascript.json. UTF-16 requires local collection helpers because Dafny's precompiled standard library uses Unicode-scalar characters. Imported source files must share the same string model.

Every generated UTF-16 file records its mode in the header. Proof additions cannot override that mode or introduce duplicate headers, including during regen --no-verify. Emitting the header unconditionally removes string-usage tracking and covers strings created only by helpers.

Standard-library detection checks Std-qualified code references, ignoring literals and comments. Ordinary text such as "Std." no longer rejects UTF-16/local proofs; genuine library imports and calls remain incompatible.

The existing fragment limits still apply: indexing and slicing have bounds obligations, case conversion is ASCII-only, split remains axiomatic, and Lean does not support UTF-16 mode.

The design document now describes the implemented behavior without speculative profiles or roadmap sections; collection tables distinguish the default standard library from opt-in local helpers.

Validated: build, example typecheck, all 187 unit tests, the full fixture suite, and real frontend/Dafny regressions for surrogate helpers and "Std." text with JavaScript result checks.

Closes #210.

@namin

namin commented Aug 29, 2026

Copy link
Copy Markdown
Member

This seems like a reasonable change, but it's breaking. Many case studies need to be updated already. Let me ponder whether we need per-project configuration (as another PR).

…pt.json

`string-semantics: "unicode-scalar" | "javascript-utf16"` (default
`unicode-scalar`, config-only) replaces the presence-triggered marker: the
first cut flipped every string-bearing file to UTF-16 and text-scanned the
mark in the verifier, which reinterpreted every case study's obligations on
merge. No config means today's output byte for byte — the 71 examples are
drift-clean and no artifact changes.

The artifact declares the model, not the config. A javascript-utf16 file
that uses `string` carries `// lsc options: string-semantics=javascript-utf16`
(the DESIGN_CONFIG.md §5 form); `dafnyVerify` pins the char mode either
way — `--unicode-char:true` when the token is absent, since a default is
not a pin, and `--unicode-char:false --allow-deprecation` when present.
Only the `:false` value is deprecated in Dafny 4.11, and `--allow-deprecation`
waives exactly that warning; the blanket `--allow-warnings` would also have
un-fatalled vacuity and missing-{:axiom} warnings.

Under javascript-utf16 the emitter writes non-ASCII units as `\uXXXX`
escapes, `String.fromCharCode` admits `0 <= n < 0x10000`, `IsJSWhitespace`
uses the `\u` escape form that mode requires, and filter/every/reduce use
local helpers because the precompiled standard library cannot load under
`--unicode-char:false` (a hard Dafny error). Under unicode-scalar each of
those emits what it always has, and a source literal containing an unpaired
surrogate is refused at extraction with its line rather than silently
replaced by the UTF-8 writer. The profile is Dafny-only: Lean's String is
scalar-based, so `--backend=lean` refuses it.

Merges main (0.6.4) so the option registry is available; the earlier
base predated tools/src/config.ts.

Design: DESIGN_STRINGS.md.
@quangng2000 quangng2000 changed the title Fix Dafny string semantics to match JavaScript UTF-16 Model JavaScript strings as a versioned profile selected in lemmascript.json Sep 22, 2026
@quangng2000
quangng2000 force-pushed the codex/issue-210-js-utf16-strings branch from 8c1d8fb to 543c617 Compare September 26, 2026 03:14

@namin namin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some minor comments. i find this change a bit invasive. not sure i am ready to merge.

Comment thread tools/src/config.ts Outdated
// as the single resolution gate: future dependent defaults (UTF-16 → local
// Dafny library) and incompatibilities belong here, before any consumer runs.
// There are no cross-option constraints in the registry: `string-semantics`
// selects the Dafny helper source by itself (DESIGN_STRINGS.md §4). Keep this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not clear

Comment thread tools/src/config.ts Outdated
values: ["unicode-scalar", "javascript-utf16"],
default: "unicode-scalar",
fileOverride: false,
description: "Which model of JavaScript strings a Dafny proof is made under (DESIGN_STRINGS.md).",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no neef to ref DESIGN_STRINGS.md?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the reference.

Comment thread tools/src/dafny-commands.ts Outdated

/**
* Verifier arguments implied by a generated file's `// lsc options:` header
* (DESIGN_STRINGS.md §5–6). Read from the artifact rather than the config, so a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again not sure why we are referencing the doc? should make sense on its own

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the reference.

Comment thread tools/src/dafny-emit.ts Outdated
case "real": return "real";
case "bool": return "bool";
case "string": return "string";
case "string": _usesStrings = true; return "string";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to make sure _usesStrings is reset properly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset is at the start of each files
export function emitDafnyFile(file: Module, tsFileName?: string, options: LscOptions = DEFAULT_OPTIONS): string {
_useSafeSlice = options["safe-slice"];
_stringSemantics = options["string-semantics"];
// Reset before scanning types, including when the previous file failed to emit.
_usesStrings = false;
resetDafnyNameCache();
buildRecordCtorMap(file.decls);

Comment thread tools/src/dafny-emit.ts Outdated
if (e.method === "filterSome") { needPreamble("SeqFilterSome"); needPreamble("OptionType"); return `SeqFilterSome(${obj})`; }
if (e.method === "every") return `Std.Collections.Seq.All(${obj}, ${args[0]})`;
if (e.method === "every") {
if (!isUtf16()) return `Std.Collections.Seq.All(${obj}, ${args[0]})`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather std-lib vs not for collections was a separate option, and utf16 forces one way

Comment thread tools/src/dafny-commands.ts Outdated
* missing-{:axiom} warnings, which a verifier must keep fatal.
*/
export function dafnyVerifyArgs(content: string, timeLimit?: number, extraFlags?: string): { args: string[]; error?: string } {
let stringSemantics = "unicode-scalar";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why doesn't this use config.ts?

Comment thread tools/test-fixtures.sh
cp tools/fixtures/deterministic-extern-equality.ts "$fixture_dir/deterministic.ts"
cp tools/fixtures/impure-extern-equality.ts "$fixture_dir/impure.ts"
cp examples/safeSlice.ts "$fixture_dir/legacy-safe-slice.ts"
cp -R tools/fixtures/utf16-project "$fixture_dir/utf16-project"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also have an ordinary example with the @ option directive

Default dafny-library to stdlib independently of the string model. Require an explicit local selection for javascript-utf16 and reject incompatible settings through the shared config resolver.

Route filter, every, and reduce through the library option; cover configuration errors, per-file state reset, and real Dafny proofs across the three valid combinations. Update fixtures and documentation.
Allow string-semantics and dafny-library directives before source statements. Validate merged settings and require matching string models across TypeScript-resolved source dependencies and compiler-selected cross-file callees.

Add examples/utf16.ts and its Dafny proof, cover option precedence and dependency boundaries, and update docs and diagnostics. Existing generated example artifacts remain unchanged.
const usesStandardLibrary = content.includes("Std.");
if (utf16 && usesStandardLibrary) {
return { args: [], error:
"ERROR: this proof combines \"string-semantics\": \"javascript-utf16\" with Dafny's standard library. " +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems misleading. should mention the local option

Comment thread SPEC.md Outdated
`proof-dir` mirrors the source's config-relative path under its configured root
(see SPEC_DAFNY.md §1). `lsc config foo.ts` prints the config path, effective
(see SPEC_DAFNY.md §1). `string-semantics` selects which model of JavaScript strings a
Dafny proof is made under (SPEC_DAFNY.md §4). Source dependencies must use the same

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source dependencies sentence unclear.

Comment thread SPEC.md Outdated
options, and resolved Dafny artifact directory; `lsc config` reports defaults
from the current directory. `backend` is deliberately not a config option.

For a standalone file, put both settings before its first statement:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not clear that this recommendation is not blanket

Comment thread SPEC_DAFNY.md Outdated

The shared `--time-limit=<seconds>` flag (SPEC.md §7) maps to Dafny's `--verification-time-limit`; `--extra-flags=<string>` is forwarded verbatim to `dafny verify`.
The char mode is read from the artifact, not the config, so a standalone `.dfy`
verifies under the model it was generated for: `dafnyVerify` pins

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dafnyVerify is an internal detail

Comment thread SPEC_DAFNY.md Outdated
`--unicode-char:true` unless the header carries
`// lsc options: string-semantics=javascript-utf16`, in which case it passes
`--unicode-char:false --allow-deprecation` (only the `:false` value is deprecated
in Dafny 4.11; `--allow-deprecation` waives exactly that warning, whereas

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is providing justification for the setup?

Comment thread SPEC.md Outdated
| `A \| B` (union param) | field intersection type | field intersection type |
| Anything else | Pass through | Pass through |

For the Dafny backend, `string` is verified in Dafny's UTF-16 code-unit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the default, right?

@quangng2000 quangng2000 changed the title Model JavaScript strings as a versioned profile selected in lemmascript.json Add configurable UTF-16 string semantics Sep 30, 2026
Comment thread tools/src/dafny-commands.ts Outdated
}

/** Detect Std-qualified code references, not text inside literals or comments. */
function usesDafnyStandardLibrary(content: string): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change seems excessive for what it precents. let's just revert this commit.

@namin

namin commented Oct 1, 2026

Copy link
Copy Markdown
Member

Hi @quangng2000 -- thanks for all your work here. This is almost ready to merge. Can you just revert the commit I flagged for revert? Then I'll do a final end-to-end review, and it should be ready to merge. Thanks, again!

@namin
namin merged commit 37eb3f2 into midspiral:main Oct 1, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align Dafny string semantics with JavaScript UTF-16 strings

4 participants