diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index 4cd1275d7d6..96698dbae11 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -1,6 +1,7 @@ ### Added * F# Interactive gains a JSON-RPC server mode, `--fsi-server-jsonrpc:`, 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:` 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 `[]` 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 diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d90fb95fbe7..58b62759c21 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: when each hole is a non-null constant string without alignment or format specifiers, as in C#, an interpolated string can define a `[]` value, an attribute argument or a type provider static argument, such as `[]` (`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 diff --git a/src/Compiler/Checking/CheckBasics.fs b/src/Compiler/Checking/CheckBasics.fs index b4185b07b01..2a3ebd0bf81 100644 --- a/src/Compiler/Checking/CheckBasics.fs +++ b/src/Compiler/Checking/CheckBasics.fs @@ -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 diff --git a/src/Compiler/Checking/CheckBasics.fsi b/src/Compiler/Checking/CheckBasics.fsi index 6709b8d92d6..61890cfbfaf 100644 --- a/src/Compiler/Checking/CheckBasics.fsi +++ b/src/Compiler/Checking/CheckBasics.fsi @@ -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 diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 05ce83c5f6b..e96aa0a662b 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -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) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 405e0300c85..f2a3204891b 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -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 // Evaluate the constant expression using static attribute argument rules let te = EvalLiteralExprOrAttribArg g te @@ -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 + | 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 [] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs = match argTys, synFillExprs with @@ -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 + 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). @@ -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 @@ -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) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index b02cde786b7..eaeff5860ec 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -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" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index bbf484350d7..e6096c4fe5a 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -69,6 +69,7 @@ type LanguageFeature = | ErrorOnBitwiseOpsOnNonIntegralEnums | OptimizeClosureIfNotInlined | ReraiseInComputationExpressions + | ConstantInterpolatedStrings /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -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") @@ -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 = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index df4d038bcb4..7f2e05c3478 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -60,6 +60,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 c5a9c4b2676..0a6d89df0c5 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Atributy nejde použít pro rozšíření typů. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Tento výraz záznamu kopírování a aktualizace mění všechna pole typu záznamu '{0}'. Zvažte použití syntaxe konstrukce záznamu. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 867b411b201..c4fd657b5d6 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Attribute können nicht auf Typerweiterungen angewendet werden. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Dieser Ausdruck zum Kopieren und Aktualisieren von Datensätzen ändert alle Felder des Datensatztyps "{0}". Erwägen Sie stattdessen die Verwendung der Datensatzerstellungssyntax. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 27e705b2ae7..79aea211e34 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Los atributos no se pueden aplicar a las extensiones de tipo. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Esta expresión de copia y actualización de registros cambia todos los campos de tipo de registro "{0}". Es preferible utilizar la sintaxis de construcción de registros. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 92a65bfeafe..5bf61275f55 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Impossible d'appliquer des attributs aux extensions de type. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Cette expression d'enregistrement de copie et de mise à jour modifie tous les champs du type d'enregistrement '{0}'. Envisagez d'utiliser la syntaxe de construction d'enregistrement à la place. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 852a48f10dc..d825eb36a73 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Gli attributi non possono essere applicati a estensioni di tipo. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Questa espressione di record di copia e aggiornamento modifica tutti i campi del tipo di record '{0}'. Provare a usare la sintassi di costruzione dei record. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 06a91ab7e68..60a0db1421b 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ 属性を型拡張に適用することはできません。 + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. この copy-and-update レコード式は、レコードの種類が '{0}' であるすべてのフィールドを変更します。代わりにレコード構築構文を使用することを検討してください。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 2e5b5a8e106..5127ba60157 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ 형식 확장에 특성을 적용할 수 없습니다. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 이 레코드 복사 및 업데이트 식은 '{0}' 레코드 형식의 모든 필드를 변경합니다. 레코드 생성 구문을 대신 사용하는 것이 좋습니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index c1487a69e3b..1cd64c18947 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Atrybutów nie można stosować do rozszerzeń typu. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. To wyrażenie rekordu kopiowania i aktualizacji zmienia wszystkie pola typu rekordu „{0}”. Zamiast tego rozważ użycie składni konstrukcji rekordu. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 645ef2ae3a4..107c0bb1ba6 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Os atributos não podem ser aplicados às extensões de tipo. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Essa expressão de registro copiar e atualizar altera todos os campos do tipo de registro '{0}'. Considere usar a sintaxe de construção de registro em vez disso. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 306f9225124..a0b1859bec4 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Атрибуты не могут быть применены к расширениям типа. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Это выражение записи копирования и обновления изменяет все поля типа записи "{0}". Вместо этого можно использовать синтаксис конструкции записи. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index c8b6cd689a1..3933ad1f6c3 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ Öznitelikler tür uzantılarına uygulanamaz. + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Bu kopyalama ve güncelleştirme kayıt ifadesi, '{0}' kayıt türündeki tüm alanları değiştirir. Bunun yerine kayıt oluşturma söz dizimini kullanmayı deneyin. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index ca45f8f398b..6a6644edb47 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ 属性不可应用于类型扩展。 + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 此复制和更新记录表达式更改记录类型“{0}”的所有字段。请考虑改用记录构造语法。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 0b261fc5869..aa981f1c4fb 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -337,6 +337,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 @@ -1337,6 +1342,11 @@ 屬性無法套用到類型延伸模組。 + + 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. + 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. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 此複製和更新記錄運算式將變更記錄類型為 '{0}' 的所有欄位。請考慮改用記錄建構語法。 diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 8cd5ee0e922..9832acd3c87 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..6893652bde8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs @@ -0,0 +1,200 @@ +// 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 + +// RFC FS-1352: interpolated strings in constant expressions. +module ConstantInterpolatedStringsTests = + + let private preview source = FSharp source |> withLangVersionPreview + + [] + let ``Literals and attribute arguments take the runtime text of a constant interpolated string`` () = + 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 quotation = + match <@ $"{Root}/items" @> with + | Quotations.Patterns.Value _ -> "folded" + | _ -> "not folded" + +printfn "%s|%s|%s|%s" Items ItemById Verbatim (sprintf CountFormat 3) +printfn "%s|%s|%s" (String.concat "," tag.Tags) tag.Name quotation +""" + |> asExe + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains @"api/v2/items|api/v2/items/{id}|\Rootapi/v2|api/v2/items: 3" + |> withStdOutContains "api/v2/a,api/v2/b|api/v2!|not folded" + + [] + let ``Extended interpolated strings fold with their own delimiters`` () = + // [] let Json = $$"""{"{{Root}}": "100%"}""" + preview "module Program\n[]\nlet Root = \"api\"\n[]\nlet Json = $$\"\"\"{\"{{Root}}\": \"100%\"}\"\"\"\nprintfn \"%s\" Json" + |> asExe + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "{\"api\": \"100%\"}" + + [] + let ``The compiler reads an attribute argument that is 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/staticStringParameter/provider.fsx") + + let provider = + Fsx $"""#load @"{providerPath}" """ + |> withName "StaticStringParameterProvider" + |> ignoreWarnings + |> compile + |> shouldSucceed + + Fsx( + $"#r @\"{provider.OutputPath.Value}\"\n" + + """ +[] +let Root = "api/v2" + +let fromConst = Provided.Text.Value + +[] +let ItemById = $"{Root}/items/{{id}}" + +let fromLiteral = Provided.Text.Value + +if fromConst <> "api/v2/items" || fromLiteral <> "api/v2/items/{id}" then + failwith $"Unexpected static arguments: '{fromConst}', '{fromLiteral}'" +""" + ) +#if NETCOREAPP + |> withOptions [ "--usesdkrefs-" ] +#endif + |> withLangVersionPreview + |> eval + |> shouldSucceed + + [] + let ``Each hole must be a non-null constant string without alignment or format specifiers`` () = + preview """ +module Program + +[] +let Root = "api/v2" +[] +let Version = 2 +[] +let NoValue: string = null +let dir = "data" + +[] +let A = $"v{Version}" +[] +let B = $"{Root,10}" +[] +let C = $"%s{Root}" +[] +let D = $"{dir}/x" +[] +let E = $"{NoValue}/x" +""" + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3925, Line 13, Col 13, Line 13, Col 20, "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.") + (Error 3925, Line 15, Col 12, Line 15, Col 16, "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.") + (Error 3925, Line 17, Col 14, Line 17, Col 18, "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.") + (Error 267, Line 19, Col 12, Line 19, Col 15, "This is not a valid constant expression or custom attribute value") + (Error 3925, Line 21, Col 12, Line 21, Col 19, "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.") + ] + + [] + let ``Only the first hole that breaks a rule is reported`` () = + preview """ +module Program + +[] +let Version = 2 +let dir = "data" + +[] +let A = $"{Version}{Version}" +[] +let B = $"{dir}{Version}" +[] +let C = $"{Version}{missing}" +""" + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3925, Line 9, Col 12, Line 9, Col 19, "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.") + (Error 267, Line 11, Col 12, Line 11, Col 15, "This is not a valid constant expression or custom attribute value") + (Error 39, Line 13, Col 21, Line 13, Col 28, "The value or constructor 'missing' is not defined.") + (Error 3925, Line 13, Col 12, Line 13, Col 19, "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.") + ] + + [] + let ``Constant interpolated strings need the preview language version`` () = + FSharp """ +module Program + +[] +let Root = "api/v2" + +[] +let Items = $"{Root}/items" +""" + |> withLangVersion "11.2" + |> typecheck + |> shouldFail + |> withSingleDiagnostic (Error 3350, Line 8, Col 13, Line 8, Col 28, "Feature 'interpolated strings in constant expressions' is not available in F# 11.2. Please use language version 'PREVIEW' or greater.") diff --git a/tests/fsharp/typeProviders/staticStringParameter/provider.fsx b/tests/fsharp/typeProviders/staticStringParameter/provider.fsx new file mode 100644 index 00000000000..d251cf8e73b --- /dev/null +++ b/tests/fsharp/typeProviders/staticStringParameter/provider.fsx @@ -0,0 +1,68 @@ +namespace StaticStringParameterProvider + +#load "../helloWorld/TypeMagic.fs" + +open System +open System.Collections.Generic +open System.Reflection +open Microsoft.FSharp.Core.CompilerServices +open Microsoft.FSharp.Quotations +open FSharp.TypeMagic + +/// Provides 'Provided.Text', whose static property 'Value' returns the static argument. +[] +type Provider() = + let modul = typeof.Module + let container = TypeContainer.Namespace(modul, "Provided") + let unappliedType = TypeBuilder.CreateType(container, "Text") + let appliedTypes = Dictionary(StringComparer.Ordinal) + + let invalidation = Event() + + interface IProvidedNamespace with + member _.NamespaceName = "Provided" + member _.GetTypes() = [| unappliedType |] + member _.GetNestedNamespaces() = [||] + member _.ResolveTypeName name = if name = unappliedType.Name then unappliedType else null + + interface ITypeProvider with + member this.GetNamespaces() = [| this |] + + member _.GetStaticParameters typeWithoutArguments = + if typeWithoutArguments.Name = unappliedType.Name then + [| TypeBuilder.CreateStaticParameter("Value", typeof, 0) |] + else + [||] + + member _.ApplyStaticArguments(_, typePathWithArguments, staticArguments) = + let name = typePathWithArguments[typePathWithArguments.Length - 1] + + lock appliedTypes (fun () -> + match appliedTypes.TryGetValue name with + | true, struct (appliedType, _) -> appliedType + | _ -> + let appliedType = + TypeBuilder.CreateType( + container, + name, + members = TypeBuilder.CacheMembers(fun declaringType -> + TypeBuilder.JoinPropertiesIntoMemberInfos + [ TypeBuilder.CreateSyntheticProperty(declaringType, "Value", typeof, isStatic = true) ])) + + appliedTypes[name] <- struct (appliedType, staticArguments[0] :?> string) + appliedType) + + member _.GetInvokerExpression(getter, _) = + let struct (_, value) = lock appliedTypes (fun () -> appliedTypes[getter.DeclaringType.Name]) + Expr.Value value + + member _.GetGeneratedAssemblyContents _ = failwith "Only erased types are provided." + + [] + member _.Invalidate = invalidation.Publish + + interface IDisposable with + member _.Dispose() = () + +[] +do ()