diff --git a/.fantomasignore b/.fantomasignore index 1b373a01b2e..fff0a1b9cbc 100644 --- a/.fantomasignore +++ b/.fantomasignore @@ -75,6 +75,7 @@ src/Compiler/SyntaxTree/LexFilter.fs src/FSharp.Core/array2.fs src/FSharp.Core/array3.fs +src/FSharp.Core/fslib-extra-pervasives.fs src/FSharp.Core/Linq.fs src/FSharp.Core/local.fs src/FSharp.Core/nativeptr.fs diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 3349ac75260..62f5d434866 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -4,3 +4,6 @@ * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) +### Added + +* Added generic `print` and `printn` functions (`'T -> unit`) to `ExtraTopLevelOperators` for simple value printing to stdout. ([RFC FS-1125](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1125-print-printn-functions.md), [PR #19265](https://github.com/dotnet/fsharp/pull/19265)) diff --git a/src/FSharp.Core/fslib-extra-pervasives.fs b/src/FSharp.Core/fslib-extra-pervasives.fs index 924f057b7e1..4dd4250843d 100644 --- a/src/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/FSharp.Core/fslib-extra-pervasives.fs @@ -277,6 +277,23 @@ module ExtraTopLevelOperators = let eprintfn format = Printf.eprintfn format + [] + let inline print (value: 'T) = + Console.Out.Write(string value) + // Culture-independent types can bypass 'string' and use TextWriter overloads directly. + // Numeric types must go through 'string' to ensure InvariantCulture formatting, + // since TextWriter.Write(int/float/...) uses the writer's FormatProvider (CurrentCulture). + when 'T : string = Console.Out.Write((# "" value : string #)) + when 'T : char = Console.Out.Write((# "" value : char #)) + when 'T : bool = Console.Out.Write((# "" value : bool #)) + + [] + let inline printn (value: 'T) = + Console.Out.WriteLine(string value) + when 'T : string = Console.Out.WriteLine((# "" value : string #)) + when 'T : char = Console.Out.WriteLine((# "" value : char #)) + when 'T : bool = Console.Out.WriteLine((# "" value : bool #)) + [] let async = AsyncBuilder() diff --git a/src/FSharp.Core/fslib-extra-pervasives.fsi b/src/FSharp.Core/fslib-extra-pervasives.fsi index dff6b2e68d1..878b62547c5 100644 --- a/src/FSharp.Core/fslib-extra-pervasives.fsi +++ b/src/FSharp.Core/fslib-extra-pervasives.fsi @@ -56,6 +56,33 @@ module ExtraTopLevelOperators = [] val eprintfn: format: Printf.TextWriterFormat<'T> -> 'T + /// Converts the value to a string using the string operator and writes it to the standard output. + /// + /// The value to print. + /// + /// + /// + /// print "Hello, " + /// print "World!" + /// // output: Hello, World! + /// + /// + [] + val inline print: value: 'T -> unit + + /// Converts the value to a string using the string operator and writes it to the standard output, followed by a newline. + /// + /// The value to print. + /// + /// + /// + /// printn "Hello, World!" + /// // output: Hello, World! + /// + /// + [] + val inline printn: value: 'T -> unit + /// Print to a string using the given format. /// /// The formatter. diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/PrintFunction.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/PrintFunction.fs new file mode 100644 index 00000000000..16db7328cac --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/PrintFunction.fs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace EmittedIL + +open Xunit +open FSharp.Test.Compiler + +module PrintFunction = + + [] + let ``print with int specializes to Int32 ToString with InvariantCulture``() = + FSharp """ +module PrintInt + +let printInt () = print 42 + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()""" + """call instance string [netstandard]System.Int32::ToString(string,""" + """callvirt instance void [netstandard]System.IO.TextWriter::Write(string)"""] + + [] + let ``printn with int specializes to Int32 ToString with InvariantCulture``() = + FSharp """ +module PrintnInt + +let printnInt () = printn 42 + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()""" + """call instance string [netstandard]System.Int32::ToString(string,""" + """callvirt instance void [netstandard]System.IO.TextWriter::WriteLine(string)"""] + + [] + let ``print with string calls Write directly``() = + FSharp """ +module PrintString + +let printStr () = print "hello" + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """callvirt instance void [netstandard]System.IO.TextWriter::Write(string)"""] + + [] + let ``print with char calls Write char overload``() = + FSharp """ +module PrintChar + +let printChar () = print 'A' + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """callvirt instance void [netstandard]System.IO.TextWriter::Write(char)"""] + + [] + let ``print with bool calls Write bool overload``() = + FSharp """ +module PrintBool + +let printBool () = print true + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """callvirt instance void [netstandard]System.IO.TextWriter::Write(bool)"""] + + [] + let ``printn with float specializes to Double ToString with InvariantCulture``() = + FSharp """ +module PrintnFloat + +let printnFloat () = printn 3.14 + """ + |> withOptimize + |> compile + |> shouldSucceed + |> verifyIL [ + """call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()""" + """call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()""" + """call instance string [netstandard]System.Double::ToString(string,""" + """callvirt instance void [netstandard]System.IO.TextWriter::WriteLine(string)"""] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 3fc031a525f..97f41602ebc 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -235,6 +235,7 @@ + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl index 5b6cc0bce4e..419265a0d97 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -1077,6 +1077,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr) Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl index 217d4b7c837..6985634e36b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -1077,6 +1077,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr) Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl index 43defdb622e..e54941e680a 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -1080,6 +1080,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr) Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl index ed913ea04d3..d2ba4b44e02 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -1080,6 +1080,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T) +Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T]) Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr) Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj index d4ff59d3cbd..829ab588f7a 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj @@ -72,6 +72,7 @@ + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/PrintTests.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/PrintTests.fs new file mode 100644 index 00000000000..4c5526a9524 --- /dev/null +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/PrintTests.fs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +// Various tests for: +// Microsoft.FSharp.Core.ExtraTopLevelOperators.print +// Microsoft.FSharp.Core.ExtraTopLevelOperators.printn + +namespace FSharp.Core.UnitTests + +open System +open System.IO +open Xunit + +[] +type PrintTests() = + + let captureConsoleOut (f: unit -> unit) = + let oldOut = Console.Out + use sw = new StringWriter() + Console.SetOut(sw) + try + f () + sw.ToString() + finally + Console.SetOut(oldOut) + + [] + member _.``print writes string value``() = + let result = captureConsoleOut (fun () -> print "hello") + Assert.Equal("hello", result) + + [] + member _.``print writes integer value``() = + let result = captureConsoleOut (fun () -> print 42) + Assert.Equal("42", result) + + [] + member _.``print writes float with InvariantCulture``() = + let result = captureConsoleOut (fun () -> print 3.14) + Assert.Equal("3.14", result) + + [] + member _.``print writes bool value``() = + let result = captureConsoleOut (fun () -> print true) + Assert.Equal("True", result) + + [] + member _.``print writes Some value``() = + let result = captureConsoleOut (fun () -> print (Some 42)) + Assert.Equal("Some(42)", result) + + [] + member _.``print writes None value``() = + let result = captureConsoleOut (fun () -> print None) + Assert.Equal("", result) + + [] + member _.``print writes list value``() = + let result = captureConsoleOut (fun () -> print [1; 2; 3]) + Assert.Equal("[1; 2; 3]", result) + + [] + member _.``printn writes value followed by newline``() = + let result = captureConsoleOut (fun () -> printn "hello") + Assert.Equal("hello" + Environment.NewLine, result) + + [] + member _.``multiple prints concatenate``() = + let result = captureConsoleOut (fun () -> + print "Hello, " + print "World!") + Assert.Equal("Hello, World!", result) + + [] + member _.``printn writes integer with newline``() = + let result = captureConsoleOut (fun () -> printn 42) + Assert.Equal("42" + Environment.NewLine, result)