Skip to content
Draft
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
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, such as `[<Literal>] let Items = $"{Root}/items"` and `[<Obsolete($"Use {nameof f} instead")>]`, when each hole is a non-null constant string, integer, decimal, character or Boolean with no alignment and no format specifier other than `%s` (`ConstantInterpolatedStrings` preview feature). ([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), [PR #20754](https://github.com/dotnet/fsharp/pull/20754))

### Fixed

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

eInNameOf: bool

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
2 changes: 2 additions & 0 deletions src/Compiler/Checking/CheckBasics.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ type TcEnv =
/// Resolving a nameof operand, rather than an executable expression.
eInNameOf: bool

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
53 changes: 50 additions & 3 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4202,6 +4202,31 @@ let EliminateNullnessFromInputType (g: TcGlobals) (inputTy: TType) (pat: Pattern
| None -> eliminateNull inputTy pat
| _ -> inputTy

[<return: Struct>]
let (|PrimitiveConstText|_|) (c: Const) =
match c with
| Const.SByte n -> ValueSome(string n)
| Const.Int16 n -> ValueSome(string n)
| Const.Int32 n -> ValueSome(string n)
| Const.Int64 n -> ValueSome(string n)
| Const.Byte n -> ValueSome(string n)
| Const.UInt16 n -> ValueSome(string n)
| Const.UInt32 n -> ValueSome(string n)
| Const.UInt64 n -> ValueSome(string n)
| Const.Decimal n -> ValueSome(string n)
| Const.Char n -> ValueSome(string n)
| Const.Bool n -> ValueSome(string n)
| _ -> ValueNone

let tryConstantHoleText (g: TcGlobals) (formatting: SynInterpolationFormatting) (mHole: range) (hole: Expr) =
match formatting, EvalLiteralExprOrAttribArg g hole with
| (SynInterpolationFormatting.DotNet(None, None) | SynInterpolationFormatting.Printf("%s", _)), Expr.Const(Const.String text, _, _) -> ValueSome text
| SynInterpolationFormatting.DotNet(None, None), Expr.Const(PrimitiveConstText text, _, ty) when not (isEnumTy g ty) -> ValueSome text
| _, Expr.Const _ ->
errorR (Error(FSComp.SR.tcConstantInterpolatedStringHole (), mHole))
ValueNone
| _ -> ValueNone

//-------------------------------------------------------------------------
// Checking types and type constraints
//-------------------------------------------------------------------------
Expand Down Expand Up @@ -5048,7 +5073,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

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

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

and TcConstantInterpolatedString (cenv: cenv, overallTy: OverallTy, env: TcEnv, m: range, tpenv: UnscopedTyparEnv, parts: SynInterpolatedStringPart list) =
let g = cenv.g

let tryPartTexts, tpenv =
(tpenv, parts) ||> List.mapFold (fun tpenv part ->
match part with
| SynInterpolatedStringPart.String(text, _) -> (fun () -> ValueSome(text.Replace("%%", "%"))), tpenv
| SynInterpolatedStringPart.FillExpr(synFill, formatting) ->
let hole, tpenv = TcExprFlex2 cenv (NewInferenceType g) env false tpenv synFill
(fun () -> tryConstantHoleText g formatting synFill.Range hole), tpenv)

let text =
tryPartTexts
|> Seq.map (fun tryPartText -> tryPartText ())
|> Seq.takeWhile _.IsSome
|> Seq.map _.Value
|> String.concat ""

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

/// Check an interpolated string expression
and [<TailCall>] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs =
match argTys, synFillExprs with
Expand Down Expand Up @@ -7949,6 +7994,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 && g.langVersion.SupportsFeature LanguageFeature.ConstantInterpolatedStrings then
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 @@ -11805,7 +11852,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 @@ -12011,7 +12058,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 @@ -1805,3 +1805,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%%s' for a string."
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 @@ -67,6 +67,7 @@ type LanguageFeature =
| ErrorOnBitwiseOpsOnNonIntegralEnums
| OptimizeClosureIfNotInlined
| ReraiseInComputationExpressions
| ConstantInterpolatedStrings

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -172,6 +173,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 @@ -318,6 +320,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 @@ -58,6 +58,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