SY-4159: Arc String Formatting#2310
Conversation
…tring stl; is now a language feature implemented in analyzer/compiler pipeline
…g of codes against unsupported types
Removes `{{` / `}}` escapes (clearing the way for `\{` / `\}`), rewrites Parse on strings.IndexAny, surfaces parse errors as diagnostics, and adds 67 specs.
Add Start, End, and SpecOffset fields to fmtstring.Segment. The LSP delegates all brace/escape/spec parsing to fmtstring.Parse; the analyzer anchors per-placeholder diagnostics on the {...} span.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## rc #2310 +/- ##
==========================================
+ Coverage 64.97% 65.22% +0.25%
==========================================
Files 2603 2608 +5
Lines 113946 114416 +470
Branches 8399 8410 +11
==========================================
+ Hits 74035 74629 +594
+ Misses 33774 33655 -119
+ Partials 6137 6132 -5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@pjdotson is a better reviewer for this pr :) |
- placeholderSuggest: PLACEHOLDER_RE skips escaped `\{` so autocomplete no longer fires on escape sequences
- fmtstring: rename ValidateNumericSpec → ValidateSpec; accept string, constant, and KindVariable types (recurses into Constraint)
- stl/strings: add format_string host fn + symbol entry so string placeholders with specs route through fmt.Sprintf
- compiler: EmitStringFormat + extend numericSuffix to cover constant kinds, so bare {123%T} and {3.14%.2f} compile
- analyzer: drop blanket rejection of specs on string-typed placeholders
pjdotson
left a comment
There was a problem hiding this comment.
Mostly looks good. I haven't looked through the Go code yet, wanted to get general functionality stuff back to you incase you have to change a lot. A couple issues:
LSP color formatting is a little weird. If i mistype something like this, .2 gets colored in red. Shouldn't this instead be a compiler error?
time.wait {
0s
} -> `Price is ${float+float2%f.2}` -> arc_string_test
See https://linear.app/synnax/issue/SY-4171/fix-arc-node-update-bug, I don't know if this was a preexisting issue or an issue introduced here.
|
|
||
| | Spec | Types | Use | Example | | ||
| | ------- | -------- | ----------------- | ----------------------- | | ||
| | `%d` | int | Decimal | `42` → `42` | |
There was a problem hiding this comment.
wht about unsigned decimal?
There was a problem hiding this comment.
%d works for both. The int label in the Types column covers signed (i8 to i64) and unsigned (u8 to u64). Go's fmt formats both as plain decimal under %d, so there's no separate %u verb.
| | `%X` | int, str | Hex, uppercase | `255` → `FF` | | ||
| | `%c` | int | Unicode character | `65` → `A` | | ||
| | `%U` | int | Unicode escape | `65` → `U+0041` | | ||
| | `%f` | float | Decimal | `3.14` → `3.140000` | |
There was a problem hiding this comment.
why can't i format integers with %f?
There was a problem hiding this comment.
Same reason as above: format specs pass straight through to Go's fmt.Sprintf, and Go's fmt only accepts %f for floats. Calling fmt.Sprintf("%f", int64(0)) returns "%!f(int64=0)", which our validator catches at compile time.
|
- Change verb delimeter from `%` to `:` - Allow for unescaped `}` to be `}` and simplify bracket placeholder detection logic - Add verbs to blacklist - Expand on tests - Update documentation - Fix auto suggest inside literal placeholders appearing with "No suggestions" bug
Issue Pull Request
Linear Issue
SY-4159
Description
Adds
{...}placeholder interpolation to Arc backtick raw string literals, with optional%specformat codes for numeric values and\{/\}escapes for literal braces. Placeholders work inline:and in flow form, where the placeholder body is registered as a synthetic function activated by an upstream trigger:
The feature spans five layers:
STR_LITERAL_RAWtoken now allows{...}and\{/\}inside its body.fmtstringpackage owns placeholder parsing, spec splitting, and numeric spec validation.AnalyzeStringFmtLiteralwalks each placeholder, type checks the inner expression, and anchors per-placeholder diagnostics on the specific{...}span via a newPosition.Advance/Diagnostic.WithRangehelper pair onx/go/diagnostics.EmitNumericToString/EmitNumericFormathost calls (arc/go/compiler/expression/string_fmt.go). Flow form lowers to a synthetic zero input WASM function gated by thefmt$IR prefix (arc/go/compiler/string_fmt.go).format_i32/u32/i64/u64/f32/f64host functions inarc/go/stl/stringsread the spec bytes from WASM memory and callfmt.Sprintf. The existingfrom_*helpers were collapsed onto genericregisterFrom/registerFormatfactories.expandRawStringPlaceholdersdelegates tofmtstring.Parseand emits semantic tokens for placeholder braces, the inner expression, and the%spectail. A newstringPlaceholdercolor is registered. A small Monaco trigger inEditor.tsxworks aroundmonaco-vscode-apiignoring the languagetokenTypesconfig so completions still fire inside{...}.The
fmtstringpackage is the single source of truth for placeholder position information. The previous hand rolled scanner in the LSP has been deleted, eliminating the drift hazard whenever escape or spec syntax evolves.Basic Readiness
Greptile Summary
This PR adds
{...}placeholder interpolation to Arc backtick raw string literals, spanning the parser, analyzer (fmtstringpackage), compiler (inline concat chain and synthetic WASM functions), runtime (newformat_*host functions), and Console LSP (semantic tokens + Monaco completion trigger).fmtstringpackage is the single source of truth for placeholder parsing; it uses:as the spec separator (e.g.{x:.2f}), handles\{escape for literal braces, and validates specs via a regex shape check plus a%!-detection probe, with a correct blacklist for verbs likev,T, andU.from_*/format_*host calls and lowers flow-form literals to synthetic zero-inputfmt$-prefixed IR functions; thecompileNumericLiteralguard that now also resets a non-numeric hint prevents string-context type leakage into numeric literal compilation.from_*registration to genericregisterFrom/registerFormathelpers and addsformat_string;formatWithSpecdefensively returns""on nil-memory or failedRead, and theformat_stringhost function silently discards thes.Geterror — both silent empty-string fallbacks are hard to diagnose if a compiler bug produces a bad pointer or handle.Confidence Score: 5/5
Safe to merge; all findings are defensive-programming gaps that only surface on impossible-in-practice inputs (nil WASM memory, invalid string handle) or malformed format strings already rejected by the flow analyzer.
The feature spans five well-tested layers and the core paths — parsing, type checking, spec validation, WASM emission, and LSP tokenization — are all correct. The three flagged items are silent empty-string returns in runtime host functions (reachable only via a compiler bug) and a missing diagnostic guard in a code path the flow analyzer already covers earlier. No correct program is affected.
arc/go/stl/strings/string.go (silent failure in formatWithSpec and format_string) and arc/go/text/analyze.go (missing error propagation for fmtstring.Parse).
Important Files Changed
:, not%as in the PR description. Logic is well-tested with comprehensive edge-case coverage.Sequence Diagram
sequenceDiagram participant User as Arc Source participant Parser as Parser (STR_LITERAL_RAW) participant FmtPkg as fmtstring.Parse participant Analyzer as Analyzer participant Compiler as Compiler participant WASM as WASM Runtime participant Host as Host (string.format_*) User->>Parser: "backtick value is {sensor:.2f} backtick" Parser->>FmtPkg: Parse(body) FmtPkg-->>Analyzer: "[]Segment{literal, placeholder{expr,spec}}" Analyzer->>Analyzer: type-check expr, ValidateSpec(spec, t) Analyzer-->>Compiler: IR (inline or synthetic fmt$ function) Compiler->>WASM: "EmitFmtSegments → from_*/format_* calls" WASM->>Host: format_f32(value, spec_ptr, spec_len) Host->>Host: memory.Read(spec_ptr, spec_len) → %.2f Host->>Host: fmt.Sprintf(%.2f, value) Host-->>WASM: string handle WASM->>WASM: string.concat(literal_handle, fmt_handle) WASM-->>User: value is 3.14Comments Outside Diff (1)
arc/go/fmtstring/fmtstring.go, line 161-175 (link)ValidateNumericSpecaccepts non-numeric verbs like%Tand%vValidateNumericSpecvalidates by callingfmt.Sprintfand checking for%!in the output. However, verbs like%T(type name) and%v(generic) also pass this check for integer/float types —fmt.Sprintf("%T", int64(0))returns"int64", not"%!T(int64=0)". A user writing{x%T}would get the runtime type string"int32"instead of the numeric value, which is almost certainly unintended. The validator should be restricted to explicitly allow only numeric verbs (e.g.,d,f,e,g,x,o,b,son strings) rather than relying on the absence of%!.Reviews (7): Last reviewed commit: "SY-4159: Misc updates" | Re-trigger Greptile