Repository navigation
Allow interpolated strings in constant expressions - #20753
xperiandri wants to merge 3 commits into
Conversation
An interpolated string whose holes are non-null constant strings without alignment or format specifiers now folds to its text where F# requires a constant: [<Literal>] values (also in signatures), attribute arguments and type provider static arguments. It was rejected with FS0267, so a constant built from other constants needed '+'. The checker marks those three contexts in TcEnv and folds the string there. Everywhere else, quotations included, interpolated strings are still lowered to String.Concat. A hole that breaks a rule reports FS3925 at the hole; a hole that is not a constant reports FS0267 at the hole. Gated on the ConstantInterpolatedStrings preview feature. Language suggestion dotnet#1347 RFC FS-1352 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
✅ Release notes checked
|
There was a problem hiding this comment.
🟡 Changes recommended
Diagnostic behavior diverges from the RFC, and type-provider static-argument support lacks direct coverage.
3 open findings
What changed in this PR
Adds preview support for constant interpolated strings across literals, attributes, and type-provider static arguments.
Changes:
- Adds constant-context tracking and compile-time interpolation folding.
- Adds feature gating, FS3925, localization, and release notes.
- Adds component tests for literals, attributes, signatures, diagnostics, and quotations.
| File | Description |
|---|---|
tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs |
Adds feature tests. |
tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj |
Registers the tests. |
src/Compiler/xlf/FSComp.txt.zh-Hant.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.zh-Hans.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.tr.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.ru.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.pt-BR.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.pl.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.ko.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.ja.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.it.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.fr.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.es.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.de.xlf |
Adds localized resources. |
src/Compiler/xlf/FSComp.txt.cs.xlf |
Adds localized resources. |
src/Compiler/FSComp.txt |
Defines FS3925 and feature text. |
src/Compiler/Facilities/LanguageFeatures.fsi |
Declares the feature case. |
src/Compiler/Facilities/LanguageFeatures.fs |
Gates the preview feature. |
src/Compiler/Checking/Expressions/CheckExpressions.fs |
Implements constant-context folding. |
src/Compiler/Checking/CheckDeclarations.fs |
Initializes constant-context state. |
src/Compiler/Checking/CheckBasics.fsi |
Exposes the internal context field. |
src/Compiler/Checking/CheckBasics.fs |
Defines the context field. |
docs/release-notes/.Language/preview.md |
Documents the language feature. |
docs/release-notes/.FSharp.Compiler.Service/11.0.200.md |
Records the FCS change. |
🧠 Review effort: Balanced
|
|
||
| // If an error occurs, don't try to recover, since the constant expression will be nothing like what we need | ||
| let te, tpenv' = TcExprNoRecover cenv (MustEqual kind) env tpenv e | ||
| let te, tpenv' = TcExprNoRecover cenv (MustEqual kind) { env with eInConstantContext = true } tpenv e |
There was a problem hiding this comment.
Fixed in cc218d6: added A type provider static argument can be a constant interpolated string, which passes const ($"{Root}/items") and a [<Literal>] interpolated string to a provider and checks the text the provider received. It fails with FS0267 when the constant context is removed from TcStaticConstantParameter.
It uses a small provider in tests/fsharp/typeProviders/staticStringParameter rather than HelloWorldTypeWithStaticStringParameter: the helloWorld provider script is only built by the .NET Framework suite and does not compile on .NET (FS0041 on StartsWith), and it cannot report the argument's text back.
| | SynInterpolatedStringPart.FillExpr(synFill, formatting) -> | ||
| let fill, tpenvAfter = TcExprFlex2 cenv (NewInferenceType g) env false tpenvAcc synFill | ||
| tpenvAcc <- tpenvAfter | ||
| text.Append(holeText fill formatting synFill.Range) |> ignore |
There was a problem hiding this comment.
Fixed in cc218d6: after the first hole that breaks a rule, later holes are still type-checked but no longer evaluated as constants, so $"{1}{2}" reports FS3925 once. The same holds when the first failure is a non-constant hole (FS0267). Test: Only the first hole that breaks a rule is reported.
| 3923,tcMethodRequiresNamedArguments,"The method '%s' requires named arguments. Use named-argument syntax, e.g. 'MethodName(argumentName = value)'." | ||
| 3924,chkNativePtrStackallocInHandler,"'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded." | ||
| featureReraiseInComputationExpressions,"calls to 'reraise' in a computation expression handler" | ||
| 3925,tcConstantInterpolatedStringHole,"This interpolated string is a constant, so each hole must be a non-null constant expression of type 'string' without alignment or format specifiers." |
There was a problem hiding this comment.
Fixed in cc218d6: FS3925 now reads "This interpolated string must be a constant, so each hole must be …", as in the RFC. The xlf files are regenerated and the test assertions updated.
RFC FS-1352 reports the rule error on the first hole that breaks a rule.
Later holes are still type-checked, but are no longer evaluated as
constants, so '$"{1}{2}"' gives FS3925 once instead of once per hole.
Reword FS3925 to the RFC's "must be a constant": the message is shown
when a hole keeps the string from being one.
Cover type provider static arguments, after 'const' and through a
literal, with a provider that returns its string static argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-constants-wider-holes Bring in the review fixes of dotnet#20753: only the first hole that breaks a rule is reported, FS3925 says "must be a constant", and a type provider test covers static arguments. The wider hole rules stay as they are. The first-hole test uses aligned holes here, because integer holes are valid on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T-Gro
left a comment
There was a problem hiding this comment.
🤖 🕵️ AI review — verify independently.
| else | ||
| let str = mkString g m printfFormatString | ||
| mkCallNewFormat g m printerTy printerArgTy printerResidueTy printerResultTy printerTupleTy str, tpenv | ||
| elif isString && env.eInConstantContext then |
There was a problem hiding this comment.
🤖 🕵️ Previously valid short-circuit literals now fail with FS3925 in preview and FS3350 in stable versions — constant restrictions reach the unevaluated RHS.
module ShortCircuit
[<Literal>]
let Flag = false && ($"{1}" = "1")|
|
||
| match formatting with | ||
| | SynInterpolationFormatting.DotNet(None, None) when isStringTy g (tyOfExpr g fill) -> | ||
| match EvalLiteralExprOrAttribArg g fill with |
There was a problem hiding this comment.
🤖 🕵️ FS0039 for missing disappears — constant evaluation of the compound first hole throws before later holes are type-checked.
module LaterHole
let dir = "data"
[<System.Obsolete($"""{dir + "/"}{missing}""")>]
let A = 1
T-Gro
left a comment
There was a problem hiding this comment.
The diff is still way too big - apply the NoBloat instructions and CodeCompaction skill.
Especially the massive code comments would benefit not just with drastic reduction, but ideally with well named functions/arguments/bindings/types making the intention clear.
If a well named piece of code just tells what the comment says - delete the comment.
- Delete the implementation and test narration around constant context, hole evaluation, accumulated state, and provider fixture behavior. Use names such as
tryConstantHoleText,allPreviousHolesWereConstant, andStaticStringValueProvider. - Flatten the nested local helpers and mutable accumulator in
TcConstantInterpolatedString. - Reuse the existing static-string type-provider fixture and compact the separate diagnostic Facts into named theory rows.
- Keep one concise language-feature release note with syntax and restrictions. Delete the duplicate FCS prose and unchanged-quotation commentary.


Under
--langversion:preview, an interpolated string whose holes are non-null constant strings with no alignment or format specifier is a constant expression, as in C# 10:[<Literal>]values, attribute arguments and type provider static arguments accept it instead of reporting FS0267, e.g.[<Obsolete($"Use {nameof getItemV2} instead")>]. Everywhere else, quotations included, interpolated strings are unchanged.Details
--langversionreports the language-version error instead of FS0267.