Skip to content

Allow interpolated strings in constant expressions - #20753

Open
xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:interpolated-string-constants
Open

xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:interpolated-string-constants

Conversation

@xperiandri

Copy link
Copy Markdown
Contributor

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

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>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 07:10
@xperiandri
xperiandri requested a review from a team as a code owner October 10, 2026 07:10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

✅ Release notes checked


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.200.md
`src/Compiler/Facilities/LanguageFeatures.fsi` docs/release-notes/.Language/preview.md

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +7831 to +7834
| 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Compiler/FSComp.txt Outdated
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."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 11, 2026
…-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 T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ 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
T-Gro self-requested a review October 13, 2026 11:18
@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Oct 13, 2026

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and StaticStringValueProvider.
  • 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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewed PR reviewed by AI review council

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants