From 84151ec2a15b5088bca49cbda74ff9594dfe7653 Mon Sep 17 00:00:00 2001 From: kerams Date: Fri, 27 Jan 2023 22:23:14 +0100 Subject: [PATCH 1/3] Allow non-inline literals to be used as printf format --- src/Compiler/Checking/CheckExpressions.fs | 59 +++++++++++++---- src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/TypedTree/TcGlobals.fs | 2 + src/Compiler/xlf/FSComp.txt.cs.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.de.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.es.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.fr.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.it.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.ja.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.ko.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.pl.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.ru.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.tr.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 ++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 ++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Language/PrintfFormatTests.fs | 64 +++++++++++++++++++ 20 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 4c1c47f53b0..201b825ac84 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -5449,7 +5449,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 true | SynExpr.InterpolatedString (parts, _, m) -> TcNonControlFlowExpr env <| fun env -> @@ -6956,16 +6956,27 @@ 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) = - - let g = cenv.g +and TcConstStringExpr cenv (overallTy: OverallTy) env m tpenv (s: string) isInlineLiteral = + let rec isFormat g typ = + match stripTyEqns g typ 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 (typ, _) -> isFormat g typ + | _ -> false) + | _ -> false - if AddCxTypeEqualsTypeUndoIfFailed env.DisplayEnv cenv.css m overallTy.Commit g.string_ty then - mkString g m s, tpenv + if isFormat cenv.g overallTy.Commit then + TcFormatStringExpr cenv overallTy env m tpenv s isInlineLiteral else - TcFormatStringExpr cenv overallTy env m tpenv s + if isInlineLiteral && not (isObjTy cenv.g overallTy.Commit) then + AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css m overallTy.Commit cenv.g.string_ty -and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) = + mkString cenv.g m s, tpenv + +and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) useFormatStringCheckContext = let g = cenv.g let aty = NewInferenceType g let bty = NewInferenceType g @@ -6979,7 +6990,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 with + | Some sink when useFormatStringCheckContext -> sink.FormatStringCheckContext + | _ -> None + let normalizedString = (fmtString.Replace("\r\n", "\n").Replace("\r", "\n")) let _argTys, atyRequired, etyRequired, _percentATys, specifierLocations, _dotnetFormatString = @@ -8758,7 +8773,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, _, _) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat -> TcConstStringExpr cenv overallTy env mItem tpenv value false + | _ -> 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 = @@ -8829,10 +8851,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 g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat && typeEquiv g exprTy g.string_ty -> + TcConstStringExpr cenv overallTy env mItem tpenv value false, 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 @@ -8847,9 +8871,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 46379fc0eb4..719fae09bdb 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1561,6 +1561,7 @@ featureErrorForNonVirtualMembersOverrides,"Raises errors for non-virtual members featureWarningWhenInliningMethodImplNoInlineMarkedFunction,"Raises warnings when 'let inline ... =' is used together with [] attribute. Function is not getting inlined." featureArithmeticInLiterals,"Allow arithmetic and logical operations in literals" featureErrorReportingOnStaticClasses,"Error reporting on static classes" +featureNonInlineLiteralsAsPrintfFormat,"Allow IL constants and string literals that are not defined inline to be used 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 e7654bea422..170b5e35856 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -61,6 +61,7 @@ type LanguageFeature = | EscapeDotnetFormattableStrings | ArithmeticInLiterals | ErrorReportingOnStaticClasses + | NonInlineLiteralsAsPrintfFormat /// LanguageVersion management type LanguageVersion(versionText) = @@ -138,6 +139,7 @@ type LanguageVersion(versionText) = LanguageFeature.EscapeDotnetFormattableStrings, previewVersion LanguageFeature.ArithmeticInLiterals, previewVersion LanguageFeature.ErrorReportingOnStaticClasses, previewVersion + LanguageFeature.NonInlineLiteralsAsPrintfFormat, previewVersion ] @@ -252,6 +254,7 @@ type LanguageVersion(versionText) = | LanguageFeature.EscapeDotnetFormattableStrings -> FSComp.SR.featureEscapeBracesInFormattableString () | LanguageFeature.ArithmeticInLiterals -> FSComp.SR.featureArithmeticInLiterals () | LanguageFeature.ErrorReportingOnStaticClasses -> FSComp.SR.featureErrorReportingOnStaticClasses () + | 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 d070a8b4e36..98754225356 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -51,6 +51,7 @@ type LanguageFeature = | EscapeDotnetFormattableStrings | ArithmeticInLiterals | ErrorReportingOnStaticClasses + | NonInlineLiteralsAsPrintfFormat /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index dd2f6e70f9e..7fbbddf05e8 100755 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -1055,6 +1055,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 be4e61a8fe3..8f6fee40a0f 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 5d662b8bf27..a93254c950f 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 1ddc22b8475..0b2968d9a01 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 a7b82e51172..e468b1e2aba 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 fcd590c3b39..0cf335a23e1 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 b064cc8d2b7..59b4ae2784e 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 41fced8e047..ceb89a689fa 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 764d818dcd4..433241f23ad 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 da8982c32ac..0a0c5565315 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 29827955000..bb16dcef6e1 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 3f3bb5dee68..2932c4d4554 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 3601169ca3a..44fe29c6b57 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 b767cad8784..bbcbd23b541 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -302,6 +302,11 @@ nameof + + Allow IL constants and string literals that are not defined inline to be used as printf format + Allow IL constants and string literals that are not defined inline to be used 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 c244723eb72..ed88251d2ee 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -179,6 +179,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..b88624380be --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs @@ -0,0 +1,64 @@ +// 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" + +let test = sprintf Format Format.Length + """ + |> withLangVersionPreview + |> compile + |> 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 + |> compile + |> 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' " + } \ No newline at end of file From 4fab27c093d614f80cb7e09040a7c0dc9e608fa1 Mon Sep 17 00:00:00 2001 From: kerams Date: Wed, 1 Feb 2023 20:02:54 +0100 Subject: [PATCH 2/3] Address comments --- src/Compiler/Checking/CheckExpressions.fs | 31 +++++++++++++------ .../Language/PrintfFormatTests.fs | 4 +-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 201b825ac84..fca9fbd2b75 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 ...") @@ -5449,7 +5462,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 true + TcConstStringExpr cenv overallTy env m tpenv s LiteralArgumentType.Inline | SynExpr.InterpolatedString (parts, _, m) -> TcNonControlFlowExpr env <| fun env -> @@ -6956,7 +6969,7 @@ 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) isInlineLiteral = +and TcConstStringExpr cenv (overallTy: OverallTy) env m tpenv (s: string) literalType = let rec isFormat g typ = match stripTyEqns g typ with | TType_app (tcref, _, _) -> tyconRefEq g tcref g.format4_tcr || tyconRefEq g tcref g.format_tcr @@ -6969,14 +6982,14 @@ and TcConstStringExpr cenv (overallTy: OverallTy) env m tpenv (s: string) isInli | _ -> false if isFormat cenv.g overallTy.Commit then - TcFormatStringExpr cenv overallTy env m tpenv s isInlineLiteral + TcFormatStringExpr cenv overallTy env m tpenv s literalType else - if isInlineLiteral && not (isObjTy cenv.g overallTy.Commit) then + if literalType = LiteralArgumentType.Inline && not (isObjTy cenv.g overallTy.Commit) then AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css m overallTy.Commit cenv.g.string_ty mkString cenv.g m s, tpenv -and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) useFormatStringCheckContext = +and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) formatStringLiteralType = let g = cenv.g let aty = NewInferenceType g let bty = NewInferenceType g @@ -6991,8 +7004,8 @@ 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 - | Some sink when useFormatStringCheckContext -> sink.FormatStringCheckContext + match cenv.tcSink.CurrentSink, formatStringLiteralType with + | Some sink, LiteralArgumentType.Inline -> sink.FormatStringCheckContext | _ -> None let normalizedString = (fmtString.Replace("\r\n", "\n").Replace("\r", "\n")) @@ -8776,7 +8789,7 @@ and TcValueItemThen cenv overallTy env vref tpenv mItem afterResolution delayed let vExpr, tpenv = match vExpr with - | Expr.Const (Const.String value, _, _) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat -> TcConstStringExpr cenv overallTy env mItem tpenv value false + | Expr.Const (Const.String value, _, _) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat -> TcConstStringExpr cenv overallTy env mItem tpenv value LiteralArgumentType.StaticField | _ -> vExpr, tpenv let vexpFlex = if isSpecial then MakeApplicableExprNoFlex cenv vExpr else MakeApplicableExprWithFlex cenv env vExpr @@ -8854,7 +8867,7 @@ and TcILFieldItemThen cenv overallTy env finfo tpenv mItem delayed = let (expr, tpenv), isSpecial = match finfo.LiteralValue with | Some (ILFieldInit.String value) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat && typeEquiv g exprTy g.string_ty -> - TcConstStringExpr cenv overallTy env mItem tpenv value false, true + TcConstStringExpr cenv overallTy env mItem tpenv value LiteralArgumentType.StaticField, true | Some lit -> (Expr.Const (TcFieldInit mItem lit, mItem, exprTy), tpenv), false | None -> diff --git a/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs index b88624380be..7ace4581def 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs @@ -35,7 +35,7 @@ let Format = "%d" let test = sprintf Format Format.Length """ |> withLangVersionPreview - |> compile + |> typecheck |> shouldSucceed [] @@ -49,7 +49,7 @@ let Format = "%s" let test = sprintf Format 42 """ |> withLangVersionPreview - |> compile + |> typecheck |> shouldFail |> withResult { Error = Error 1 From c919df2dae4c12c4e5af934b953d063ae0726c82 Mon Sep 17 00:00:00 2001 From: kerams Date: Sun, 12 Feb 2023 09:39:58 +0100 Subject: [PATCH 3/3] Improve diagnostics --- src/Compiler/Checking/CheckExpressions.fs | 23 +++++++----- src/Compiler/FSComp.txt | 2 +- src/Compiler/xlf/FSComp.txt.cs.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.de.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.es.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.fr.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.it.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.ja.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.ko.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.pl.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.ru.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.tr.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 4 +-- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 4 +-- .../Language/PrintfFormatTests.fs | 36 +++++++++++++++++-- 16 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 53764a936e0..1dc801689a7 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -6977,24 +6977,29 @@ 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) literalType = - let rec isFormat g typ = - match stripTyEqns g typ with + 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 (typ, _) -> isFormat g typ + | TyparConstraint.CoercesTo (ty, _) -> isFormat g ty | _ -> false) | _ -> false - if isFormat cenv.g overallTy.Commit then + let g = cenv.g + + 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 - if literalType = LiteralArgumentType.Inline && not (isObjTy cenv.g overallTy.Commit) then - AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css m overallTy.Commit cenv.g.string_ty + if literalType = LiteralArgumentType.Inline && not (isObjTy g overallTy.Commit) then + AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css m overallTy.Commit g.string_ty - mkString cenv.g m s, tpenv + mkString g m s, tpenv and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: string) formatStringLiteralType = let g = cenv.g @@ -8796,7 +8801,7 @@ and TcValueItemThen cenv overallTy env vref tpenv mItem afterResolution delayed let vExpr, tpenv = match vExpr with - | Expr.Const (Const.String value, _, _) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat -> TcConstStringExpr cenv overallTy env mItem tpenv value LiteralArgumentType.StaticField + | 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 @@ -8873,7 +8878,7 @@ and TcILFieldItemThen cenv overallTy env finfo tpenv mItem delayed = // Get static IL field let (expr, tpenv), isSpecial = match finfo.LiteralValue with - | Some (ILFieldInit.String value) when g.langVersion.SupportsFeature LanguageFeature.NonInlineLiteralsAsPrintfFormat && typeEquiv g exprTy g.string_ty -> + | 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), tpenv), false diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index c5d56bef76a..d17728dccf8 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1564,7 +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,"Allow IL constants and string literals that are not defined inline to be used as printf format" +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/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index c02833c4b2b..da1d76c755b 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 528fd05ca21..186ce458734 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 4ad0260d108..f15aea23087 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 5dc5bca7981..3643c1aa164 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 40e591fd62c..0e7df41a2a9 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index b283f3561d5..284b52c46d5 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index ece1ed289af..dc86b3fea82 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index ab059988f4b..31ffd49b289 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 97ed05ee351..95e059af656 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 6871cc90199..c0103a792b6 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 80d863e07f6..122ef5f06c9 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 0f9b4ad36b1..c304296d55d 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index af041fe4b80..a5a3e73039b 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -308,8 +308,8 @@ - Allow IL constants and string literals that are not defined inline to be used as printf format - Allow IL constants and string literals that are not defined inline to be used as printf format + String values marked as literals and IL constants as printf format + String values marked as literals and IL constants as printf format diff --git a/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs index 7ace4581def..be034de402a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/PrintfFormatTests.fs @@ -32,10 +32,12 @@ module PrintfFormatTests [] let Format = "%d" -let test = sprintf Format Format.Length +if sprintf Format Format.Length <> "2" then + failwith "failed" """ |> withLangVersionPreview - |> typecheck + |> asExe + |> compileAndRun |> shouldSucceed [] @@ -61,4 +63,32 @@ let test = sprintf Format 42 'string' but here has type 'int' " - } \ No newline at end of file + } + +[] +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