diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index b4b5b67fc1e..1dc801689a7 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -260,6 +260,19 @@ let dontInferTypars = ExplicitTyparInfo ([], [], false) let noArgOrRetAttribs = ArgAndRetAttribs ([], []) +[] +type LiteralArgumentType = + /// Literal defined at call site + /// + /// call "literal" + | Inline + + /// F# literal value or IL constant + /// + /// let [] lit = "x" + /// call lit + | StaticField + /// A flag to represent the sort of bindings are we processing. /// Processing "declaration" and "class" bindings that make up a module (such as "let x = 1 let y = 2") /// shares the same code paths (e.g. TcLetBinding and TcLetrecBindings) as processing expression bindings (such as "let x = 1 in ...") @@ -5450,7 +5463,7 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE | SynExpr.Const (SynConst.String (s, _, m), _) -> TcNonControlFlowExpr env <| fun env -> CallExprHasTypeSink cenv.tcSink (m, env.NameEnv, overallTy.Commit, env.AccessRights) - TcConstStringExpr cenv overallTy env m tpenv s + TcConstStringExpr cenv overallTy env m tpenv s LiteralArgumentType.Inline | SynExpr.InterpolatedString (parts, _, m) -> TcNonControlFlowExpr env <| fun env -> @@ -6963,16 +6976,32 @@ and TcObjectExpr (cenv: cenv) env tpenv (objTy, realObjTy, argopt, binds, extraI //------------------------------------------------------------------------- /// Check a constant string expression. It might be a 'printf' format string -and TcConstStringExpr cenv (overallTy: OverallTy) env m tpenv (s: string) = +and TcConstStringExpr cenv (overallTy: OverallTy) env m tpenv (s: string) literalType = + let rec isFormat g ty = + match stripTyEqns g ty with + | TType_app (tcref, _, _) -> tyconRefEq g tcref g.format4_tcr || tyconRefEq g tcref g.format_tcr + | TType_var (typar, _) -> + typar.Constraints + |> List.exists (fun c -> + match c with + | TyparConstraint.CoercesTo (ty, _) -> isFormat g ty + | _ -> false) + | _ -> false let g = cenv.g - if AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy.Commit g.string_ty then - mkString g m s, tpenv + if isFormat g overallTy.Commit then + if literalType = LiteralArgumentType.StaticField then + checkLanguageFeatureAndRecover g.langVersion LanguageFeature.NonInlineLiteralsAsPrintfFormat m + + TcFormatStringExpr cenv overallTy env m tpenv s literalType else - TcFormatStringExpr cenv overallTy env m tpenv s + if literalType = LiteralArgumentType.Inline && not (isObjTy g overallTy.Commit) then + AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css m overallTy.Commit g.string_ty + + mkString g m s, tpenv -and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) = +and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) formatStringLiteralType = let g = cenv.g let aty = NewInferenceType g let bty = NewInferenceType g @@ -6986,7 +7015,11 @@ and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: strin if ok then // Parse the format string to work out the phantom types - let formatStringCheckContext = match cenv.tcSink.CurrentSink with None -> None | Some sink -> sink.FormatStringCheckContext + let formatStringCheckContext = + match cenv.tcSink.CurrentSink, formatStringLiteralType with + | Some sink, LiteralArgumentType.Inline -> sink.FormatStringCheckContext + | _ -> None + let normalizedString = (fmtString.Replace("\r\n", "\n").Replace("\r", "\n")) let _argTys, atyRequired, etyRequired, _percentATys, specifierLocations, _dotnetFormatString = @@ -8765,7 +8798,14 @@ and TcValueItemThen cenv overallTy env vref tpenv mItem afterResolution delayed // Value get | _ -> let _, vExpr, isSpecial, _, _, tpenv = TcVal true cenv env tpenv vref None (Some afterResolution) mItem - let vexpFlex = (if isSpecial then MakeApplicableExprNoFlex cenv vExpr else MakeApplicableExprWithFlex cenv env vExpr) + + let vExpr, tpenv = + match vExpr with + | Expr.Const (Const.String value, _, _) -> TcConstStringExpr cenv overallTy env mItem tpenv value LiteralArgumentType.StaticField + | _ -> vExpr, tpenv + + let vexpFlex = if isSpecial then MakeApplicableExprNoFlex cenv vExpr else MakeApplicableExprWithFlex cenv env vExpr + PropagateThenTcDelayed cenv overallTy env tpenv mItem vexpFlex vexpFlex.Type ExprAtomicFlag.Atomic delayed and TcPropertyItemThen cenv overallTy env nm pinfos tpenv mItem afterResolution staticTyOpt delayed = @@ -8836,10 +8876,12 @@ and TcILFieldItemThen cenv overallTy env finfo tpenv mItem delayed = | _ -> // Get static IL field - let expr = + let (expr, tpenv), isSpecial = match finfo.LiteralValue with + | Some (ILFieldInit.String value) when typeEquiv g exprTy g.string_ty -> + TcConstStringExpr cenv overallTy env mItem tpenv value LiteralArgumentType.StaticField, true | Some lit -> - Expr.Const (TcFieldInit mItem lit, mItem, exprTy) + (Expr.Const (TcFieldInit mItem lit, mItem, exprTy), tpenv), false | None -> let isStruct = finfo.IsValueType let boxity = if isStruct then AsValue else AsObject @@ -8854,9 +8896,16 @@ and TcILFieldItemThen cenv overallTy env finfo tpenv mItem delayed = // Add an I_nop if this is an initonly field to make sure we never recognize it as an lvalue. See mkExprAddrOfExpr. if finfo.IsInitOnly then AI_nop ] - mkAsmExpr (ilInstrs, finfo.TypeInst, [], [exprTy], mItem) + (mkAsmExpr (ilInstrs, finfo.TypeInst, [], [exprTy], mItem), tpenv), false - PropagateThenTcDelayed cenv overallTy env tpenv mItem (MakeApplicableExprWithFlex cenv env expr) exprTy ExprAtomicFlag.Atomic delayed + let exprTy, exprFlex = + if isSpecial then + let exprFlex = MakeApplicableExprNoFlex cenv expr + exprFlex.Type, exprFlex + else + exprTy, MakeApplicableExprWithFlex cenv env expr + + PropagateThenTcDelayed cenv overallTy env tpenv mItem exprFlex exprTy ExprAtomicFlag.Atomic delayed and TcRecdFieldItemThen cenv overallTy env rfinfo tpenv mItem delayed = let g = cenv.g diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 02bc779ab35..d17728dccf8 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1564,6 +1564,7 @@ featureErrorReportingOnStaticClasses,"Error reporting on static classes" featureTryWithInSeqExpressions,"Support for try-with in sequence expressions" featureWarningWhenCopyAndUpdateRecordChangesAllFields,"Raises warnings when an copy-and-update record expression changes all fields of a record." featureStaticMembersInInterfaces,"Static members in interfaces" +featureNonInlineLiteralsAsPrintfFormat,"String values marked as literals and IL constants as printf format" 3353,fsiInvalidDirective,"Invalid directive '#%s %s'" 3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." 3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 095e12ac95a..4c8730e6163 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -64,6 +64,7 @@ type LanguageFeature = | TryWithInSeqExpression | WarningWhenCopyAndUpdateRecordChangesAllFields | StaticMembersInInterfaces + | NonInlineLiteralsAsPrintfFormat /// LanguageVersion management type LanguageVersion(versionText) = @@ -144,6 +145,7 @@ type LanguageVersion(versionText) = LanguageFeature.TryWithInSeqExpression, previewVersion LanguageFeature.WarningWhenCopyAndUpdateRecordChangesAllFields, previewVersion LanguageFeature.StaticMembersInInterfaces, previewVersion + LanguageFeature.NonInlineLiteralsAsPrintfFormat, previewVersion ] @@ -261,6 +263,7 @@ type LanguageVersion(versionText) = | LanguageFeature.TryWithInSeqExpression -> FSComp.SR.featureTryWithInSeqExpressions () | LanguageFeature.WarningWhenCopyAndUpdateRecordChangesAllFields -> FSComp.SR.featureWarningWhenCopyAndUpdateRecordChangesAllFields () | LanguageFeature.StaticMembersInInterfaces -> FSComp.SR.featureStaticMembersInInterfaces () + | LanguageFeature.NonInlineLiteralsAsPrintfFormat -> FSComp.SR.featureNonInlineLiteralsAsPrintfFormat () /// 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 badfce8c012..650a1583cfb 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -54,6 +54,7 @@ type LanguageFeature = | TryWithInSeqExpression | WarningWhenCopyAndUpdateRecordChangesAllFields | StaticMembersInInterfaces + | NonInlineLiteralsAsPrintfFormat /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index 349d988fd42..d45349f954c 100755 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -1056,6 +1056,8 @@ type TcGlobals( member _.format_tcr = v_format_tcr + member _.format4_tcr = v_format4_tcr + member _.expr_tcr = v_expr_tcr member _.raw_expr_tcr = v_raw_expr_tcr diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index cf7f2ad933c..da1d76c755b 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns neproměnné vzory napravo od vzorů typu „jako“ diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 6af5f7b5edf..186ce458734 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns Nicht-Variablenmuster rechts neben as-Mustern diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index fee5bdbc84a..f15aea23087 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns patrones no variables a la derecha de los patrones "as" diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 7b10a5441cf..3643c1aa164 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns modèles non variables à droite de modèles « as » diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 1446a0e9513..0e7df41a2a9 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns modelli non variabili a destra dei modelli 'as' diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index fefe3ec525a..284b52c46d5 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns 'as' パターンの右側の非変数パターン diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index db13251e6e7..dc86b3fea82 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns 'as' 패턴의 오른쪽에 있는 변수가 아닌 패턴 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 73455911504..31ffd49b289 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns stałe wzorce po prawej stronie wzorców typu „as” diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 2070a1bf6ce..95e059af656 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns padrões não-variáveis à direita dos padrões 'as'. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 3762829b9c5..c0103a792b6 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns шаблоны без переменных справа от шаблонов "as" diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 3a1ca285157..122ef5f06c9 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns 'as' desenlerinin sağındaki değişken olmayan desenler diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 731b4d15713..c304296d55d 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns "as" 模式右侧的非变量模式 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 5374ae02eb6..a5a3e73039b 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -307,6 +307,11 @@ nameof + + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format + + non-variable patterns to the right of 'as' patterns 'as' 模式右邊的非變數模式 diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 4b4f1577e8c..f6372bf61cf 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -182,6 +182,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs new file mode 100644 index 00000000000..be034de402a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Compiler.ComponentTests.Language.PrintfFormatTests + +open Xunit +open FSharp.Test.Compiler + +[] +let ``Constant defined in C# can be used as printf format``() = + let csLib = + CSharp """ +public static class Library +{ + public const string Version = "1.0.0"; +}""" |> withName "CsLib" + + FSharp """ +module PrintfFormatTests + +let printLibraryVersion () = printfn Library.Version + """ + |> withLangVersionPreview + |> withReferences [csLib] + |> compile + |> shouldSucceed + +[] +let ``Non-inline literal can be used as printf format, type matches``() = + FSharp """ +module PrintfFormatTests + +[] +let Format = "%d" + +if sprintf Format Format.Length <> "2" then + failwith "failed" + """ + |> withLangVersionPreview + |> asExe + |> compileAndRun + |> shouldSucceed + +[] +let ``Non-inline literal can be used as printf format, type does not match``() = + FSharp """ +module PrintfFormatTests + +[] +let Format = "%s" + +let test = sprintf Format 42 + """ + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withResult { + Error = Error 1 + Range = { StartLine = 7 + StartColumn = 27 + EndLine = 7 + EndColumn = 29 } + Message = "This expression was expected to have type + 'string' +but here has type + 'int' " + } + +[] +let ``Non-inline literals cannot be used as printf format in lang version70``() = + let csLib = + CSharp """ +public static class Library +{ + public const string Version = "1.0.0"; +}""" |> withName "CsLib" + + FSharp """ +module PrintfFormatTests + +[] +let Format = "%s%d%s" + +let bad1 = sprintf Format "yup" Format.Length (string Format.Length) +let ok1 = sprintf "%s" Format +let bad2 = sprintf Library.Version + """ + |> withLangVersion70 + |> withReferences [csLib] + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 3350, Line 7, Col 20, Line 7, Col 26, "Feature 'String values marked as literals and IL constants as printf format' is not available in F# 7.0. Please use language version 'PREVIEW' or greater.") + (Error 3350, Line 9, Col 20, Line 9, Col 35, "Feature 'String values marked as literals and IL constants as printf format' is not available in F# 7.0. Please use language version 'PREVIEW' or greater.") + ] \ No newline at end of file