diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d90fb95fbe7..d84ca0e28b0 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -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 `[] let Items = $"{Root}/items"` and `[]`, when each hole is a non-null constant string without alignment or format specifier (`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)) + ### Fixed ### Changed diff --git a/src/Compiler/Checking/CheckBasics.fs b/src/Compiler/Checking/CheckBasics.fs index b4185b07b01..6a750425e14 100644 --- a/src/Compiler/Checking/CheckBasics.fs +++ b/src/Compiler/Checking/CheckBasics.fs @@ -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 diff --git a/src/Compiler/Checking/CheckBasics.fsi b/src/Compiler/Checking/CheckBasics.fsi index 6709b8d92d6..1fb6d8a01ce 100644 --- a/src/Compiler/Checking/CheckBasics.fsi +++ b/src/Compiler/Checking/CheckBasics.fsi @@ -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 diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index df9037a8d47..b3583bac81d 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -6055,6 +6055,7 @@ let emptyTcEnv g = eIsIndexerSetter = false eIsControlFlow = false eInNameOf = false + eInConstantContext = false eInObjectExpr = false eCaughtExceptionVal = ValueNone eCachedImplicitYieldExpressions = HashMultiMap(HashIdentity.Structural, useConcurrentDictionary = true) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index d26dc8d0f63..ae9b1764f1c 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -4247,6 +4247,18 @@ let EliminateNullnessFromInputType (g: TcGlobals) (inputTy: TType) (pat: Pattern | None -> eliminateNull inputTy pat | _ -> inputTy +let tryEvalConstant (g: TcGlobals) (expr: Expr) = + try + suppressErrorReporting (fun () -> ValueSome(EvalLiteralExprOrAttribArg g expr)) + with + | ReportedError _ + | :? DivideByZeroException -> ValueNone + +let tryConstantHoleText (g: TcGlobals) (formatting: SynInterpolationFormatting) (hole: Expr) = + match formatting, tryEvalConstant g hole with + | SynInterpolationFormatting.DotNet(None, None), ValueSome(Expr.Const(Const.String text, _, _)) -> ValueSome text + | _ -> ValueNone + //------------------------------------------------------------------------- // Checking types and type constraints //------------------------------------------------------------------------- @@ -5093,7 +5105,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 @@ -7864,6 +7876,27 @@ 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 partTexts, tpenv = + (tpenv, parts) ||> List.mapFold (fun tpenv part -> + match part with + | SynInterpolatedStringPart.String(text, _) -> Choice1Of2(text.Replace("%%", "%")), tpenv + | SynInterpolatedStringPart.FillExpr(synFill, formatting) -> + let hole, tpenv = TcExprFlex2 cenv (NewInferenceType g) env false tpenv synFill + + match tryConstantHoleText g formatting hole with + | ValueSome text -> Choice1Of2 text, tpenv + | ValueNone -> Choice2Of2(mkCallStringOperator g synFill.Range (tyOfExpr g hole) hole), tpenv) + + let expr = + match partTexts |> List.tryPick (function Choice2Of2 nonConstant -> Some nonConstant | Choice1Of2 _ -> None) with + | Some nonConstant -> nonConstant + | None -> mkString g m (partTexts |> List.map (function Choice1Of2 text -> text | Choice2Of2 _ -> "") |> String.concat "") + + TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> expr, tpenv) + /// Check an interpolated string expression and [] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs = match argTys, synFillExprs with @@ -8010,6 +8043,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: concatenate rendered parts, // type-checking each hole in place (no separate batch, no flat fill-expression list). @@ -11841,7 +11876,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 @@ -12047,7 +12082,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) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 24a36cbbd42..8b46f590beb 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1804,3 +1804,4 @@ featureTypeArgumentDependencyOrdering,"ordering explicit type arguments by their 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." 3925,optInlineMemberCannotUseBase,"An inline member cannot use 'base' or protected members unless those uses can be eliminated before the member itself is inlined." featureReraiseInComputationExpressions,"calls to 'reraise' in a computation expression handler" +featureConstantInterpolatedStrings,"interpolated strings in constant expressions" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 11237ca2454..a1ef86eb8c1 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -65,6 +65,7 @@ type LanguageFeature = | ErrorOnBitwiseOpsOnNonIntegralEnums | OptimizeClosureIfNotInlined | ReraiseInComputationExpressions + | ConstantInterpolatedStrings /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -164,6 +165,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") @@ -308,6 +310,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 = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 6f0a036296e..0aee1247107 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -56,6 +56,7 @@ type LanguageFeature = | ErrorOnBitwiseOpsOnNonIntegralEnums | OptimizeClosureIfNotInlined | ReraiseInComputationExpressions + | ConstantInterpolatedStrings /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index e41845e798a..6a21aaf42f9 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -327,6 +327,11 @@ Vyvolá upozornění, pokud je atribut TailCall použit u nerekurzivních funkcí. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 519f828d71a..917bb6c38c3 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -327,6 +327,11 @@ Löst Warnungen aus, wenn das Attribut "TailCall" für nicht rekursive Funktionen verwendet wird. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index f6782b1159a..0359b527ff8 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -327,6 +327,11 @@ Genera advertencias si el atributo 'TailCall' se usa en funciones no recursivas. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 241354c7630..6816f3de3b4 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -327,6 +327,11 @@ Émet des avertissements si l’attribut « TailCall » est utilisé sur des fonctions non récursives. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index c4eea93f88b..16fac672c74 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -327,6 +327,11 @@ Genera avvisi se l'attributo 'TailCall' viene utilizzato in funzioni non ricorsive. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index c2dd267a254..4a1fa4d0c61 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -327,6 +327,11 @@ 'TailCall' 属性が再帰関数以外で使用されている場合、警告が発せられます。 + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 21b8570f3ff..c50a8412125 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -327,6 +327,11 @@ 'TailCall' 특성이 비 재귀 함수에 사용되는 경우 경고를 발생합니다. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 11b56630b75..48260f5bb46 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -327,6 +327,11 @@ Zgłasza ostrzeżenia, jeśli atrybut „TailCall” jest używany w funkcjach niekursywnych. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index db305b53657..24e062a8f85 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -327,6 +327,11 @@ Gera avisos se o atributo "TailCall" for usado em funções não recursivas. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 152dd4d1390..a15734d67e0 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -327,6 +327,11 @@ Выдает предупреждения, если атрибут TailCall используется в нерекурсивных функциях. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index dd454a2201e..f97d4806364 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -327,6 +327,11 @@ 'TailCall' özniteliği özyinelemeli olmayan işlevlerde kullanılıyorsa uyarılar oluşturur. + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 416d20c080f..32b8e51e9c2 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -327,6 +327,11 @@ 如果在非递归函数上使用“TailCall”属性,则引发警告。 + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 7b324a1b967..6d07d2dca4e 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -327,6 +327,11 @@ 如果 'TailCall' 屬性用於非遞迴函數,則引發警告。 + + interpolated strings in constant expressions + interpolated strings in constant expressions + + Deprecate places where 'seq' can be omitted Deprecate places where 'seq' can be omitted diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 2b473df5e1b..67ea5bfeb2b 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -386,6 +386,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs new file mode 100644 index 00000000000..04687149209 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open System.IO +open Xunit +open FSharp.Test.Compiler + +module ConstantInterpolatedStringsTests = + + let private preview source = FSharp source |> withLangVersionPreview + + [] + let ``Constant interpolated strings take their run-time text`` () = + preview """ +module Program + +[] +let Root = "api/v2" + +[] +let Items = $"{Root}/items" + +[] +let ItemById = $"{Items}/{{id}}" + +[] +let CountFormat = $"{Items}: %%d" + +[] +let Verbatim = $@"\{nameof Root}" + $"{Root}" + +type TagAttribute(tags: string[]) = + inherit System.Attribute() + member _.Tags = tags + member val Name = "" with get, set + +[] +type Tagged() = class end + +let tag = typeof.GetCustomAttributes(typeof, false) |> Array.exactlyOne :?> TagAttribute +let quotationIsFolded = match <@ $"{Root}/items" @> with Quotations.Patterns.Value _ -> true | _ -> false + +printfn "%s|%s|%s|%s|%s|%s|%b" Items ItemById Verbatim (sprintf CountFormat 3) (String.concat "," tag.Tags) tag.Name quotationIsFolded +""" + |> asExe + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains @"api/v2/items|api/v2/items/{id}|\Rootapi/v2|api/v2/items: 3|api/v2/a,api/v2/b|api/v2!|false" + + [] + let ``An Obsolete message can be a constant interpolated string`` () = + preview """ +module Program + +let getItemV2 (id: int) = id + +[] +let getItem id = getItemV2 id + +let item = getItem 1 +""" + |> typecheck + |> withSingleDiagnostic (Warning 44, Line 9, Col 12, Line 9, Col 19, "This construct is deprecated. Use getItemV2 instead") + + [] + let ``A signature literal can be a constant interpolated string`` () = + Fsi "module M\n[]\nval Root: string = \"api\"\n[]\nval Items: string = $\"{Root}/items\"" + |> withAdditionalSourceFile (FsSource "module M\n[]\nlet Root = \"api\"\n[]\nlet Items = $\"{Root}/items\"") + |> withLangVersionPreview + |> asLibrary + |> compile + |> shouldSucceed + + [] + let ``A type provider static argument can be a constant interpolated string`` () = + let providerPath = Path.Combine(__SOURCE_DIRECTORY__, "../../fsharp/typeProviders/staticStringValue/provider.fsx") + + let provider = + Fsx $"""#load @"{providerPath}" """ + |> withName "StaticStringValueProvider" + |> ignoreWarnings + |> compile + |> shouldSucceed + + Fsx $$"""#r @"{{provider.OutputPath.Value}}" + +[] +let Root = "api/v2" + +[] +let Items = $"{Root}/items" + +type FromConst = Provided.Text +type FromLiteral = Provided.Text""" +#if NETCOREAPP + |> withOptions [ "--usesdkrefs-" ] +#endif + |> withLangVersionPreview + |> eval + |> shouldSucceed + + [] + [] + [] + [] + [] + [] + [] + let ``A hole must be a non-null constant string without alignment or format specifier`` (hole: string, errorCode: int) = + preview $""" +module Program + +[] +let Root = "api/v2" +[] +let Version = 2 +[] +let NoValue: string = null +let dir = "data" + +[] +let Rejected = $"{hole}" +""" + |> typecheck + |> shouldFail + |> withErrorCode errorCode + + [] + let ``A hole that divides by zero is rejected at the hole`` () = + preview """ +module Program + +[] +let Text = $"{1 / 0}" +""" + |> typecheck + |> withDiagnostics + [ Error 267, Line 5, Col 15, Line 5, Col 20, "This is not a valid constant expression or custom attribute value" + Error 837, Line 5, Col 12, Line 5, Col 22, "This is not a valid constant expression" ] + + [] + let ``Only the first hole that breaks a rule is reported`` () = + preview """ +module Program + +[] +let Version = 2 + +[] +let Twice = $"{Version}{Version}" +""" + |> typecheck + |> withDiagnostics + [ Error 267, Line 8, Col 16, Line 8, Col 23, "This is not a valid constant expression or custom attribute value" + Error 837, Line 8, Col 13, Line 8, Col 34, "This is not a valid constant expression" ] + + [] + [] + [] + let ``An interpolated string that is not evaluated is not checked as a constant`` (langVersion: string) = + FSharp """ +module Program + +[] +let Root = "api/v2" + +[] +let Unevaluated = false && ($"{1}" = "1") + +[] +let Picked = if true then Root else $"{1}" +""" + |> withLangVersion langVersion + |> typecheck + |> shouldSucceed + + [] + let ``Before the preview language version a constant interpolated string is still rejected`` () = + FSharp """ +module Program + +[] +let Root = "api/v2" + +[] +let Items = $"{Root}/items" +""" + |> withLangVersion "11.2" + |> typecheck + |> shouldFail + |> withDiagnostics + [ Error 267, Line 8, Col 13, Line 8, Col 28, "This is not a valid constant expression or custom attribute value" + Error 837, Line 8, Col 13, Line 8, Col 28, "This is not a valid constant expression" ] diff --git a/tests/fsharp/typeProviders/staticStringValue/provider.fsx b/tests/fsharp/typeProviders/staticStringValue/provider.fsx new file mode 100644 index 00000000000..ca2dc4ef2e0 --- /dev/null +++ b/tests/fsharp/typeProviders/staticStringValue/provider.fsx @@ -0,0 +1,41 @@ +namespace StaticStringValueProvider + +#load "../helloWorld/TypeMagic.fs" + +open System +open Microsoft.FSharp.Core.CompilerServices +open FSharp.TypeMagic + +[] +type Provider() = + let container = TypeContainer.Namespace(typeof.Module, "Provided") + let text = TypeBuilder.CreateType(container, "Text") + let invalidation = Event() + + interface IProvidedNamespace with + member _.NamespaceName = "Provided" + member _.GetTypes() = [| text |] + member _.GetNestedNamespaces() = [||] + member _.ResolveTypeName name = if name = text.Name then text else null + + interface ITypeProvider with + member this.GetNamespaces() = [| this |] + member _.GetStaticParameters _ = [| TypeBuilder.CreateStaticParameter("Value", typeof, 0) |] + + member _.ApplyStaticArguments(_, typePathWithArguments, staticArguments) = + if string staticArguments[0] <> "api/v2/items" then + failwith $"Unexpected static argument '{staticArguments[0]}'" + + TypeBuilder.CreateType(container, typePathWithArguments[typePathWithArguments.Length - 1]) + + member _.GetInvokerExpression(_, _) = failwith "No members are provided." + member _.GetGeneratedAssemblyContents _ = failwith "Only erased types are provided." + + [] + member _.Invalidate = invalidation.Publish + + interface IDisposable with + member _.Dispose() = () + +[] +do ()