diff --git a/DEVGUIDE.md b/DEVGUIDE.md index e605c711dba..e5480e39ae0 100644 --- a/DEVGUIDE.md +++ b/DEVGUIDE.md @@ -275,9 +275,25 @@ Where `` corresponds to the latest Visual Studio version on your machin Use the `Debug` configuration to test your changes locally. It is the default. Do not use the `Release` configuration! Local development and testing of Visual Studio tooling is not designed for the `Release` configuration. -### Writing and running benchmarks +### Benchmarking -Existing compiler benchmarks can be found in `tests\benchmarks\`. +Existing compiler benchmarks can be found in `tests\benchmarks\`. The folder contains READMEs describing specific benchmark projects as well as guidelines for creating new benchmarks. There is also `FSharp.Benchmarks.sln` solution containing all the benchmark project and their dependencies. + +To exercise the benchmarking infrastructure locally, run: + +(Windows) +```cmd +build.cmd -configuration Release -testBenchmarks +``` + +(Linux/Mac) +```shell +./build.sh --configuration Release --testBenchmarks +``` + +This is executed in CI as well. It does the following: +- builds all the benchmarking projects +- does smoke testing for fast benchmarks (executes them once to check they don't fail in the runtime) ### Benchmarking and profiling the compiler @@ -286,151 +302,6 @@ Existing compiler benchmarks can be found in `tests\benchmarks\`. * Always build both versions of compiler/FCS from source and not use pre-built binaries from SDK (SDK binaries are crossgen'd, which can affect performance). * To run `Release` build of compiler/FCS. -### Example benchmark setup using [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) - -1. Perform a clean build of the compiler and FCS from source (as described in this document, build can be done with `-noVisualStudio` in case if FCS/FSharp.Core is being benchmarked/profiled). - -2. Create a benchmark project (in this example, the project will be created in `tests\benchmarks\FCSBenchmarks`). - - ```shell - cd tests\benchmarks\FCSBenchmarks - dotnet new console -o FcsBench --name FcsBench -lang F# - ``` - -3. Add needed packages and project references. - - ```shell - cd FcsBench - dotnet add package BenchmarkDotNet - dotnet add reference ..\..\..\src\Compiler\FSharp.Compiler.Service.fsproj - ``` - -4. Additionally, if you want to test changes to the FSharp.Core (note that the relative path can be different) - - ```shell - dotnet add reference ..\..\..\src\FSharp.Core\FSharp.Core.fsproj - ``` - - > as well as the following property have to be added to `FcsBench.fsproj`: - - ```xml - - true - - ``` - -5. Add a new benchmark for FCS/FSharp.Core by editing `Program.fs`. - - ```fsharp - open System.IO - open FSharp.Compiler.CodeAnalysis - open FSharp.Compiler.Diagnostics - open FSharp.Compiler.Text - open BenchmarkDotNet.Attributes - open BenchmarkDotNet.Running - - [] - type CompilerService() = - let mutable checkerOpt = None - let mutable sourceOpt = None - - let parsingOptions = - { - SourceFiles = [|"CheckExpressions.fs"|] - ConditionalDefines = [] - DiagnosticOptions = FSharpDiagnosticOptions.Default - LangVersionText = "default" - IsInteractive = false - LightSyntax = None - CompilingFsLib = false - IsExe = false - } - - [] - member _.Setup() = - match checkerOpt with - | None -> - checkerOpt <- Some(FSharpChecker.Create(projectCacheSize = 200)) - | _ -> () - - match sourceOpt with - | None -> - sourceOpt <- Some <| SourceText.ofString(File.ReadAllText("""C:\Users\vlza\code\fsharp\src\Compiler\Checking\CheckExpressions.fs""")) - | _ -> () - - - [] - member _.ParsingTypeCheckerFs() = - match checkerOpt, sourceOpt with - | None, _ -> failwith "no checker" - | _, None -> failwith "no source" - | Some(checker), Some(source) -> - let results = checker.ParseFile("CheckExpressions.fs", source, parsingOptions) |> Async.RunSynchronously - if results.ParseHadErrors then failwithf "parse had errors: %A" results.Diagnostics - - [] - member _.ParsingTypeCheckerFsSetup() = - match checkerOpt with - | None -> failwith "no checker" - | Some(checker) -> - checker.InvalidateAll() - checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() - checker.ParseFile("dummy.fs", SourceText.ofString "dummy", parsingOptions) |> Async.RunSynchronously |> ignore - - [] - let main _ = - BenchmarkRunner.Run() |> ignore - 0 - ``` - - > For more detailed information about available BenchmarkDotNet options, please refer to [BenchmarkDotNet Documentation](https://benchmarkdotnet.org/articles/overview.html). - -6. Build and run the benchmark. - - ```shell - dotnet build -c Release - dotnet run -c Release - ``` - -7. You can find results in `.\BenchmarkDotNet.Artifacts\results\` in the current benchmark project directory. - - ```shell - > ls .\BenchmarkDotNet.Artifacts\results\ - - Directory: C:\Users\vlza\code\fsharp\tests\benchmarks\FCSBenchmarks\FcsBench\BenchmarkDotNet.Artifacts\results - - Mode LastWriteTime Length Name - ---- ------------- ------ ---- - -a--- 4/25/2022 1:42 PM 638 Program.CompilerService-report-github.md - -a--- 4/25/2022 1:42 PM 1050 Program.CompilerService-report.csv - -a--- 4/25/2022 1:42 PM 1169 Program.CompilerService-report.html - ``` - - > *-report-github.md can be used to post benchmark results to GitHub issue/PR/discussion or RFC. - > - >*-report.csv can be used for comparison purposes. - - **Example output:** - - ``` ini - - BenchmarkDotNet=v0.13.1, OS=Windows 10.0.25102 - Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores - .NET SDK=6.0.200 - [Host] : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT DEBUG - Job-GDIBXX : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT - - InvocationCount=1 UnrollFactor=1 - - ``` - - | Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Allocated | - |--------------------- |---------:|--------:|--------:|---------:|----------:|----------:|----------:| - | ParsingTypeCheckerFs | 199.4 ms | 3.84 ms | 9.78 ms | 195.5 ms | 4000.0000 | 1000.0000 | 28 MB | - -8. Repeat for any number of changes you would like to test. -9. **Optionally:** benchmark code and results can be included as part of the PR for future reference. - ## Additional resources The primary technical guide to the core compiler code is [The F# Compiler Technical Guide](https://github.com/dotnet/fsharp/blob/main/docs/index.md). Please read and contribute to that guide. diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md index 85735d5b430..b8dc1de0de9 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md @@ -2,7 +2,7 @@ * Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514)) * `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550)) -* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588)) +* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588), [PR #16643](https://github.com/dotnet/fsharp/pull/16643)) * Keep parens for problematic exprs (`if`, `match`, etc.) in `$"{(…):N0}"`, `$"{(…),-3}"`, etc. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578)) * Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode [#PR 16471](https://github.com/dotnet/fsharp/pull/16471)) @@ -13,6 +13,7 @@ * Parser recovers on complex primary constructor patterns, better tree representation for primary constructor patterns. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425)) * Name resolution: keep type vars in subsequent checks ([PR #16456](https://github.com/dotnet/fsharp/pull/16456)) * Higher-order-function-based API for working with the untyped abstract syntax tree. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462)) +* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473)) ### Changed diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index 0fce580b51e..832dd924a44 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -3,6 +3,7 @@ * Better generic unmanaged structs handling. ([Language suggestion #692](https://github.com/fsharp/fslang-suggestions/issues/692), [PR #12154](https://github.com/dotnet/fsharp/pull/12154)) * Bidirectional F#/C# interop for 'unmanaged' constraint. ([PR #12154](https://github.com/dotnet/fsharp/pull/12154)) * Make `.Is*` discriminated union properties visible. ([Language suggestion #222](https://github.com/fsharp/fslang-suggestions/issues/222), [PR #16341](https://github.com/dotnet/fsharp/pull/16341)) +* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473)) ### Fixed diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4be35244cae..503c4903f09 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,11 +1,13 @@ + https://github.com/dotnet/source-build-reference-packages 412264fd6c04712d1d31ff05d37c6919101ef4f4 + https://github.com/dotnet/msbuild 2cbc8b6aef648cf21c6a68a0dab7fe09a614e475 @@ -37,6 +39,11 @@ https://github.com/dotnet/xliff-tasks 73f0850939d96131c28cf6ea6ee5aacb4da0083a + + + + https://github.com/dotnet/xliff-tasks + 73f0850939d96131c28cf6ea6ee5aacb4da0083a diff --git a/eng/build-utils.ps1 b/eng/build-utils.ps1 index d5375c29732..a0c5299cb95 100644 --- a/eng/build-utils.ps1 +++ b/eng/build-utils.ps1 @@ -179,7 +179,7 @@ function Get-PackageDir([string]$name, [string]$version = "") { } function Run-MSBuild([string]$projectFilePath, [string]$buildArgs = "", [string]$logFileName = "", [switch]$parallel = $true, [switch]$summary = $true, [switch]$warnAsError = $true, [string]$configuration = $script:configuration, [string]$verbosity = $script:verbosity) { - # Because we override the C#/VB toolset to build against our LKG package, it is important + # Because we override the C#/VB toolset to build against our LKG (Last Known Good) package, it is important # that we do not reuse MSBuild nodes from other jobs/builds on the machine. Otherwise, # we'll run into issues such as https://github.com/dotnet/roslyn/issues/6211. # MSBuildAdditionalCommandLineArgs= diff --git a/eng/build.sh b/eng/build.sh index 3b992d6bfab..b8915397d25 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -25,6 +25,7 @@ usage() echo "Test actions:" echo " --testcoreclr Run unit tests on .NET Core (short: --test, -t)" echo " --testCompilerComponentTests Run FSharp.Compiler.ComponentTests on .NET Core" + echo " --testBenchmarks Build and Run Benchmark suite" echo "" echo "Advanced settings:" echo " --ci Building in CI" @@ -56,6 +57,7 @@ pack=false publish=false test_core_clr=false test_compilercomponent_tests=false +test_benchmarks=false configuration="Debug" verbosity='minimal' binary_log=false @@ -126,6 +128,9 @@ while [[ $# > 0 ]]; do --testcompilercomponenttests) test_compilercomponent_tests=true ;; + --testbenchmarks) + test_benchmarks=true + ;; --ci) ci=true ;; @@ -330,4 +335,10 @@ if [[ "$test_compilercomponent_tests" == true ]]; then TestUsingNUnit --testproject "$repo_root/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj" --targetframework $coreclrtestframework --notestfilter fi +if [[ "$test_benchmarks" == true ]]; then + pushd "$repo_root/tests/benchmarks" + ./SmokeTestBenchmarks.sh + popd +fi + ExitWithExitCode 0 diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 2cac6dfbec3..e59fb6bd5ff 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -5093,7 +5093,11 @@ and TcPatLongIdentActivePatternCase warnOnUpper (cenv: cenv) (env: TcEnv) vFlags if dtys.Length = args.Length + 1 && ((isOptionTy g retTy && isUnitTy g (destOptionTy g retTy)) || - (isValueOptionTy g retTy && isUnitTy g (destValueOptionTy g retTy))) then + (isValueOptionTy g retTy && isUnitTy g (destValueOptionTy g retTy))) || + // `bool` partial AP always be treated as `unit option` + // For `val (|P|_|) : _ -> bool`, only allow `match x with | P -> ...` + // For `val (|P|_|) : _ -> _ -> bool`, only allow `match x with | P parameter -> ...` + (not apinfo.IsTotal && isBoolTy g retTy) then args, SynPat.Const(SynConst.Unit, m) else List.frontAndBack args @@ -10752,14 +10756,25 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt | Some (apinfo, apOverallTy, _) -> let activePatResTys = NewInferenceTypes g apinfo.ActiveTags let _, apReturnTy = stripFunTy g apOverallTy - - if isStructRetTy && apinfo.IsTotal then - errorR(Error(FSComp.SR.tcInvalidStructReturn(), mBinding)) - - if isStructRetTy then + let apRetTy = + if apinfo.IsTotal then + if isStructRetTy then errorR(Error(FSComp.SR.tcInvalidStructReturn(), mBinding)) + ActivePatternReturnKind.RefTypeWrapper + else + if isStructRetTy || isValueOptionTy cenv.g apReturnTy then ActivePatternReturnKind.StructTypeWrapper + elif isBoolTy cenv.g apReturnTy then ActivePatternReturnKind.Boolean + else ActivePatternReturnKind.RefTypeWrapper + + match apRetTy with + | ActivePatternReturnKind.Boolean -> + checkLanguageFeatureError g.langVersion LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern mBinding + | ActivePatternReturnKind.StructTypeWrapper when not isStructRetTy -> + checkLanguageFeatureError g.langVersion LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern mBinding + | ActivePatternReturnKind.StructTypeWrapper -> checkLanguageFeatureError g.langVersion LanguageFeature.StructActivePattern mBinding + | ActivePatternReturnKind.RefTypeWrapper -> () - UnifyTypes cenv env mBinding (apinfo.ResultType g rhsExpr.Range activePatResTys isStructRetTy) apReturnTy + UnifyTypes cenv env mBinding (apinfo.ResultType g rhsExpr.Range activePatResTys apRetTy) apReturnTy | None -> if isStructRetTy then diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 21bfa7a2308..b70c297e988 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -87,9 +87,9 @@ let ActivePatternElemsOfValRef g (vref: ValRef) = match TryGetActivePatternInfo vref with | Some apinfo -> - let isStructRetTy = + let retKind = if apinfo.IsTotal then - false + ActivePatternReturnKind.RefTypeWrapper else let _, apReturnTy = stripFunTy g vref.TauType let hasStructAttribute() = @@ -97,8 +97,10 @@ let ActivePatternElemsOfValRef g (vref: ValRef) = |> List.exists (function | Attrib(targetsOpt = Some(System.AttributeTargets.ReturnValue)) as a -> IsMatchingFSharpAttribute g g.attrib_StructAttribute a | _ -> false) - isStructTy g apReturnTy || hasStructAttribute() - apinfo.ActiveTags |> List.mapi (fun i _ -> APElemRef(apinfo, vref, i, isStructRetTy)) + if isValueOptionTy g apReturnTy || hasStructAttribute() then ActivePatternReturnKind.StructTypeWrapper + elif isBoolTy g apReturnTy then ActivePatternReturnKind.Boolean + else ActivePatternReturnKind.RefTypeWrapper + apinfo.ActiveTags |> List.mapi (fun i _ -> APElemRef(apinfo, vref, i, retKind)) | None -> [] /// Try to make a reference to a value in a module. diff --git a/src/Compiler/Checking/PatternMatchCompilation.fs b/src/Compiler/Checking/PatternMatchCompilation.fs index 168bd86e60e..4e8ced8fadb 100644 --- a/src/Compiler/Checking/PatternMatchCompilation.fs +++ b/src/Compiler/Checking/PatternMatchCompilation.fs @@ -43,7 +43,7 @@ type Pattern = | TPat_as of Pattern * PatternValBinding * range (* note: can be replaced by TPat_var, i.e. equals TPat_conjs([TPat_var; pat]) *) | TPat_disjs of Pattern list * range | TPat_conjs of Pattern list * range - | TPat_query of (Expr * TType list * bool * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range + | TPat_query of (Expr * TType list * ActivePatternReturnKind * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range | TPat_unioncase of UnionCaseRef * TypeInst * Pattern list * range | TPat_exnconstr of TyconRef * Pattern list * range | TPat_tuple of TupInfo * Pattern list * TType list * range @@ -618,8 +618,8 @@ let getDiscrimOfPattern (g: TcGlobals) tpinst t = Some(DecisionTreeTest.UnionCase (c, instTypes tpinst tyargs')) | TPat_array (args, ty, _m) -> Some(DecisionTreeTest.ArrayLength (args.Length, ty)) - | TPat_query ((activePatExpr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), _, _m) -> - Some (DecisionTreeTest.ActivePatternCase (activePatExpr, instTypes tpinst resTys, isStructRetTy, apatVrefOpt, idx, apinfo)) + | TPat_query ((activePatExpr, resTys, retKind, apatVrefOpt, idx, apinfo), _, _m) -> + Some (DecisionTreeTest.ActivePatternCase (activePatExpr, instTypes tpinst resTys, retKind, apatVrefOpt, idx, apinfo)) | TPat_error range -> Some (DecisionTreeTest.Error range) @@ -941,8 +941,8 @@ let rec investigationPoints inpPat = let rec erasePartialPatterns inpPat = match inpPat with - | TPat_query ((expr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), p, m) -> - if apinfo.IsTotal then TPat_query ((expr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), erasePartialPatterns p, m) + | TPat_query ((expr, resTys, retKind, apatVrefOpt, idx, apinfo), p, m) -> + if apinfo.IsTotal then TPat_query ((expr, resTys, retKind, apatVrefOpt, idx, apinfo), erasePartialPatterns p, m) else TPat_disjs ([], m) (* always fail *) | TPat_as (p, x, m) -> TPat_as (erasePartialPatterns p, x, m) | TPat_disjs (subPats, m) -> TPat_disjs(erasePartials subPats, m) @@ -1293,15 +1293,20 @@ let CompilePatternBasic // Active pattern matches: create a variable to hold the results of executing the active pattern. // If a struct return we continue with an expression for taking the address of that location. - | EdgeDiscrim(_, DecisionTreeTest.ActivePatternCase(activePatExpr, resTys, isStructRetTy, _apatVrefOpt, _, apinfo), m) :: _ -> + | EdgeDiscrim(_, DecisionTreeTest.ActivePatternCase(activePatExpr, resTys, retKind, _apatVrefOpt, _, apinfo), m) :: _ -> if not (isNil origInputValTypars) then error(InternalError("Unexpected generalized type variables when compiling an active pattern", m)) - let resTy = apinfo.ResultType g m resTys isStructRetTy + let resTy = apinfo.ResultType g m resTys retKind let argExpr = GetSubExprOfInput subexpr let appExpr = mkApps g ((activePatExpr, tyOfExpr g activePatExpr), [], [argExpr], m) - let vOpt, addrExp, _readonly, _writeonly = mkExprAddrOfExprAux g isStructRetTy false NeverMutates appExpr None mMatch + let mustTakeAddress = + match retKind with + | ActivePatternReturnKind.StructTypeWrapper -> true + | ActivePatternReturnKind.RefTypeWrapper + | ActivePatternReturnKind.Boolean -> false + let vOpt, addrExp, _readonly, _writeonly = mkExprAddrOfExprAux g mustTakeAddress false NeverMutates appExpr None mMatch match vOpt with | None -> let v, vExpr = mkCompGenLocal m ("activePatternResult" + string (newUnique())) resTy @@ -1357,13 +1362,17 @@ let CompilePatternBasic // Convert active pattern edges to tests on results data let discrim' = match discrim with - | DecisionTreeTest.ActivePatternCase(_pexp, resTys, isStructRetTy, _apatVrefOpt, idx, apinfo) -> + | DecisionTreeTest.ActivePatternCase(_pexp, resTys, retKind, _apatVrefOpt, idx, apinfo) -> let aparity = apinfo.ActiveTags.Length let total = apinfo.IsTotal if not total && aparity > 1 then error(Error(FSComp.SR.patcPartialActivePatternsGenerateOneResult(), m)) - if not total then DecisionTreeTest.UnionCase(mkAnySomeCase g isStructRetTy, resTys) + if not total then + match retKind with + | ActivePatternReturnKind.Boolean -> DecisionTreeTest.Const(Const.Bool true) + | ActivePatternReturnKind.RefTypeWrapper -> DecisionTreeTest.UnionCase(mkAnySomeCase g false, resTys) + | ActivePatternReturnKind.StructTypeWrapper -> DecisionTreeTest.UnionCase(mkAnySomeCase g true, resTys) elif aparity <= 1 then DecisionTreeTest.Const(Const.Unit) else DecisionTreeTest.UnionCase(mkChoiceCaseRef g m aparity idx, resTys) | _ -> discrim @@ -1435,7 +1444,7 @@ let CompilePatternBasic let newActives = removeActive path actives match patAtActive with | TPat_wild _ | TPat_as _ | TPat_tuple _ | TPat_disjs _ | TPat_conjs _ | TPat_recd _ -> failwith "Unexpected projection pattern" - | TPat_query ((_, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), p, m) -> + | TPat_query ((_, resTys, retKind, apatVrefOpt, idx, apinfo), p, m) -> if apinfo.IsTotal then // Total active patterns always return choice values let hasParam = (match apatVrefOpt with None -> true | Some (vref, _) -> doesActivePatternHaveFreeTypars g vref) @@ -1463,10 +1472,12 @@ let CompilePatternBasic if i = iInvestigated then let subAccess _j tpinst _ = let expr = Option.get inpExprOpt - if isStructRetTy then + match retKind with + | ActivePatternReturnKind.Boolean -> expr + | ActivePatternReturnKind.StructTypeWrapper -> // In this case, the inpExprOpt is already an address-of expression mkUnionCaseFieldGetProvenViaExprAddr (expr, mkValueSomeCase g, instTypes tpinst resTys, 0, mExpr) - else + | ActivePatternReturnKind.RefTypeWrapper -> mkUnionCaseFieldGetUnprovenViaExprAddr (expr, mkSomeCase g, instTypes tpinst resTys, 0, mExpr) mkSubFrontiers path subAccess newActives [p] (fun path j -> PathQuery(path, int64 j)) else diff --git a/src/Compiler/Checking/PatternMatchCompilation.fsi b/src/Compiler/Checking/PatternMatchCompilation.fsi index 5b2d94c8ff5..b4d68aa320c 100644 --- a/src/Compiler/Checking/PatternMatchCompilation.fsi +++ b/src/Compiler/Checking/PatternMatchCompilation.fsi @@ -27,7 +27,10 @@ type Pattern = | TPat_as of Pattern * PatternValBinding * range | TPat_disjs of Pattern list * range | TPat_conjs of Pattern list * range - | TPat_query of (Expr * TType list * bool * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range + | TPat_query of + (Expr * TType list * ActivePatternReturnKind * (ValRef * TypeInst) option * int * ActivePatternInfo) * + Pattern * + range | TPat_unioncase of UnionCaseRef * TypeInst * Pattern list * range | TPat_exnconstr of TyconRef * Pattern list * range | TPat_tuple of TupInfo * Pattern list * TType list * range diff --git a/src/Compiler/Driver/GraphChecking/TrieMapping.fs b/src/Compiler/Driver/GraphChecking/TrieMapping.fs index ff7b32e5640..add261d570c 100644 --- a/src/Compiler/Driver/GraphChecking/TrieMapping.fs +++ b/src/Compiler/Driver/GraphChecking/TrieMapping.fs @@ -123,7 +123,7 @@ let processSynModuleOrNamespace<'Decl> // Only the last node can be a module, depending on the SynModuleOrNamespaceKind. let rec visit continuation (xs: LongIdent) = match xs with - | [] -> failwith "should not be empty" + | [] -> ImmutableDictionary.Empty |> continuation | [ finalPart ] -> let name = finalPart.idText diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 8df6fecfdfb..a9fc2692157 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1593,6 +1593,7 @@ featurePreferExtensionMethodOverPlainProperty,"prefer extension method over plai featureWarningIndexedPropertiesGetSetSameType,"Indexed properties getter and setter must have the same type" featureChkTailCallAttrOnNonRec,"Raises warnings if the 'TailCall' attribute is used on non-recursive functions." featureUnionIsPropertiesVisible,"Union case test properties" +featureBooleanReturningAndReturnTypeDirectedPartialActivePattern,"Boolean-returning and return-type-directed partial active patterns" 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." 3355,tcNotAnIndexerNamedIndexingNotYetEnabled,"The value '%s' is not a function and does not support index notation." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 643224b904a..e7c2a25ee3d 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -84,6 +84,7 @@ type LanguageFeature = | PreferExtensionMethodOverPlainProperty | WarningIndexedPropertiesGetSetSameType | WarningWhenTailCallAttrOnNonRec + | BooleanReturningAndReturnTypeDirectedPartialActivePattern /// LanguageVersion management type LanguageVersion(versionText) = @@ -195,6 +196,7 @@ type LanguageVersion(versionText) = LanguageFeature.WarningIndexedPropertiesGetSetSameType, previewVersion LanguageFeature.WarningWhenTailCallAttrOnNonRec, previewVersion LanguageFeature.UnionIsPropertiesVisible, previewVersion + LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern, previewVersion ] static let defaultLanguageVersion = LanguageVersion("default") @@ -336,6 +338,8 @@ type LanguageVersion(versionText) = | LanguageFeature.PreferExtensionMethodOverPlainProperty -> FSComp.SR.featurePreferExtensionMethodOverPlainProperty () | LanguageFeature.WarningIndexedPropertiesGetSetSameType -> FSComp.SR.featureWarningIndexedPropertiesGetSetSameType () | LanguageFeature.WarningWhenTailCallAttrOnNonRec -> FSComp.SR.featureChkTailCallAttrOnNonRec () + | LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern -> + FSComp.SR.featureBooleanReturningAndReturnTypeDirectedPartialActivePattern () /// 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 7af2317e3c3..29d6c2c33a3 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -75,6 +75,7 @@ type LanguageFeature = | PreferExtensionMethodOverPlainProperty | WarningIndexedPropertiesGetSetSameType | WarningWhenTailCallAttrOnNonRec + | BooleanReturningAndReturnTypeDirectedPartialActivePattern /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs index 259948dc706..148d39da787 100644 --- a/src/Compiler/Service/FSharpProjectSnapshot.fs +++ b/src/Compiler/Service/FSharpProjectSnapshot.fs @@ -364,7 +364,10 @@ and internal ProjectCore |> Md5Hasher.addDateTimes (ReferencesOnDisk |> Seq.map (fun r -> r.LastModified)) |> Md5Hasher.addBytes' ( ReferencedProjects - |> Seq.map (fun (FSharpReference(_name, p)) -> p.ProjectSnapshot.SignatureVersion) + |> Seq.map (function + | FSharpReference(_name, p) -> p.ProjectSnapshot.SignatureVersion + | PEReference(getStamp, _) -> Md5Hasher.empty |> Md5Hasher.addDateTime (getStamp ()) + | ILModuleReference(_name, getStamp, _) -> Md5Hasher.empty |> Md5Hasher.addDateTime (getStamp ())) )) let fullHashString = lazy (fullHash.Value |> Md5Hasher.toString) @@ -429,12 +432,32 @@ and internal ProjectCore member _.CacheKey = cacheKey.Value and [] FSharpReferencedProjectSnapshot = - | FSharpReference of projectOutputFile: string * options: FSharpProjectSnapshot - //| PEReference of projectOutputFile: string * getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader - //| ILModuleReference of - // projectOutputFile: string * - // getStamp: (unit -> DateTime) * - // getReader: (unit -> ILModuleReader) + /// + /// A reference to an F# project. The physical data for it is stored/cached inside of the compiler service. + /// + /// The fully qualified path to the output of the referenced project. This should be the same value as the -r reference in the project options for this referenced project. + /// Snapshot of the referenced F# project + | FSharpReference of projectOutputFile: string * snapshot: FSharpProjectSnapshot + /// + /// A reference to any portable executable, including F#. The stream is owned by this reference. + /// The stream will be automatically disposed when there are no references to FSharpReferencedProject and is GC collected. + /// Once the stream is evaluated, the function that constructs the stream will no longer be referenced by anything. + /// If the stream evaluation throws an exception, it will be automatically handled. + /// + /// A function that calculates a last-modified timestamp for this reference. This will be used to determine if the reference is up-to-date. + /// A function that opens a Portable Executable data stream for reading. + | PEReference of getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader + + /// + /// A reference to an ILModuleReader. + /// + /// The fully qualified path to the output of the referenced project. This should be the same value as the -r reference in the project options for this referenced project. + /// A function that calculates a last-modified timestamp for this reference. This will be used to determine if the reference is up-to-date. + /// A function that creates an ILModuleReader for reading module data. + | ILModuleReference of + projectOutputFile: string * + getStamp: (unit -> DateTime) * + getReader: (unit -> FSharp.Compiler.AbstractIL.ILBinaryReader.ILModuleReader) /// /// The fully qualified path to the output of the referenced project. This should be the same value as the -r @@ -442,7 +465,9 @@ and [ member this.OutputFile = match this with - | FSharpReference(projectOutputFile, _) -> projectOutputFile + | FSharpReference(projectOutputFile = projectOutputFile) + | ILModuleReference(projectOutputFile = projectOutputFile) -> projectOutputFile + | PEReference(delayedReader = reader) -> reader.OutputFile /// /// Creates a reference for an F# project. The physical data for it is stored/cached inside of the compiler service. @@ -458,6 +483,11 @@ and [ projectOutputFile1 = projectOutputFile2 && options1 = options2 + | PEReference(getStamp1, reader1), PEReference(getStamp2, reader2) -> + reader1.OutputFile = reader2.OutputFile && (getStamp1 ()) = (getStamp2 ()) + | ILModuleReference(projectOutputFile1, getStamp1, _), ILModuleReference(projectOutputFile2, getStamp2, _) -> + projectOutputFile1 = projectOutputFile2 && (getStamp1 ()) = (getStamp2 ()) + | _ -> false | _ -> false @@ -524,17 +554,19 @@ and [] FSha let! referencedProjects = options.ReferencedProjects - |> Seq.choose (function + |> Seq.map (function | FSharpReferencedProject.FSharpReference(outputName, options) -> - Some( - async { - let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator) - - return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot) - } - ) - // TODO: other types - | _ -> None) + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator) + + return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot) + } + | FSharpReferencedProject.PEReference(getStamp, reader) -> + async.Return <| FSharpReferencedProjectSnapshot.PEReference(getStamp, reader) + | FSharpReferencedProject.ILModuleReference(outputName, getStamp, getReader) -> + async.Return + <| FSharpReferencedProjectSnapshot.ILModuleReference(outputName, getStamp, getReader)) + |> Async.Sequential let referencesOnDisk, otherOptions = @@ -601,7 +633,9 @@ let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshotBase<_>) = ReferencedProjects = projectSnapshot.ReferencedProjects |> Seq.map (function - | FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions)) + | FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions) + | PEReference(getStamp, reader) -> FSharpReferencedProject.PEReference(getStamp, reader) + | ILModuleReference(name, getStamp, getReader) -> FSharpReferencedProject.ILModuleReference(name, getStamp, getReader)) |> Seq.toArray IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 298c0e6b627..9f3881ecfd2 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -539,6 +539,41 @@ type internal TransparentCompiler member x.FileName = nm } + | FSharpReferencedProjectSnapshot.PEReference(getStamp, delayedReader) -> + { new IProjectReference with + member x.EvaluateRawContents() = + node { + let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable + + match ilReaderOpt with + | Some ilReader -> + let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs + let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData + return ProjectAssemblyDataResult.Available data + | _ -> + // Note 'false' - if a PEReference doesn't find an ILModuleReader then we don't + // continue to try to use an on-disk DLL + return ProjectAssemblyDataResult.Unavailable false + } + + member x.TryGetLogicalTimeStamp _ = getStamp () |> Some + member x.FileName = delayedReader.OutputFile + } + + | FSharpReferencedProjectSnapshot.ILModuleReference(nm, getStamp, getReader) -> + { new IProjectReference with + member x.EvaluateRawContents() = + cancellable { + let ilReader = getReader () + let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs + let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData + return ProjectAssemblyDataResult.Available data + } + |> NodeCode.FromCancellable + + member x.TryGetLogicalTimeStamp _ = getStamp () |> Some + member x.FileName = nm + } ] let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshotBase<_>) = @@ -762,20 +797,23 @@ type internal TransparentCompiler |> List.map (fun (m, fileName) -> m, FSharpFileSnapshot.CreateFromFileSystem(fileName)) return - Some - { - Id = bootstrapId - AssemblyName = assemblyName - OutFile = outFile - TcConfig = tcConfig - TcImports = tcImports - TcGlobals = tcGlobals - InitialTcInfo = initialTcInfo - LoadedSources = loadedSources - LoadClosure = loadClosureOpt - LastFileName = sourceFiles |> List.last - //ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider - } + match sourceFiles with + | [] -> None + | _ -> + Some + { + Id = bootstrapId + AssemblyName = assemblyName + OutFile = outFile + TcConfig = tcConfig + TcImports = tcImports + TcGlobals = tcGlobals + InitialTcInfo = initialTcInfo + LoadedSources = loadedSources + LoadClosure = loadClosureOpt + LastFileName = sourceFiles |> List.tryLast |> Option.defaultValue "" + //ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider + } } let ComputeBootstrapInfo (projectSnapshot: ProjectSnapshot) = @@ -1112,7 +1150,7 @@ type internal TransparentCompiler ApplyMetaCommandsFromInputToTcConfig(tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider) |> ignore - let sink = TcResultsSinkImpl(tcGlobals) + let sink = TcResultsSinkImpl(tcGlobals, file.SourceText) let hadParseErrors = not (Array.isEmpty file.ParseErrors) @@ -1141,16 +1179,13 @@ type internal TransparentCompiler //fileChecked.Trigger fileName - let newErrors = - Array.append file.ParseErrors (errHandler.CollectedPhasedDiagnostics) - fileChecked.Trigger(fileName, Unchecked.defaultof<_>) return { finisher = finisher moduleNamesDict = moduleNamesDict - tcDiagnosticsRev = [ newErrors ] + tcDiagnosticsRev = [ errHandler.CollectedPhasedDiagnostics ] tcDependencyFiles = [ fileName ] sink = sink } @@ -1353,7 +1388,7 @@ type internal TransparentCompiler let! snapshotWithSources = LoadSources bootstrapInfo priorSnapshot let file = snapshotWithSources.SourceFiles |> List.last - let! parseResults = getParseResult projectSnapshot creationDiags file bootstrapInfo.TcConfig + let! parseResults = getParseResult projectSnapshot Seq.empty file bootstrapInfo.TcConfig let! result, tcInfo = ComputeTcLastFile bootstrapInfo snapshotWithSources @@ -1405,8 +1440,7 @@ type internal TransparentCompiler Some symbolEnv ) - let tcDiagnostics = - [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |] + let tcDiagnostics = [| yield! extraDiagnostics; yield! tcDiagnostics |] let loadClosure = None // TODO: script support @@ -1649,7 +1683,8 @@ type internal TransparentCompiler | ProjectAssemblyDataResult.Available data -> Some data | _ -> None - let symbolUses = tcInfo.sink |> Seq.map (fun sink -> sink.GetSymbolUses()) + let symbolUses = + tcInfo.sink |> Seq.rev |> Seq.map (fun sink -> sink.GetSymbolUses()) let details = (bootstrapInfo.TcGlobals, diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 2109361291e..da8e43bfba5 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -4565,6 +4565,17 @@ type DecisionTreeCase = member x.DebugText = x.ToString() override x.ToString() = sprintf "DecisionTreeCase(...)" + +[] +type ActivePatternReturnKind = + | RefTypeWrapper + | StructTypeWrapper + | Boolean + member this.IsStruct with get () = + match this with + | RefTypeWrapper -> false + | StructTypeWrapper + | Boolean -> true [] type DecisionTreeTest = @@ -4585,20 +4596,20 @@ type DecisionTreeTest = /// Test if the input to a decision tree is an instance of the given type | IsInst of source: TType * target: TType - /// Test.ActivePatternCase(activePatExpr, activePatResTys, isStructRetTy, activePatIdentity, idx, activePatInfo) + /// Test.ActivePatternCase(activePatExpr, activePatResTys, activePatRetKind, activePatIdentity, idx, activePatInfo) /// /// Run the active pattern and bind a successful result to a /// variable in the remaining tree. /// activePatExpr -- The active pattern function being called, perhaps applied to some active pattern parameters. /// activePatResTys -- The result types (case types) of the active pattern. - /// isStructRetTy -- Is the active pattern a struct return + /// activePatRetKind -- Indicating what is returning from the active pattern /// activePatIdentity -- The value and the types it is applied to. If there are any active pattern parameters then this is empty. /// idx -- The case number of the active pattern which the test relates to. /// activePatternInfo -- The extracted info for the active pattern. | ActivePatternCase of activePatExpr: Expr * activePatResTys: TTypes * - isStructRetTy: bool * + activePatRetKind: ActivePatternReturnKind * activePatIdentity: (ValRef * TypeInst) option * idx: int * activePatternInfo: ActivePatternInfo @@ -4667,7 +4678,7 @@ type ActivePatternElemRef = activePatternInfo: ActivePatternInfo * activePatternVal: ValRef * caseIndex: int * - isStructRetTy: bool + activePatRetKind: ActivePatternReturnKind /// Get the full information about the active pattern being referred to member x.ActivePatternInfo = (let (APElemRef(info, _, _, _)) = x in info) @@ -4676,7 +4687,7 @@ type ActivePatternElemRef = member x.ActivePatternVal = (let (APElemRef(_, vref, _, _)) = x in vref) /// Get a reference to the value for the active pattern being referred to - member x.IsStructReturn = (let (APElemRef(_, _, _, isStructRetTy)) = x in isStructRetTy) + member x.ActivePatternRetKind = (let (APElemRef(_, _, _, activePatRetKind)) = x in activePatRetKind) /// Get the index of the active pattern element within the overall active pattern member x.CaseIndex = (let (APElemRef(_, _, n, _)) = x in n) diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 3eb47b5eb47..7f0a0ef4ed3 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -3267,6 +3267,18 @@ type DecisionTreeCase = /// Get the discriminator associated with the case member Discriminator: DecisionTreeTest +/// Indicating what is returning from an AP +[] +type ActivePatternReturnKind = + /// Returning `_ option` or `Choice<_, _, .., _>` + | RefTypeWrapper + /// Returning `_ voption` + | StructTypeWrapper + /// Returning bool + | Boolean + + member IsStruct: bool + [] type DecisionTreeTest = @@ -3287,20 +3299,20 @@ type DecisionTreeTest = /// Test if the input to a decision tree is an instance of the given type | IsInst of source: TType * target: TType - /// Test.ActivePatternCase(activePatExpr, activePatResTys, isStructRetTy, activePatIdentity, idx, activePatInfo) + /// Test.ActivePatternCase(activePatExpr, activePatResTys, activePatRetKind, activePatIdentity, idx, activePatInfo) /// /// Run the active pattern type bind a successful result to a /// variable in the remaining tree. /// activePatExpr -- The active pattern function being called, perhaps applied to some active pattern parameters. /// activePatResTys -- The result types (case types) of the active pattern. - /// isStructRetTy -- Is the active pattern a struct return + /// activePatRetKind -- Indicating what is returning from the active pattern /// activePatIdentity -- The value type the types it is applied to. If there are any active pattern parameters then this is empty. /// idx -- The case number of the active pattern which the test relates to. /// activePatternInfo -- The extracted info for the active pattern. | ActivePatternCase of activePatExpr: Expr * activePatResTys: TTypes * - isStructRetTy: bool * + activePatRetKind: ActivePatternReturnKind * activePatIdentity: (ValRef * TypeInst) option * idx: int * activePatternInfo: Syntax.PrettyNaming.ActivePatternInfo @@ -3359,7 +3371,7 @@ type ActivePatternElemRef = activePatternInfo: Syntax.PrettyNaming.ActivePatternInfo * activePatternVal: ValRef * caseIndex: int * - isStructRetTy: bool + activePatRetKind: ActivePatternReturnKind override ToString: unit -> string @@ -3376,7 +3388,7 @@ type ActivePatternElemRef = member DebugText: string /// Get a reference to the value for the active pattern being referred to - member IsStructReturn: bool + member ActivePatternRetKind: ActivePatternReturnKind /// Records the "extra information" for a value compiled as a method (rather /// than a closure or a local), including argument names, attributes etc. diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 7beb639a2ee..8ef6ce62182 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -3672,6 +3672,13 @@ let mkNullableTy (g: TcGlobals) ty = TType_app (g.system_Nullable_tcref, [ty], g let mkListTy (g: TcGlobals) ty = TType_app (g.list_tcr_nice, [ty], g.knownWithoutNull) +let isBoolTy (g: TcGlobals) ty = + match tryTcrefOfAppTy g ty with + | ValueNone -> false + | ValueSome tcref -> + tyconRefEq g g.system_Bool_tcref tcref || + tyconRefEq g g.bool_tcr tcref + let isValueOptionTy (g: TcGlobals) ty = match tryTcrefOfAppTy g ty with | ValueNone -> false @@ -9228,14 +9235,17 @@ type ActivePatternInfo with member x.DisplayNameByIdx idx = x.ActiveTags[idx] |> ConvertLogicalNameToDisplayName - member apinfo.ResultType g m retTys isStruct = + member apinfo.ResultType g m retTys retKind = let choicety = mkChoiceTy g m retTys if apinfo.IsTotal then choicety - elif isStruct then mkValueOptionTy g choicety - else mkOptionTy g choicety + else + match retKind with + | ActivePatternReturnKind.RefTypeWrapper -> mkOptionTy g choicety + | ActivePatternReturnKind.StructTypeWrapper -> mkValueOptionTy g choicety + | ActivePatternReturnKind.Boolean -> g.bool_ty - member apinfo.OverallType g m argTy retTys isStruct = - mkFunTy g argTy (apinfo.ResultType g m retTys isStruct) + member apinfo.OverallType g m argTy retTys retKind = + mkFunTy g argTy (apinfo.ResultType g m retTys retKind) //--------------------------------------------------------------------------- // Active pattern validation diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 2345ac5eb40..bec1e031185 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -1528,6 +1528,9 @@ val mkVoidPtrTy: TcGlobals -> TType /// Build a single-dimensional array type val mkArrayType: TcGlobals -> TType -> TType +/// Determine if a type is a bool type +val isBoolTy: TcGlobals -> TType -> bool + /// Determine if a type is a value option type val isValueOptionTy: TcGlobals -> TType -> bool @@ -2449,10 +2452,11 @@ type PrettyNaming.ActivePatternInfo with member DisplayNameByIdx: idx: int -> string /// Get the result type for the active pattern - member ResultType: g: TcGlobals -> range -> TType list -> bool -> TType + member ResultType: g: TcGlobals -> range -> TType list -> ActivePatternReturnKind -> TType /// Get the overall type for a function that implements the active pattern - member OverallType: g: TcGlobals -> m: range -> argTy: TType -> retTys: TType list -> isStruct: bool -> TType + member OverallType: + g: TcGlobals -> m: range -> argTy: TType -> retTys: TType list -> retKind: ActivePatternReturnKind -> TType val doesActivePatternHaveFreeTypars: TcGlobals -> ValRef -> bool diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index c07ba0d7033..c7dbc64bac7 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -247,6 +247,11 @@ automatické generování vlastnosti Message pro deklarace exception + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Povolit implicitní atribut Extension pro deklarující typy, moduly diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index d0feebf9d97..d5800420f00 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -247,6 +247,11 @@ Automatische Generierung der Eigenschaft „Message“ für „exception“-Deklarationen + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Implizites Erweiterungsattribut für deklarierende Typen und Module zulassen diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index fae7ec8f032..dcfce175697 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -247,6 +247,11 @@ generación automática de la propiedad 'Message' para declaraciones 'exception' + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Permitir atributo Extension implícito en tipos declarativo, módulos diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 4a98252d56f..590c4fb8e15 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -247,6 +247,11 @@ génération automatique de la propriété « Message » pour les déclarations « exception » + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Autoriser l’attribut implicite Extension lors de la déclaration des types, modules diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 969c6716e2f..70613984b76 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -247,6 +247,11 @@ generazione automatica della proprietà 'Messaggio' per le dichiarazioni 'eccezione' + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Consentire l'attributo estensione implicito per i tipi dichiarabili, i moduli diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 440e83c32b9..84a9ca6ef70 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -247,6 +247,11 @@ `exception` 宣言の `Message` プロパティの自動生成 + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules 型、モジュールの宣言で暗黙的な拡張属性を許可する diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 1a55695d885..38ffac7d32d 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -247,6 +247,11 @@ 'exception' 선언에 대한 'Message' 속성 자동 생성 + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules 유형, 모듈 선언에 암시적 확장 속성 허용 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index fdf34864db0..ac1d54f63c4 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -247,6 +247,11 @@ Automatyczne generowanie właściwości „Wiadomość“ dla deklaracji „Wyjątek“ + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Zezwalaj na niejawny atrybut Rozszerzenie dla deklarujących typów, modułów diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 86ede3eed08..6beb1946267 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -247,6 +247,11 @@ geração automática da propriedade 'Message' para declarações de 'exception' + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Permitir atributo de Extensão implícito em tipos declarativos, módulos diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index e5844060d67..1b83dc7cc6b 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -247,6 +247,11 @@ автоматическое создание свойства “Message” для объявлений “exception” + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Разрешить атрибут неявного расширения для объявляющих типов, модулей diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 6e5505f1b7f..43f5069b2eb 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -247,6 +247,11 @@ 'exception' bildirimleri için 'Message' özelliğinin otomatik olarak oluşturulması + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules Türler, modüller bildirirken örtük Extension özniteliğine izin ver diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 3e5248d2115..91cad01e99e 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -247,6 +247,11 @@ 自动生成“异常”声明的“消息”属性 + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules 允许对声明类型、模块使用隐式扩展属性 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index a1b3568925f..f12b3fd0b5e 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -247,6 +247,11 @@ 自動產生 'exception' 宣告的 'Message' 屬性 + + Boolean-returning and return-type-directed partial active patterns + Boolean-returning and return-type-directed partial active patterns + + Allow implicit Extension attribute on declaring types, modules 允許宣告類型、模組上的隱含擴充屬性 diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 694b9c580e9..e442335f940 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -18,6 +18,14 @@ let waitFor (mre: ManualResetEvent) = if not <| mre.WaitOne timeout then failwith "waitFor timed out" +let waitUntil condition value = + task { + let sw = Stopwatch.StartNew() + while not <| condition value do + if sw.Elapsed > timeout then + failwith "waitUntil timed out" + do! Task.Delay 10 + } let rec internal spinFor (duration: TimeSpan) = node { @@ -29,6 +37,24 @@ let rec internal spinFor (duration: TimeSpan) = } +type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(memoize: AsyncMemoize<'a,'b,'c>) as self = + + let events = ConcurrentQueue() + + do memoize.OnEvent self.Add + + member _.Add (e, (_label, k, _version)) = events.Enqueue (e, k) + + member _.Received value = events |> Seq.exists (fst >> (=) value) + + member _.CountOf value count = events |> Seq.filter (fst >> (=) value) |> Seq.length |> (=) count + + member _.ShouldBe (expected) = + let expected = expected |> Seq.toArray + let actual = events |> Seq.toArray + Assert.Equal<_ array>(expected, actual) + + [] let ``Basics``() = @@ -69,21 +95,14 @@ let ``We can cancel a job`` () = let jobStarted = new ManualResetEvent(false) - let jobCanceled = new ManualResetEvent(false) - let computation action = node { action() |> ignore do! spinFor timeout failwith "Should be canceled before it gets here" } - let eventLog = ConcurrentQueue() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_label, k, _version)) -> - eventLog.Enqueue (e, k) - if e = Canceled then - jobCanceled.Set() |> ignore - ) + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -96,13 +115,10 @@ let ``We can cancel a job`` () = waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts3.Token) - waitFor jobRequested + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel() cts2.Cancel() @@ -111,16 +127,16 @@ let ``We can cancel a job`` () = cts3.Cancel() - waitFor jobCanceled + do! waitUntil events.Received Canceled - Assert.Equal<(JobEvent * int) array>([| + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Canceled, key - |], eventLog |> Seq.toArray ) + ] } [] @@ -136,9 +152,9 @@ let ``Job is restarted if first requestor cancels`` () = return key * 2 } - let eventLog = ConcurrentStack() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_, k, _version)) -> eventLog.Push (e, k)) + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) + use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -151,13 +167,10 @@ let ``Job is restarted if first requestor cancels`` () = waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitFor jobRequested + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel() @@ -168,16 +181,13 @@ let ``Job is restarted if first requestor cancels`` () = let! result = _task2 Assert.Equal(2, result) - let orderedLog = eventLog |> Seq.rev |> Seq.toList - let expected = [ + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Finished, key ] - - Assert.Equal<_ list>(expected, orderedLog) } [] @@ -192,10 +202,10 @@ let ``Job is restarted if first requestor cancels but keeps running if second re waitFor jobCanComplete return key * 2 } + + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) - let eventLog = ConcurrentStack() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Push (e, k)) use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -208,13 +218,10 @@ let ``Job is restarted if first requestor cancels but keeps running if second re waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitFor jobRequested + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel() @@ -227,16 +234,13 @@ let ``Job is restarted if first requestor cancels but keeps running if second re let! result = _task3 Assert.Equal(2, result) - let orderedLog = eventLog |> Seq.rev |> Seq.toList - let expected = [ + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Finished, key ] - - Assert.Equal<_ list>(expected, orderedLog) } diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/StructTypes/StructActivePatterns.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/StructTypes/StructActivePatterns.fs index 2d9a31d2c7b..1b121b27766 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/StructTypes/StructActivePatterns.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/StructTypes/StructActivePatterns.fs @@ -46,13 +46,11 @@ let rec (|IsOne|_|) someNumber = | 1 -> ValueSome 1 | _ -> ValueNone """ + |> withLangVersion80 |> withOptions ["--warnaserror+"] |> typecheck |> shouldFail - |> withSingleDiagnostic (Error 1,Line 2, Col 9 , Line 2, Col 31, """This expression was expected to have type - ''a option' -but here has type - 'int voption' """) + |> withSingleDiagnostic (Error 3350, Line 2, Col 9, Line 2, Col 31, "Feature 'Boolean-returning and return-type-directed partial active patterns' is not available in F# 8.0. Please use language version 'PREVIEW' or greater.") [] let ``Rec struct active pattern is possible`` () = diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index a9dd5382893..ec4fc441f29 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -220,9 +220,10 @@ - - - + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs index f4c5c41770d..735e7828b89 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs @@ -151,14 +151,14 @@ let GetAllUsesOfAllSymbols() = .Build() use _ = Activity.start "GetAllUsesOfAllSymbols" [ ] - - let result = - async { + + let result = + async { let project = makeTestProject() let checker = ProjectWorkflowBuilder(project, useGetSource=true, useChangeNotifications = true).Checker - do! saveProject project false checker + do! saveProject project false checker let options = project.GetProjectOptions checker - let! checkProjectResults = checker.ParseAndCheckProject(options) + let! checkProjectResults = checker.ParseAndCheckProject(options) return checkProjectResults.GetAllUsesOfAllSymbols() } |> Async.RunSynchronously @@ -166,4 +166,4 @@ let GetAllUsesOfAllSymbols() = traceProvider.ForceFlush() |> ignore traceProvider.Dispose() - Assert.Equal(79, result.Length) + if result.Length <> 79 then failwith $"Expected 79 symbolUses, got {result.Length}:\n%A{result}" diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index 90dc85c4a33..5ff288185c4 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -479,12 +479,12 @@ type SignatureFiles = Yes = 1 | No = 2 | Some = 3 let fuzzingTest seed (project: SyntheticProject) = task { let rng = System.Random seed - let checkingThreads = 3 - let maxModificationDelayMs = 10 + let checkingThreads = 10 + let maxModificationDelayMs = 50 let maxCheckingDelayMs = 20 //let runTimeMs = 30000 let signatureFileModificationProbability = 0.25 - let modificationLoopIterations = 10 + let modificationLoopIterations = 50 let checkingLoopIterations = 5 let minCheckingTimeoutMs = 0 @@ -622,7 +622,7 @@ let fuzzingTest seed (project: SyntheticProject) = task { } try - let! _x = threads |> Seq.skip 1 |> Task.WhenAll + let! _x = threads |> Task.WhenAll () with | e -> diff --git a/tests/FSharp.Compiler.ComponentTests/Language/BooleanReturningAndReturnTypeDirectedPartialActivePatternTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/BooleanReturningAndReturnTypeDirectedPartialActivePatternTests.fs new file mode 100644 index 00000000000..ce630714e64 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/BooleanReturningAndReturnTypeDirectedPartialActivePatternTests.fs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module Language.BooleanReturningAndReturnTypeDirectedPartialActivePatternTests + +open Xunit +open FSharp.Test.Compiler +open FSharp.Test.ScriptHelpers + +let fsiSession = getSessionForEval [||] LangVersion.Preview + +let runCode = evalInSharedSession fsiSession + +[] +let ``Partial struct active pattern returns ValueOption`1 without []`` () = + FSharp "let (|P1|_|) x = ValueNone" + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + +[] +let ``Partial struct active pattern returns bool`` () = + FSharp "let (|P1|_|) x = false" + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + +[] +let ``Single case active pattern returning bool should success`` () = + FSharp """ +let (|IsA|) x = x = "A" +let (IsA r) = "A" + """ + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + +[] +let ``Partial struct active pattern results can be retrieved`` () = + Fsx """ +let fail msg = + printfn "%s" msg + failwith msg + +let (|P1|_|) x = x <> 0 +let (|EqualTo|_|) y x = x = y + +match 0, 1 with +| P1, _ -> fail "unit" +| _, P1 -> () +| _ -> fail "unit" + +match "x" with +| EqualTo "y" -> fail "with argument" +| EqualTo "x" -> () +| _ -> fail "with argument" + """ + |> withLangVersionPreview + |> runCode + |> shouldSucceed + +// negative tests + +[] +let ``bool active pattern (-langversion:8.0)`` () = + FSharp """let (|OddBool|_|) x = x % 2 = 1 +let (|OddVOption|_|) x = if x % 2 = 1 then ValueSome() else ValueNone + """ + |> withLangVersion80 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3350, Line 1, Col 5, Line 1, Col 20, "Feature 'Boolean-returning and return-type-directed partial active patterns' is not available in F# 8.0. Please use language version 'PREVIEW' or greater.") + (Error 3350, Line 2, Col 5, Line 2, Col 23, "Feature 'Boolean-returning and return-type-directed partial active patterns' is not available in F# 8.0. Please use language version 'PREVIEW' or greater.") + ] + +[] +let ``Can not receive result from bool active pattern`` () = + FSharp """let (|IsA|_|) x = x = "A" + +match "A" with +| IsA result -> "A" +| _ -> "Not A" + +match "A" with +| IsA result -> result +| _ -> "Not A" + +match "A" with +| IsA "to match return value" -> "Matched" +| _ -> "not Matched" +""" + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1, Line 4, Col 3, Line 4, Col 13, + "This expression was expected to have type + 'string -> bool' +but here has type + 'bool' ") + (Error 39, Line 4, Col 7, Line 4, Col 13, + "The value or constructor 'result' is not defined. Maybe you want one of the following: + Result") + (Error 1, Line 8, Col 3, Line 8, Col 13, + "This expression was expected to have type + 'string -> bool' +but here has type + 'bool' ") + (Error 39, Line 8, Col 7, Line 8, Col 13, + "The value or constructor 'result' is not defined. Maybe you want one of the following: + Result") + (Error 1, Line 12, Col 3, Line 12, Col 30, + "This expression was expected to have type + 'string -> bool' +but here has type + 'bool' ") + ] diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TrieMappingTests.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TrieMappingTests.fs index e5bc3ef0299..6ad29818a2a 100644 --- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TrieMappingTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TrieMappingTests.fs @@ -540,6 +540,27 @@ let ``Tries are built up incrementally`` () = ParsedInput = parseSourceCode ("D.fs", "module D") } |] - + for idx, t in trie do Assert.AreEqual(idx + 1, t.Children.Count) + + +module InvalidSyntax = + + [] + let ``Unnamed module`` () = + let trie = + getLastTrie + [| { Idx = 0 + FileName = "A.fs" + ParsedInput = + parseSourceCode ( + "A.fs", + """ + module + + () + """ + ) } |] + + Assert.True trie.Children.IsEmpty diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index b7484492a42..b6eeea8f992 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -2338,19 +2338,44 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FS FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.String, Int32, FSharp.Compiler.Text.ISourceText) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String get_Label() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: FSharpProjectSnapshot get_snapshot() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: FSharpProjectSnapshot snapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader] getReader +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader] get_getReader() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] getStamp +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] get_getStamp() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: FSharp.Compiler.CodeAnalysis.DelayedILModuleReader delayedReader +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: FSharp.Compiler.CodeAnalysis.DelayedILModuleReader get_delayedReader() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] getStamp +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] get_getStamp() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 FSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 ILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 PEReference FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean Equals(System.Object) -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot get_options() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot options +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsFSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsPEReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsFSharpReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsILModuleReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsPEReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot CreateFSharp(System.String, FSharpProjectSnapshot) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewFSharpReference(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewILModuleReference(System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewPEReference(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime], FSharp.Compiler.CodeAnalysis.DelayedILModuleReader) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 GetHashCode() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 Tag FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 get_Tag() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String OutputFile FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String ToString() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_OutputFile() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_projectOutputFile() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String projectOutputFile FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(ReferenceOnDisk) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object, System.Collections.IEqualityComparer) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index b7484492a42..b6eeea8f992 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2338,19 +2338,44 @@ FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FS FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.String, Int32, FSharp.Compiler.Text.ISourceText) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String get_Label() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: FSharpProjectSnapshot get_snapshot() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: FSharpProjectSnapshot snapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader] getReader +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader] get_getReader() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] getStamp +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] get_getStamp() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: FSharp.Compiler.CodeAnalysis.DelayedILModuleReader delayedReader +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: FSharp.Compiler.CodeAnalysis.DelayedILModuleReader get_delayedReader() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] getStamp +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference: Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime] get_getStamp() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 FSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 ILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags: Int32 PEReference FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean Equals(System.Object) -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot get_options() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot options +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsFSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean IsPEReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsFSharpReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsILModuleReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean get_IsPEReference() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+FSharpReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+ILModuleReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+PEReference +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot+Tags FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot CreateFSharp(System.String, FSharpProjectSnapshot) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewFSharpReference(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewILModuleReference(System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.ILBinaryReader+ILModuleReader]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewPEReference(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime], FSharp.Compiler.CodeAnalysis.DelayedILModuleReader) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 GetHashCode() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 Tag FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 get_Tag() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String OutputFile FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String ToString() FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_OutputFile() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_projectOutputFile() -FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String projectOutputFile FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(ReferenceOnDisk) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object) FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object, System.Collections.IEqualityComparer) diff --git a/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md b/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md index d3bd6c823e6..72a6acc3f1e 100644 --- a/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md +++ b/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md @@ -12,7 +12,7 @@ To run a benchmark for a local FCS in the current codebase you can run the bench ```dotnet run --project HistoricalBenchmark.fsproj -c Release --filter *``` -To run a comparison use the `runner.ipynb` .NET notebook +To run a comparison use the `runner.ipynb` .NET notebook. ## How it works @@ -35,9 +35,4 @@ As of now the minimum supported version of FCS is 13.0.0 ## Sample results -Below is a sample result of running the notebook locally with a selection of versions: -![a](./sample_result.png?raw=true) - -## Other - -You can find this document under 'tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md'. \ No newline at end of file +See sample results in the dedicated [sample_results](./sample_results/) folder. \ No newline at end of file diff --git a/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/sample_results/README.md b/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/sample_results/README.md index 098d4f8a0c4..803e47ed266 100644 --- a/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/sample_results/README.md +++ b/tests/benchmarks/FCSBenchmarks/BenchmarkComparison/sample_results/README.md @@ -1,13 +1,16 @@ # Sample results -This folder contains a selection of results obtained by running the notebook located in `../runner.ipynb` + +This folder contains a selection of results obtained by running the notebook located in `../runner.ipynb`. ## Timings are not accurate + The results were gathered on a busy machine without much care taken to provide a reliable performance environment. While this means the timing metrics are not very useful, the results can still be useful for two reasons: * allocation data is quite accurate as it doesn't tend to depend much on the environment * they work as examples that can make using the benchmarks easier ## Structure + Each directory contains 3 files output by `HistoricalBenchmark.Runner.runAll` function for a given selection of versions. The three different version sets are: @@ -16,10 +19,12 @@ The three different version sets are: - `10_latest_nuget_versions` - 10 FCS NuGet versions between `v41.0.2` and ``v41.0.5-preview.22327.2` ## Observations -One thing that can be observed by looking at the results in `between_2_nuget_versions` is the noticable increase of allocations in https://github.com/dotnet/fsharp/pull/11517 + +One thing that can be observed by looking at the results in `between_2_nuget_versions` is the noticeable increase of allocations in https://github.com/dotnet/fsharp/pull/11517. While this isn't necessarily something worth addressing, partly because later revisions show reduced allocations, it shows how running a historical benchmark can be potentially useful. ## Notes + - The metrics gathered here are very limited - much more data can be gathered from each benchmark. - Such historical benchmarks run locally might be mostly deprecated once CI setup exists for performance tests that will provide the necessary historical information \ No newline at end of file diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/README.md b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/README.md index 90e90b0fd6f..1b574ec1b56 100644 --- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/README.md +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/README.md @@ -2,8 +2,8 @@ ## What is it -* A selection of BDN benchmarks analysing FCS performance -* Uses BDN's commandline API +* A selection of BDN benchmarks analyzing FCS performance +* Uses BDN's command line API ## How to run it @@ -11,12 +11,15 @@ Running all benchmarks: ```dotnet run -c Release --filter *``` Running a specific benchmark: -```dotnet run -c Release --filter *ParsingTypeCheckerFs*``` +```dotnet run -c Release --filter *ParsingCheckExpressionsFs*``` ## Sample results -*TODO* - -## Other - -You can find this document under 'tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md'. \ No newline at end of file +| Method | Job | UnrollFactor | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated | +|--------------------------------------- |----------- |------------- |---------------:|-------------:|--------------:|---------------:|------------:|-----------:|----------:|-----------:| +| SimplifyNames | DefaultJob | 16 | 17,221.4 us | 378.14 us | 1,097.04 us | 17,164.1 us | 1875.0000 | 31.2500 | - | 11,654 KB | +| UnusedOpens | DefaultJob | 16 | 852.7 us | 16.96 us | 36.87 us | 852.0 us | 120.1172 | 37.1094 | - | 736 KB | +| UnusedDeclarations | DefaultJob | 16 | 208.2 us | 6.65 us | 19.09 us | 202.7 us | 71.5332 | 3.6621 | - | 438 KB | +| ParsingCheckExpressionsFs | Job-CXFNSP | 1 | 255,107.0 us | 39,778.24 us | 117,287.03 us | 186,340.7 us | 4000.0000 | 1000.0000 | - | 30,082 KB | +| ILReading | Job-CXFNSP | 1 | 1,256,653.6 us | 24,802.85 us | 48,958.41 us | 1,249,170.3 us | 102000.0000 | 31000.0000 | 2000.0000 | 671,507 KB | +| TypeCheckFileWith100ReferencedProjects | Job-CXFNSP | 1 | 6,541.1 us | 242.62 us | 700.00 us | 6,614.2 us | - | - | - | 3,547 KB | diff --git a/tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md b/tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md index 534e2762961..0cd8807aa16 100644 --- a/tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md +++ b/tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md @@ -9,12 +9,18 @@ ## How to run it 1. Run `dotnet run -c release` -2. Output available on the commandline and in `BenchmarkDotNet.Artifacts/` +2. Output available on the command line and in `BenchmarkDotNet.Artifacts/` ## Sample results -*TODO* - -## Other - -You can find this document under 'tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md'. \ No newline at end of file +``` +BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621 +11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores +.NET SDK=6.0.320 + [Host] : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT DEBUG + DefaultJob : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT +``` + +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|--------------------------- |--------:|--------:|--------:|-------------:|------------:|----------:|----------:| +| ParseAndCheckFileInProject | 22.14 s | 0.543 s | 1.522 s | 1645000.0000 | 307000.0000 | 6000.0000 | 10 GB | diff --git a/tests/benchmarks/FCSBenchmarks/README.md b/tests/benchmarks/FCSBenchmarks/README.md index 6d4a3a674f0..aedaf8c38aa 100644 --- a/tests/benchmarks/FCSBenchmarks/README.md +++ b/tests/benchmarks/FCSBenchmarks/README.md @@ -2,26 +2,19 @@ ## What can be found here -Benchmarks that exercise performance of `FSharp.Compiler.Service` +Benchmarks that exercise performance of `FSharp.Compiler.Service`. + +## Testing performance of FSharp.Compiler.Service -## Testing performance of FSharp.Compiler.Service Performance of the compiler service is crucial for having good developer experience. This includes compilation, type checking and any other parts of the API used by IDEs. When making changes to the FCS source code, consider running some of these to assess impact of the changes on performance. ## Benchmark list + * `BenchmarkComparison/` - a Notebook-based benchmark that analyses performance of `FSharpChecker.ParseAndCheckFileInProject` on a single-file F# project. Supports comparing different revisions of the FCS codebase and fetching the code automatically. -* `CompilerServiceBenchmarks/` - +* `CompilerServiceBenchmarks/` - a selection of BDN benchmarks analyzing FCS performance * `FCSSourceFiles/` - analyses performance of `FSharpChecker.ParseAndCheckFileInProject` for the `FSharp.Core` project. Uses locally available source code. All the above benchmarks use BenchmarkDotNet. - -## Quickly validating that the benchmarks work -`SmokeTestAllBenchmarks.ps1` allows to run all BDN benchmarks in this directory with a minimum number of iterations, as a way to verify that the benchmarks still work. - -This doesn't validate the notebook-based meta-benchmarks. - -## Other - -You can find this document under 'tests/benchmarks/FCSBenchmarks/README.md'. \ No newline at end of file diff --git a/tests/benchmarks/README.md b/tests/benchmarks/README.md index c3500eb8bb5..dc304fed2ce 100644 --- a/tests/benchmarks/README.md +++ b/tests/benchmarks/README.md @@ -12,10 +12,6 @@ Each of them assesses a slightly different use case and is run in a different wa Since there is currently no dedicated hardware setup for running benchmarks in a highly accurate fashion, the results obtained by running them locally have to be treated carefully. Specifically results obtained on different hardware or in different environments should be treated differently. -Note that there are plans to update the performance testing infrastructure. More information can be found at the following links: -* https://github.com/dotnet/fsharp/discussions/12526 -* https://github.com/dotnet/performance/issues/2457 - ### Types of performance tests Performance tests in this codebase can be broadly put into two groups: @@ -26,9 +22,10 @@ Group 1. affects end users of programs, while group 2. affects developer experie ### Directory structure -Tests are structured as follows +The code is structured as follows * `CompiledCodeBenchmarks/` - benchmarks that test compiled code performance. * `FCSBenchmarks/` - benchmarks of the compiler service itself. +* `FSharp.Benchmarks.Common/` - the library with common code. ### Jupyter notebooks @@ -43,7 +40,11 @@ It helps avoid common benchmarking pitfalls and provide highly-accurate, repeata A BDN benchmark is an executable. To run it, simply run `dotnet run %BenchmarkProject.fsproj%` in the benchmark's directory. -### Writing a new benchmark +## Quickly validating that the benchmarks work + +`SmokeTestBenchmarks.ps1` allows to run faster BDN benchmarks with a minimum number of iterations, as a way to verify that the benchmarks still work. This doesn't validate the notebook-based meta-benchmarks. + +## Authoring benchmarks When adding a benchmark, consider: * choosing an appropriate subdirectory @@ -52,8 +53,147 @@ When adding a benchmark, consider: * * how to run the benchmark, including any environment requirements * * an example of the results it produces -For instructions on how to write a BDN benchmark see [DEVGUIDE](https://github.com/dotnet/fsharp/blob/main/DEVGUIDE.md). - -## Other +Here are the steps for creating benchmarks: + +1. Perform a clean build of the compiler and FCS from source (as described in this document, build can be done with `-noVisualStudio` in case if FCS/FSharp.Core is being benchmarked/profiled). + +2. Create a benchmark project (in this example, the project will be created in `tests\benchmarks\FCSBenchmarks`). + + ```shell + cd tests\benchmarks\FCSBenchmarks + dotnet new console -o FcsBench --name FcsBench -lang F# + ``` + +3. Add needed packages and project references. + + ```shell + cd FcsBench + dotnet add package BenchmarkDotNet + dotnet add reference ..\..\..\src\Compiler\FSharp.Compiler.Service.fsproj + ``` + +4. Additionally, if you want to test changes to the FSharp.Core (note that the relative path can be different) + + ```shell + dotnet add reference ..\..\..\src\FSharp.Core\FSharp.Core.fsproj + ``` + + And the following property has to be added to `FcsBench.fsproj`: + + ```xml + + true + + ``` + +5. Add a new benchmark for FCS/FSharp.Core by editing `Program.fs`. + + ```fsharp + open System.IO + open FSharp.Compiler.CodeAnalysis + open FSharp.Compiler.Diagnostics + open FSharp.Compiler.Text + open BenchmarkDotNet.Attributes + open BenchmarkDotNet.Running + + [] + type CompilerService() = + let mutable checkerOpt = None + let mutable sourceOpt = None + + let parsingOptions = + { + SourceFiles = [|"CheckExpressions.fs"|] + ConditionalDefines = [] + DiagnosticOptions = FSharpDiagnosticOptions.Default + LangVersionText = "default" + IsInteractive = false + LightSyntax = None + CompilingFsLib = false + IsExe = false + } + + [] + member _.Setup() = + match checkerOpt with + | None -> + checkerOpt <- Some(FSharpChecker.Create(projectCacheSize = 200)) + | _ -> () + + match sourceOpt with + | None -> + sourceOpt <- Some <| SourceText.ofString(File.ReadAllText("""C:\Users\vlza\code\fsharp\src\Compiler\Checking\CheckExpressions.fs""")) + | _ -> () + + [] + member _.ParsingCheckExpressionsFs() = + match checkerOpt, sourceOpt with + | None, _ -> failwith "no checker" + | _, None -> failwith "no source" + | Some(checker), Some(source) -> + let results = checker.ParseFile("CheckExpressions.fs", source, parsingOptions) |> Async.RunSynchronously + if results.ParseHadErrors then failwithf "parse had errors: %A" results.Diagnostics + + [] + member _.ParsingCheckExpressionsFsSetup() = + match checkerOpt with + | None -> failwith "no checker" + | Some(checker) -> + checker.InvalidateAll() + checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + checker.ParseFile("dummy.fs", SourceText.ofString "dummy", parsingOptions) |> Async.RunSynchronously |> ignore + + [] + let main _ = + BenchmarkRunner.Run() |> ignore + 0 + ``` + + For more detailed information about available BenchmarkDotNet options, please refer to [BenchmarkDotNet Documentation](https://benchmarkdotnet.org/articles/overview.html). + +6. Build and run the benchmark. + + ```shell + dotnet build -c Release + dotnet run -c Release + ``` + +7. You can find results in `.\BenchmarkDotNet.Artifacts\results\` in the current benchmark project directory. + + ```shell + > ls .\BenchmarkDotNet.Artifacts\results\ + + Directory: C:\Users\vlza\code\fsharp\tests\benchmarks\FCSBenchmarks\FcsBench\BenchmarkDotNet.Artifacts\results + + Mode LastWriteTime Length Name + ---- ------------- ------ ---- + -a--- 4/25/2022 1:42 PM 638 Program.CompilerService-report-github.md + -a--- 4/25/2022 1:42 PM 1050 Program.CompilerService-report.csv + -a--- 4/25/2022 1:42 PM 1169 Program.CompilerService-report.html + ``` + + *-report-github.md can be used to post benchmark results to GitHub issue/PR/discussion or RFC. + + *-report.csv can be used for comparison purposes. + + **Example output:** + + ``` ini + + BenchmarkDotNet=v0.13.1, OS=Windows 10.0.25102 + Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores + .NET SDK=6.0.200 + [Host] : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT DEBUG + Job-GDIBXX : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT + + InvocationCount=1 UnrollFactor=1 + + ``` + + | Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Allocated | + |-------------------------- |---------:|--------:|--------:|---------:|----------:|----------:|----------:| + | ParsingCheckExpressionsFs | 199.4 ms | 3.84 ms | 9.78 ms | 195.5 ms | 4000.0000 | 1000.0000 | 28 MB | + +8. Repeat for any number of changes you would like to test. +9. **Optionally:** benchmark code and results can be included as part of the PR for future reference. -You can find this document under 'tests/benchmarks/README.md'. diff --git a/tests/benchmarks/SmokeTestBenchmarks.sh b/tests/benchmarks/SmokeTestBenchmarks.sh new file mode 100644 index 00000000000..0da2c8f2213 --- /dev/null +++ b/tests/benchmarks/SmokeTestBenchmarks.sh @@ -0,0 +1,18 @@ +# Smoke test for checking that some (faster) benchmarks work. +# The test is successful if all the benchmarks run and produce results. +# The actual numbers produced aren't accurate. + +run() { + local path=$1 + dotnet run \ + --project $path \ + -c release \ + --no-build \ + --job Dry \ + --allCategories short \ + --stopOnFirstError +} + +run "FCSBenchmarks/BenchmarkComparison/HistoricalBenchmark.fsproj" +run "FCSBenchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj" +run "FCSBenchmarks/FCSSourceFiles/FCSSourceFiles.fsproj" diff --git a/tests/fsharp/Compiler/Language/StructActivePatternTests.fs b/tests/fsharp/Compiler/Language/StructActivePatternTests.fs index 8780e6c8469..c1e71755e1b 100644 --- a/tests/fsharp/Compiler/Language/StructActivePatternTests.fs +++ b/tests/fsharp/Compiler/Language/StructActivePatternTests.fs @@ -179,11 +179,8 @@ let (|Foo|_|) x = ValueNone """ [|(FSharpDiagnosticSeverity.Error, 842, (2, 3, 2, 9), "This attribute is not valid for use on this language element"); - (FSharpDiagnosticSeverity.Error, 1, (2, 1, 3, 16), - "This expression was expected to have type - ''a option' -but here has type - ''b voption' ")|] + (FSharpDiagnosticSeverity.Error, 3350, (2, 1, 3, 16), + "Feature 'Boolean-returning and return-type-directed partial active patterns' is not available in F# 8.0. Please use language version 'PREVIEW' or greater.")|] [] let ``StructAttribute not allowed on other bindings than partial active pattern definitions`` () = diff --git a/tests/service/CSharpProjectAnalysis.fs b/tests/service/CSharpProjectAnalysis.fs index f23f3038e42..69a13799d7f 100644 --- a/tests/service/CSharpProjectAnalysis.fs +++ b/tests/service/CSharpProjectAnalysis.fs @@ -27,7 +27,7 @@ let internal getProjectReferences (content: string, dllFiles, libDirs, otherFlag let projFileName = Path.ChangeExtension(base1, ".fsproj") FileSystem.OpenFileForWriteShim(fileName1).Write(content) let options = - checker.GetProjectOptionsFromCommandLineArgs(projFileName, + { checker.GetProjectOptionsFromCommandLineArgs(projFileName, [| yield "--debug:full" yield "--define:DEBUG" yield "--optimize-" @@ -41,8 +41,7 @@ let internal getProjectReferences (content: string, dllFiles, libDirs, otherFlag yield "-r:"+dllFile for libDir in libDirs do yield "-I:"+libDir - yield! otherFlags - yield fileName1 |]) + yield! otherFlags |]) with SourceFiles = [| fileName1 |] } let results = checker.ParseAndCheckProject(options) |> Async.RunImmediate if results.HasCriticalErrors then let builder = System.Text.StringBuilder() diff --git a/tests/service/MultiProjectAnalysisTests.fs b/tests/service/MultiProjectAnalysisTests.fs index a306107eb36..350cf73095e 100644 --- a/tests/service/MultiProjectAnalysisTests.fs +++ b/tests/service/MultiProjectAnalysisTests.fs @@ -133,8 +133,12 @@ let u = Case1 3 let cleanFileName a = if a = fileName1 then "file1" else "??" [] -let ``Test multi project 1 basic`` () = +[] +[] +let ``Test multi project 1 basic`` useTransparentCompiler = + let checker = if useTransparentCompiler then transparentCompilerChecker else checker + let wholeProjectResults = checker.ParseAndCheckProject(MultiProject1.options) |> Async.RunImmediate [ for x in wholeProjectResults.AssemblySignature.Entities -> x.DisplayName ] |> shouldEqual ["MultiProject1"] @@ -704,9 +708,12 @@ let ``Test multi project2 errors`` useTransparentCompiler = wholeProjectResultsC.Diagnostics.Length |> shouldEqual 1 - [] -let ``Test multi project 2 all symbols`` () = +[] +[] +let ``Test multi project 2 all symbols`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let mpA = checker.ParseAndCheckProject(Project2A.options) |> Async.RunImmediate let mpB = checker.ParseAndCheckProject(Project2B.options) |> Async.RunImmediate @@ -832,7 +839,12 @@ let ``Test active patterns' XmlDocSig declared in referenced projects`` useTrans [] -let ``In-memory cross-project references to projects using generative type provides should fallback to on-disk references`` () = +[] +[] +let ``In-memory cross-project references to projects using generative type provides should fallback to on-disk references`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker + // The type provider and its dependency are compiled as part of the solution build #if DEBUG let csDLL = __SOURCE_DIRECTORY__ + @"/../../artifacts/bin/TestTP/Debug/netstandard2.0/CSharp_Analysis.dll" diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 6e3de8133f1..154517ed6a1 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -58,6 +58,7 @@ module internal FSharpProjectSnapshotSerialization = | FSharpReference(projectOutputFile, snapshot) -> output.Add("projectOutputFile", projectOutputFile) output.Add("snapshot", serializeSnapshot snapshot) + | _ -> () output