diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d90fb95fbe7..bc5e4ef6ca2 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, 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 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 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 ceb7a4da0aa..ed4031ac54e 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -4202,6 +4202,31 @@ let EliminateNullnessFromInputType (g: TcGlobals) (inputTy: TType) (pat: Pattern | None -> eliminateNull inputTy pat | _ -> inputTy +[] +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 //------------------------------------------------------------------------- @@ -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 @@ -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 [] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs = match argTys, synFillExprs with @@ -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). @@ -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 @@ -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) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 1fa6ba59402..913644401d2 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -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" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 0a34624112e..9ea5d646315 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -67,6 +67,7 @@ type LanguageFeature = | ErrorOnBitwiseOpsOnNonIntegralEnums | OptimizeClosureIfNotInlined | ReraiseInComputationExpressions + | ConstantInterpolatedStrings /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -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") @@ -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 = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 4afa992cde5..1871a6fcd78 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -58,6 +58,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 8bd83440050..263130ef52e 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 ab6af560121..03802cbd2fa 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 3f7773686fd..cd5157e8e74 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 7a3822ab851..eb464431f99 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 21aa6a838be..ada22ddd3a8 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 c49b5490212..8dc9691025f 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ 属性を型拡張に適用することはできません。 + + 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. + 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. + + 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 11cebee14be..70179997e68 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ 형식 확장에 특성을 적용할 수 없습니다. + + 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. + 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. + + 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 9aa0452706b..c68da8fdae8 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 388d56c9a0c..bb08c3da23e 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,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 string, integer, decimal, character or Boolean, with no alignment and no format specifier other than '%s' for a string. + 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. + + 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 ef935e3d593..39ed2ff6bc9 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ Атрибуты не могут быть применены к расширениям типа. + + 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. + 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. + + 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 1a917da78aa..248aff7735b 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ Öznitelikler tür uzantılarına uygulanamaz. + + 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. + 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. + + 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 f159ad4ee1b..296a848c02f 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ 属性不可应用于类型扩展。 + + 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. + 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. + + 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 abb0041c10d..32206ccbe64 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -332,6 +332,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 @@ -1332,6 +1337,11 @@ 屬性無法套用到類型延伸模組。 + + 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. + 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. + + 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 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..2f63fefb333 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/ConstantInterpolatedStringsTests.fs @@ -0,0 +1,175 @@ +// 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}" + +[] +let Version = 2 + +[] +let Scalars = $"v{Version}{'-'}{true}|{-1L}|{18446744073709551615UL}|{-12.3400m}|%s{Root}" + +let root, version = Root, Version +let scalarsAtRunTime = $"v{version}{'-'}{true}|{-1L}|{18446744073709551615UL}|{-12.3400m}|%s{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 +printfn "%s|%b" Scalars (Scalars = scalarsAtRunTime) +""" + |> 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" + |> withStdOutContains "v2-True|-1|18446744073709551615|-12.3400|api/v2|true" + + [] + 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, integer, decimal, character or Boolean`` (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 ``Only the first hole that breaks a rule is reported`` () = + preview """ +module Program + +[] +let Twice = $"{1.5}{1.5}" +""" + |> typecheck + |> withSingleDiagnostic (Error 3925, Line 5, Col 16, Line 5, Col 19, "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.") + + [] + let ``Before the preview language version interpolated strings in literals are unchanged`` () = + FSharp """ +module Program + +[] +let Root = "api/v2" + +[] +let Unevaluated = false && ($"{1}" = "1") + +[] +let Picked = if true then Root else $"{1}" + +[] +let Items = $"{Root}/items" +""" + |> withLangVersion "11.2" + |> typecheck + |> shouldFail + |> withDiagnostics + [ Error 267, Line 14, Col 13, Line 14, Col 28, "This is not a valid constant expression or custom attribute value" + Error 837, Line 14, Col 13, Line 14, 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 ()