Lower interpolation into a call to concat#16556
Conversation
❗ Release notes required
|
a44ceed to
e9e4f4b
Compare
79260a2 to
fdea8a5
Compare
Sanity check that lowering to concat does not break these simple cases
Initial attempt with many TODOs, also not sure whether it should be done in checking, but it seems that later we would have to again parse the string (since CheckExpressions is going from AST version of an interpolated string to a sprintf call basically)
Cannot really optimize this way if width and other flags are specified. Typed interpolated expressions should be possible to support, but skipping them for now (TODO).
E.g. $"{x}{y}" has 5 string parts, including 3 empty strings
There were false positives before
fdea8a5 to
a60c558
Compare
|
Also, if anyone is curious about same benchmarks but with server GC:
With optimization:
|
Well yeah I just noticed that it also deals with <5 parts so I wonder if your PR doesn't somehow supersede that thing. |
It doesn't supersede it for sure. |
|
Alright then, thanks for the explanation there :) |
|
/run fantomas |
Co-authored-by: psfinaki <5451366+psfinaki@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
A string-typed interpolated string is lowered to System.String.Concat of its parts rather than the reflection-based printf engine: a string-typed hole is passed through directly, any other plain hole is converted with `string x`, an aligned/formatted hole with `String.Format(InvariantCulture, ...)`, and a printf-specifier hole with `sprintf`. This removes the reflection dependency on the common path, so these interpolations become trim- and NativeAOT-compatible. This generalizes and replaces the language-version-gated String.Concat optimization (dotnet#16556), which only handled all-string holes: the lowering now applies to every string-typed interpolation, ungated. The reflection path is used only for PrintfFormat/FormattableString-typed interpolation. The syntax tree now carries each hole's formatting explicitly, so a printf specifier no longer leaks into an adjacent literal and alignment is no longer a fake tuple: type SynInterpolatedStringPart = | String of value: string * range: range | FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting type SynInterpolationFormatting = | DotNet of alignment: SynExpr option * format: Ident option | Printf of specifier: string * range: range Behavioural change: plain `{x}` holes now render with invariant culture (the F# `string` operator) rather than the current thread culture, matching `string`. Adds a NativeAOT regression test under tests/AheadOfTime/NativeAOT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A string-typed interpolated string is lowered to System.String.Concat of its parts rather than the reflection-based printf engine: a string-typed hole is passed through directly, any other plain hole is converted with `string x`, an aligned/formatted hole with `String.Format(InvariantCulture, ...)`, and a printf-specifier hole with `sprintf`. This removes the reflection dependency on the common path, so these interpolations become trim- and NativeAOT-compatible. This generalizes and replaces the language-version-gated String.Concat optimization (dotnet#16556), which only handled all-string holes: the lowering now applies to every string-typed interpolation, ungated. The reflection path is used only for PrintfFormat/FormattableString-typed interpolation. The syntax tree now carries each hole's formatting explicitly, so a printf specifier no longer leaks into an adjacent literal and alignment is no longer a fake tuple: type SynInterpolatedStringPart = | String of value: string * range: range | FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting type SynInterpolationFormatting = | DotNet of alignment: SynExpr option * format: Ident option | Printf of specifier: string * range: range Behavioural change: plain `{x}` holes now render with invariant culture (the F# `string` operator) rather than the current thread culture, matching `string`. Adds a NativeAOT regression test under tests/AheadOfTime/NativeAOT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite TcInterpolatedStringViaConcat to type-check each interpolation
part in place and convert it to a string expression, then String.Concat
them. This removes the parallel 'holeIsString' bool list and the
flat-fillExprs/dense-parts interleave entirely: 'build' now walks a
single list (the parts), threading only tpenv.
- Plain '{x}' holes are built directly in the typed tree: a string is
passed through raw (matching dotnet#16556's lean IL), anything else is
converted via the 'string' operator, emitted through a new
string_operator_info intrinsic + mkCallStringOperator helper.
- Aligned/formatted and printf holes are checked from a small synthesized
String.Format/sprintf expression, so name resolution still does the BCL
work.
- The function-value warning is re-homed per-hole.
Known follow-ups: ill-typed formatted holes currently report their error
twice (the formatted arm type-checks the hole once for the warning and
again inside String.Format); the warning wants to move to its own pass
over hole types, which also removes that double check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Description
Optimization that lowers string interpolation into a call to concat iff there are at most 4 string parts and all fill expressions are strings.
Fixes #16247
Benchmarks
Run a benchmark like in this gist with and without the feature flag set.
Results without the flag (no optimization):
Results with the flag set (with optimization):
Checklist