perf_lint: add PERF025 — redundant string() in string interpolation - #2912
Conversation
Flags a `string(value)` cast used as a direct string-interpolation element:
the interpolation already stringifies every element via the builder's
DebugDataWalker, so the cast just allocates a throwaway string the builder
then copies.
Fires for stringify-equivalent value types (signed ints, float, double,
string, das_string). Unsigned ints also warn but with a `{x:d}` format-tag
hint, since they interpolate as hex by default while string() gives decimal.
Skips value-shape conversions (array<uint8>/uri/text_range) and string()
nested as an argument to another call (e.g. length(string(x))).
Adds the lint fixture, rst reference section, and skill table row. Also drops
a redundant string(das_string) the rule found in perf_lint's own PERF024
message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new performance lint rule PERF025 to daslib/perf_lint.das that flags string(value) casts used as direct string-interpolation elements (e.g. "{string(iv)}"), since the string builder's DebugDataWalker already stringifies elements, making the cast a wasted allocation. The rule classifies the wrapped value to decide whether to warn plainly (signed ints, float, double, string, das_string) or warn with a :d format-tag hint (unsigned ints, which interpolate as hex by default), and intentionally skips value-shape-changing conversions (array<uint8>, uri, text_range), nested string(...) arguments, and explicit string(x, true) hex requests.
Changes:
- Add
Perf025Kindenum, three helpers, and apreVisitExprStringBuilderElementoverride implementing the rule. - Add fixture
utils/lint/tests/perf025_redundant_string_in_interp.daswithexpect 31208:8covering all equivalent and unsigned-hex cases plus negative cases. - Drop a redundant
string(das_string)cast inside PERF024's own warning message (a self-hit from the new rule), and updatelint.rstandskills/perf_lint.mddocumentation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| daslib/perf_lint.das | Implements PERF025 (enum, helpers, visitor hook) and removes a self-hit in PERF024's message. |
| utils/lint/tests/perf025_redundant_string_in_interp.das | New fixture exercising 8 bad cases (5 equivalent + 3 unsigned_hex) and 4 good cases. |
| doc/source/reference/language/lint.rst | Reference section describing PERF025 behavior, the :d hint, and skipped cases. |
| skills/perf_lint.md | Adds a PERF025 row summarizing detection shape and edge cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
New
daslib/perf_lint.dasrule PERF025: flags astring(value)cast used as a direct string-interpolation element. The interpolation already stringifies every element via the string builder'sDebugDataWalker, so the cast just allocates a throwaway string the builder then copies.Behavior
Fires for stringify-equivalent value types — where
"{x}"andstring(x)produce identical text (verified empirically):int/int8/int16/int64,float,double,string,das_stringuint/uint8/uint16/uint64{x:d}hint: unsigned interpolates as hex by default ("{42u}"→0x2a) whilestring()gives decimalDeliberately skipped (dropping the cast would change the value, not just the format — no format tag fixes it):
string(array<uint8>)(bytes→text),string(uri),string(text_range)string()nested as an argument to another call, e.g."{length(string(x))}"— only direct interpolation elements are flaggedstring(x, true)Detection lives in the per-element
preVisitExprStringBuilderElementhook; the kind is modeled as an enum (Perf025Kind { skip; equivalent; unsigned_hex }). A format spec ("{x:fmt}") lowers to_::fmt(":fmt", x), butfmthas no string-valued overload, sostring(x)can never sit under a fmt wrapper — no unwrap needed.Also drops one redundant
string(das_string)the new rule found inperf_lint's own PERF024 message string.Files
daslib/perf_lint.das— the rule (enum + 3 helpers + 1 visitor override) and the self-hit fixutils/lint/tests/perf025_redundant_string_in_interp.das— fixture (expect 31208:8)doc/source/reference/language/lint.rst— PERF025 reference sectionskills/perf_lint.md— rules-table rowTest plan
utils/lint/main.dason changed.dasfiles → 0 warnings (PERF025 is lint-clean against its own code)error[31208](5 plain-drop + 3:d-hint); good cases produce 0; matchesexpect 31208:8"{string(fn.name)}") confirmed firing; direct"{fn.name}"does notperf*lint fixtures still match their declaredexpectcounts (no regression)detect-dupeon the new helpers → no duplicatesdas-fmt --verifypassed on the full tree via pre-push hook)lint.rstchange (no warnings attributable to it)AOT/JIT steps are N/A: this is a compile-time lint pass that touches no codegen and is required by no AOT/runtime test.
Follow-up (separate sweep, not this PR): the rule surfaces a handful of pre-existing hits elsewhere in the tree (
utils/mcp/subtools/aot.das,tutorials/macros/function_macro_mod.das,modules/dasClangBind/cbind/cbind_boost.das).🤖 Generated with Claude Code