Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.200.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### Added

* F# Interactive gains a JSON-RPC server mode, `--fsi-server-jsonrpc:<pipe name>`, in which a host submits interactions over a named pipe and receives structured results — diagnostics with positions, escaping exceptions, the values each interaction bound, and the session's own process id — instead of recovering them by looking for a `SERVER-PROMPT>` marker in the output text. Program output continues to flow through the redirected console streams. The pipe admits only the user running the session; `--fsi-server-client-pid:<pid>` names the host process whose exit ends the session. `FsiEvaluationSession` exposes both options as `JsonRpcServerPipeName` and `JsonRpcClientProcessId`. The mode is part of the .NET fsi only. ([PR #20396](https://github.com/dotnet/fsharp/pull/20396))
* Added the `ConstantInterpolatedStrings` language feature (`--langversion:preview`): an interpolated string whose holes are non-null constant strings without alignment or format specifiers is a constant expression, so `[<Literal>]` values, attribute arguments and type provider static arguments accept it instead of reporting FS0267. ([Suggestion #1347](https://github.com/fsharp/fslang-suggestions/issues/1347), [RFC FS-1352](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1352-interpolated-strings-in-constant-expressions.md), [PR #20753](https://github.com/dotnet/fsharp/pull/20753))

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Features assigned to F# 11.2 are listed in [11.2](https://fsharp.github.io/fshar

### Added

* Allow interpolated strings in constant expressions: when each hole is a non-null constant string without alignment or format specifiers, as in C#, an interpolated string can define a `[<Literal>]` value, an attribute argument or a type provider static argument, such as `[<Obsolete($"Use {nameof getItemV2} instead")>]` (`ConstantInterpolatedStrings` preview feature). Outside these contexts interpolated strings, and their quotations, are unchanged. ([Suggestion #1347](https://github.com/fsharp/fslang-suggestions/issues/1347), [RFC FS-1352](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1352-interpolated-strings-in-constant-expressions.md), [PR #20753](https://github.com/dotnet/fsharp/pull/20753))

### Fixed

### Changed
3 changes: 3 additions & 0 deletions src/Compiler/Checking/CheckBasics.fs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ type TcEnv =

eInNameOf: bool

/// Checking an expression that must evaluate to a constant: a literal, an attribute argument or a static argument.
eInConstantContext: bool

/// Are we checking the body of an object expression? Such a body has family access to the
/// implemented type, but its closures are not nested under that type, so they cannot keep it (#5302).
eInObjectExpr: bool
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Checking/CheckBasics.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ type TcEnv =
/// Resolving a nameof operand, rather than an executable expression.
eInNameOf: bool

/// Checking an expression that must evaluate to a constant: a literal, an attribute argument or a static argument.
eInConstantContext: bool

/// Are we checking the body of an object expression? Such a body has family access to the
/// implemented type, but its closures are not nested under that type, so they cannot keep it (#5302).
eInObjectExpr: bool
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6097,6 +6097,7 @@ let emptyTcEnv g =
eIsIndexerSetter = false
eIsControlFlow = false
eInNameOf = false
eInConstantContext = false
eInObjectExpr = false
eCaughtExceptionVal = ValueNone
eCachedImplicitYieldExpressions = HashMultiMap(HashIdentity.Structural, useConcurrentDictionary = true)
Expand Down
46 changes: 43 additions & 3 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5048,7 +5048,7 @@ and TcStaticConstantParameter (cenv: cenv) (env: TcEnv) tpenv kind (StripParenTy
| SynType.StaticConstantExpr(e, _ ) ->

// 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.


// Evaluate the constant expression using static attribute argument rules
let te = EvalLiteralExprOrAttribArg g te
Expand Down Expand Up @@ -7803,6 +7803,44 @@ and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv,

TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> resultExpr, tpenv)

/// Fold a string-typed interpolated string in a constant context to its text (RFC FS-1352). Each hole must be
/// a non-null constant string without alignment or format specifiers.
and TcConstantInterpolatedString (cenv: cenv, overallTy: OverallTy, env: TcEnv, m: range, tpenv: UnscopedTyparEnv, parts: SynInterpolatedStringPart list) =
let g = cenv.g
checkLanguageFeatureAndRecover g.langVersion LanguageFeature.ConstantInterpolatedStrings m

let holeText (fill: Expr) (formatting: SynInterpolationFormatting) (mHole: range) =
let invalidHole () =
errorR (Error(FSComp.SR.tcConstantInterpolatedStringHole (), mHole))
ValueNone

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

| Expr.Const(Const.String text, _, _) -> ValueSome text
| Expr.Const(Const.Zero, _, _) -> invalidHole ()
| _ -> ValueNone // EvalLiteralExprOrAttribArg has reported that the hole is not a constant
| _ -> invalidHole ()

let text = Text.StringBuilder()
let mutable tpenvAcc = tpenv
// Only the first hole that breaks a rule is reported; later holes are still type-checked.
let mutable holesAreConstant = true

for part in parts do
match part with
| SynInterpolatedStringPart.String(s, _) -> text.Append(s.Replace("%%", "%")) |> ignore
| SynInterpolatedStringPart.FillExpr(synFill, formatting) ->
let fill, tpenvAfter = TcExprFlex2 cenv (NewInferenceType g) env false tpenvAcc synFill
tpenvAcc <- tpenvAfter

if holesAreConstant then
match holeText fill formatting synFill.Range with
| ValueSome hole -> text.Append(hole: string) |> ignore
| ValueNone -> holesAreConstant <- false

TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> mkString g m (text.ToString()), tpenvAcc)

/// Check an interpolated string expression
and [<TailCall>] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs =
match argTys, synFillExprs with
Expand Down Expand Up @@ -7950,6 +7988,8 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
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")

TcConstantInterpolatedString (cenv, overallTy, env, m, tpenv, parts)
elif isString then
// String-typed interpolation: lower to a reflection-free System.String.Concat of the parts,
// type-checking each hole in place (no separate batch, no flat fill-expression list).
Expand Down Expand Up @@ -11806,7 +11846,7 @@ and TcLiteral (cenv: cenv) overallTy env tpenv (attrs, synLiteralValExpr) =
hasFlag valFlags WellKnownValAttributes.LiteralAttribute

if hasLiteralAttr then
let literalValExpr, _ = TcExpr cenv (MustEqual overallTy) env tpenv synLiteralValExpr
let literalValExpr, _ = TcExpr cenv (MustEqual overallTy) { env with eInConstantContext = true } tpenv synLiteralValExpr
match EvalLiteralExprOrAttribArg g literalValExpr with
| Expr.Const (c, _, ty) ->
if c = Const.Zero && isStructTy g ty then
Expand Down Expand Up @@ -12012,7 +12052,7 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
let meths = minfos |> List.map (fun minfo -> minfo, None)
let afterResolution = ForNewConstructors cenv.tcSink env tyId.idRange methodName minfos
let (expr, attributeAssignedNamedItems, _), _ =
TcMethodApplication true cenv env tpenv None [] mAttr mAttr methodName None ad PossiblyMutates false meths afterResolution NormalValUse [arg] (MustEqual ty) None []
TcMethodApplication true cenv { env with eInConstantContext = true } tpenv None [] mAttr mAttr methodName None ad PossiblyMutates false meths afterResolution NormalValUse [arg] (MustEqual ty) None []

UnifyTypes cenv env mAttr ty (tyOfExpr g expr)

Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1806,3 +1806,5 @@ featureTypeArgumentDependencyOrdering,"ordering explicit type arguments by their
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 must be a constant, so each hole must be a non-null constant expression of type 'string' without alignment or format specifiers."
featureConstantInterpolatedStrings,"interpolated strings in constant expressions"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type LanguageFeature =
| ErrorOnBitwiseOpsOnNonIntegralEnums
| OptimizeClosureIfNotInlined
| ReraiseInComputationExpressions
| ConstantInterpolatedStrings

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -178,6 +179,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
// F# preview
// Unfinished features that still need work before they can be assigned a release language version.
LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work
LanguageFeature.ConstantInterpolatedStrings, previewVersion
]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -327,6 +329,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.ErrorOnBitwiseOpsOnNonIntegralEnums -> FSComp.SR.featureErrorOnBitwiseOpsOnNonIntegralEnums ()
| LanguageFeature.OptimizeClosureIfNotInlined -> FSComp.SR.featureOptimizeClosureIfNotInlined ()
| LanguageFeature.ReraiseInComputationExpressions -> FSComp.SR.featureReraiseInComputationExpressions ()
| LanguageFeature.ConstantInterpolatedStrings -> FSComp.SR.featureConstantInterpolatedStrings ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type LanguageFeature =
| ErrorOnBitwiseOpsOnNonIntegralEnums
| OptimizeClosureIfNotInlined
| ReraiseInComputationExpressions
| ConstantInterpolatedStrings

/// LanguageVersion management
type LanguageVersion =
Expand Down
10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading