Add configurable UTF-16 string semantics - #211
Conversation
|
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.
8c1d8fb to
543c617
Compare
namin
left a comment
There was a problem hiding this comment.
some minor comments. i find this change a bit invasive. not sure i am ready to merge.
| // 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 |
| 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).", |
There was a problem hiding this comment.
no neef to ref DESIGN_STRINGS.md?
There was a problem hiding this comment.
removed the reference.
|
|
||
| /** | ||
| * 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 |
There was a problem hiding this comment.
again not sure why we are referencing the doc? should make sense on its own
There was a problem hiding this comment.
remove the reference.
| case "real": return "real"; | ||
| case "bool": return "bool"; | ||
| case "string": return "string"; | ||
| case "string": _usesStrings = true; return "string"; |
There was a problem hiding this comment.
need to make sure _usesStrings is reset properly
There was a problem hiding this comment.
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);
| 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]})`; |
There was a problem hiding this comment.
I would rather std-lib vs not for collections was a separate option, and utf16 forces one way
| * 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"; |
| 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" |
There was a problem hiding this comment.
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. " + |
There was a problem hiding this comment.
this seems misleading. should mention the local option
| `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 |
There was a problem hiding this comment.
Source dependencies sentence unclear.
| 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: |
There was a problem hiding this comment.
it's not clear that this recommendation is not blanket
|
|
||
| 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 |
There was a problem hiding this comment.
dafnyVerify is an internal detail
| `--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 |
There was a problem hiding this comment.
this is providing justification for the setup?
| | `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 |
| } | ||
|
|
||
| /** Detect Std-qualified code references, not text inside literals or comments. */ | ||
| function usesDafnyStandardLibrary(content: string): boolean { |
There was a problem hiding this comment.
this change seems excessive for what it precents. let's just revert this commit.
|
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! |
This reverts commit 0c78eab.
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:
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,
splitremains 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.