diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5299179bd03..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,132 +0,0 @@ -## Contribution Guidelines - -The Visual F# team is proud to be a contributor to F#, and urge you to join in too. F# users and the F# community are grateful for all contributions to F#. - -Besides this overview, we recommend ["A journey into the F~ compiler"](https://skillsmatter.com/skillscasts/11629-a-journey-into-the-f-sharp-compiler/), a talk by Steffen Forkmann. -For those contributing to the core of the F# compiler, we recommend ["The F# Compiler Technical Overview"](http://fsharp.github.io/2015/09/29/fsharp-compiler-guide.html) - -### Getting Started - -- Install required software -- Clone the repo \ - `git clone https://github.com/microsoft/visualfsharp.git` -- Read how to build in [DEVGUIDE.md](DEVGUIDE.md) -- Read how to run tests in [TESTGUIDE.md](TESTGUIDE.md) - -### What to Contribute? - -There are several important ways that you can contribute. We are especially grateful for early feedback on in-development features, bug reports with repro steps, bug fixes with regression test cases, cross-platform expertise and changes, documentation updates, feature tests, suggestions, comments, and ideas. - -We initially solicit contributions for - -- compiler optimizations -- compiler performance improvements -- code generation improvements -- bug fixes (see the [issues list](https://github.com/microsoft/visualfsharp/issues)) -- library improvements -- F# language and library features - -New features are welcome, but be aware that Visual F# is a high-quality programming language with high-quality tools, and we wish to keep it that way. Before embarking on an extensive feature implementation, make a proposal in a GitHub issue or on the [F# Language Suggestions](https://github.com/fsharp/fslang-suggestions) so the community can review and comment on it. - -### Issues - -When submitting issues, please use the following guidelines - -- Suggestions for the F# Language and Core library should be added and reviewed at the [F# Language Suggestions](https://github.com/fsharp/fslang-suggestions). - -- Suggestions for the Visual F# Tools should be added and reviewed at the [Visual Studio F# Tools GitHub](https://github.com/microsoft/visualfsharp). - -- New Bug Reports should always give accurate, clear steps for reproducing the bug, and all relevant details about versions, platform, etc. We suggest the following template: - - Title: <a short, clear title> - - Description: <a description of the problem> - - Repro Steps: <step by step description> - - Expected: <what is expected> - - Actual: <what you really get> - - Severity: a description on how bad it is and why - is it blocking? - - Version: Language, compiler, library, platform version - - Link: Give a link to a ZIP, log files or other files if needed - - Likely Cause: Link to the place in the code where things are likely going wrong, if possible - - Workaround: List any known workarounds - -### CLA - -Contributors are required to sign a [Contribution License Agreement](https://cla.microsoft.com/) (CLA) before any pull requests will be considered. After submitting a request via the provided form, electronically sign the CLA when you receive the email containing the link to the document. This only needs to be done once for each Microsoft OSS project you contribute to. - -### Quality and Testing - -Contributions to this repository will be rigorously policed for quality. - -All code submissions should be submitted with regression test cases, and will be subject to peer review by the community and Microsoft. The bar for contributions will be high. This will result in a higher-quality, more stable product. - -- We expect contributors to be actively involved in quality assurance. -- Partial, incomplete, or poorly-tested contributions will not be accepted. -- Contributions may be put on hold according to stability, testing, and design-coherence requirements. - -#### Mimimum Bar for Code Cleanup Pull Requests - -In addition to the above, "Code Cleanup" pull requests have the following minimum requirements: - -- There must be no chance of a behavioural change, performance degradation or regression under any reasonable reading of the code in the context of the codebase as a whole. - -- Code cleanup which is unrelated to a bug fix or feature should generally be made separate to other checkins where possible. -- Code cleanup is much more likely to be accepted towards the start of a release cycle. - -#### Mimimum Bar for Performance Improvement Pull Requests - -Performance improvement checkins have the following minimum requirements (in addition to the above) - -- Performance tests and figures must be given, either in the PR or in the notes associated with the PR. PRs without performance figures will be closed with a polite request to please add them. - -- The PR must show a reliable, substantive performance improvement that justifies the complexity introduced. For the compiler, performance improvements of ~1% are of interest. For the core library, it will depend on the routine in question. For the Visual F# tools, reactivity of the user interface will be of more interest than raw CPU performance. - -- Performance improvements should not cause performance degradation in existing code. - -#### Mimimum Bar for Bug Fix Pull Requests - -Bug fix PRs have the following minimum requirements - -- There must be a separate tracking bug entry in the public GitHub issues. A link should be given in the PR. PRs without a matching bug link will be closed with a polite request to please add it. - -- The code changes must be reasonably minimal and as low-churn, non-intrusive as possible. Unrelated cleanup should be done in separate PRs (see above), and fixes should be as small as possible. Code cleanup that is part of making a clear and accurate fix is acceptable as part of a bug fix, but care should be taken that it doesn't obscure the fix itself. For example, renaming identifiers to be clearer in a way that would have avoided the original bug is acceptable, but care must still be taken that the actual fix is still apparent and reviewable in the overall diff for the fix. - -- Thorough test cases must be included in the PR (unless tests already exist for a failing case). PRs without matching tests will be closed with a polite request to please add the tests. However, if you need help adding tests, please note this in the description of the change and people will guide you through where to add the tests. - -- Bug fix PRs should not cause performance degradation in existing code. - -#### Mimimum Bar for Feature Pull Requests - -Feature PRs have the following minimum requirements: - -- For F# Language and Library features, include a link to the [F# Language Suggestions](https://github.com/fsharp/fslang-suggestions) issue - -- For Visual F# Tools features, include a link to the [Visual F# Tools](https://github.com/microsoft/visualfsharp) issue for the feature. - -- For F# Library features, if you have made additions to the FSharp.Core library public surface area, update [the SurfaceArea tests](https://github.com/Microsoft/visualfsharp/tree/fsharp4/tests/FSharp.Core.UnitTests). - -- For F# Language and Library features, you will be asked to submit a speclet for the feature to the [F# Language Design](https://github.com/fsharp/fslang-design) GitHub repository of speclets. In some cases you will only need to do this after your feature is accepted, but for more complex features you may be asked to do this during the review of the feature. - -- Language feature implementations must take into account the expectations of typical users about the performance - impact of using the feature. For example, we should avoid the situation where using an optional language feature - which appears benign to a typical user has a large negative performance impact on code. - -- Language feature implementations should not cause performance degradation in existing code. - -### Language Evolution - -We are committed to carefully managing the evolution of the F# language. - -We actively solicit contributions related to the F# language design, but the process for handling these differs substantially from other kinds of contributions. Significant language and library change should be suggested and reviewed at the [F# Language Suggestions](https://github.com/fsharp/fslang-suggestions) repository. - -### Coding guidelines - -Although there is currently no strict set of coding or style guidelines, use common sense when contributing code - make an effort to use a similar style to nearby existing code. If you have a passion for helping us develop a set of coding guidelines that we can roll out and apply within this project, get involved and start a discussion issue. diff --git a/DEVGUIDE.md b/DEVGUIDE.md index ec1b03afe74..f9ebde61e5d 100644 --- a/DEVGUIDE.md +++ b/DEVGUIDE.md @@ -4,7 +4,7 @@ Get the latest source code from the master branch by running this git command: - git clone https://github.com/Microsoft/visualfsharp.git + git clone https://github.com/dotnet/fsharp.git Before running the build scripts, ensure that you have cleaned up the visualfsharp repo by running this git command: @@ -60,7 +60,7 @@ For Linux/Mac: Running tests: - ./build.sh -test + ./build.sh --test ### Developing the Visual F# IDE Tools (Windows Only) diff --git a/FSharp.sln b/FSharp.sln index c34abdba39a..e1c47795e8d 100644 --- a/FSharp.sln +++ b/FSharp.sln @@ -16,9 +16,6 @@ EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.Interactive.Settings", "src\fsharp\FSharp.Compiler.Interactive.Settings\FSharp.Compiler.Interactive.Settings.fsproj", "{649FA588-F02E-457C-9FCF-87E46407481E}" EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fsi", "src\fsharp\fsi\fsi.fsproj", "{D0E98C0D-490B-4C61-9329-0862F6E87645}" - ProjectSection(ProjectDependencies) = postProject - {649FA588-F02E-457C-9FCF-87E46407481E} = {649FA588-F02E-457C-9FCF-87E46407481E} - EndProjectSection EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpSuite.Tests", "tests\fsharp\FSharpSuite.Tests.fsproj", "{C163E892-5BF7-4B59-AA99-B0E8079C67C4}" EndProject diff --git a/FSharpBuild.Directory.Build.props b/FSharpBuild.Directory.Build.props index a1ba7ab156d..515de9bbdc7 100644 --- a/FSharpBuild.Directory.Build.props +++ b/FSharpBuild.Directory.Build.props @@ -11,8 +11,7 @@ $(RepoRoot)src $(ArtifactsDir)\SymStore - $(ArtifactsDir)\Bootstrap - $(ArtifactsDir)/fsc/Proto/netcoreapp2.1 + $(ArtifactsDir)\Bootstrap 4.4.0 1182;0025;$(WarningsAsErrors) @@ -96,10 +95,10 @@ - $(ProtoOutputPath)\Microsoft.FSharp.Targets - $(ProtoOutputPath)\Microsoft.FSharp.NetSdk.props - $(ProtoOutputPath)\Microsoft.FSharp.NetSdk.targets - $(ProtoOutputPath)\Microsoft.FSharp.Overrides.NetSdk.targets + $(ProtoOutputPath)\fsc\Microsoft.FSharp.Targets + $(ProtoOutputPath)\fsc\Microsoft.FSharp.NetSdk.props + $(ProtoOutputPath)\fsc\Microsoft.FSharp.NetSdk.targets + $(ProtoOutputPath)\fsc\Microsoft.FSharp.Overrides.NetSdk.targets diff --git a/FSharpBuild.Directory.Build.targets b/FSharpBuild.Directory.Build.targets index d87d68d36c7..7548cef7acf 100644 --- a/FSharpBuild.Directory.Build.targets +++ b/FSharpBuild.Directory.Build.targets @@ -5,29 +5,30 @@ - + - <__TargetFilePath>@(CopyAndSubstituteText->'$(IntermediateOutputPath)%(Filename)%(Extension)') - <__TargetFileName>@(CopyAndSubstituteText->'%(Filename)%(Extension)') + <__TargetFilePath>@(NoneSubstituteText->'$(IntermediateOutputPath)%(Filename)%(Extension)') + <__TargetFileName>@(NoneSubstituteText->'%(Filename)%(Extension)') - <_ReplacementText>$([System.IO.File]::ReadAllText('%(CopyAndSubstituteText.FullPath)')) - <_ReplacementText Condition="'%(CopyAndSubstituteText.Pattern1)' != ''">$(_ReplacementText.Replace('%(CopyAndSubstituteText.Pattern1)', '%(CopyAndSubstituteText.Replacement1)')) - <_ReplacementText Condition="'%(CopyAndSubstituteText.Pattern2)' != ''">$(_ReplacementText.Replace('%(CopyAndSubstituteText.Pattern2)', '%(CopyAndSubstituteText.Replacement2)')) + <_ReplacementText>$([System.IO.File]::ReadAllText('%(NoneSubstituteText.FullPath)')) + <_ReplacementText Condition="'%(NoneSubstituteText.Pattern1)' != ''">$(_ReplacementText.Replace('%(NoneSubstituteText.Pattern1)', '%(NoneSubstituteText.Replacement1)')) + <_ReplacementText Condition="'%(NoneSubstituteText.Pattern2)' != ''">$(_ReplacementText.Replace('%(NoneSubstituteText.Pattern2)', '%(NoneSubstituteText.Replacement2)')) + + <_CopyToOutputDirectory Condition="'%(NoneSubstituteText.CopyToOutputDirectory)' != ''">%(NoneSubstituteText.CopyToOutputDirectory) + <_CopyToOutputDirectory Condition="'%(NoneSubstituteText.CopyToOutputDirectory)' == ''">Never - + - - + diff --git a/FSharpTests.Directory.Build.props b/FSharpTests.Directory.Build.props index 7c00805dda5..8a7a832a43e 100644 --- a/FSharpTests.Directory.Build.props +++ b/FSharpTests.Directory.Build.props @@ -32,9 +32,9 @@ - <_FSharpBuildTargetFramework Condition="'$(FSharpTestCompilerVersion)' == 'net40'">net472 - <_FSharpBuildTargetFramework Condition="'$(FSharpTestCompilerVersion)' == 'coreclr'">netcoreapp2.1 - <_FSharpBuildBinPath>$(MSBuildThisFileDirectory)artifacts\bin\FSharp.Build\$(Configuration)\$(_FSharpBuildTargetFramework) + <_FSharpBuildTargetFramework Condition="'$(MSBuildRuntimeType)'!='Core'">net472 + <_FSharpBuildTargetFramework Condition="'$(MSBuildRuntimeType)'=='Core'">netcoreapp2.1 + <_FSharpBuildBinPath>$(MSBuildThisFileDirectory)artifacts\bin\fsc\$(Configuration)\$(_FSharpBuildTargetFramework) $(_FSharpBuildBinPath)\FSharp.Build.dll diff --git a/TESTGUIDE.md b/TESTGUIDE.md index 80f51322d7e..ef34e6f51bb 100644 --- a/TESTGUIDE.md +++ b/TESTGUIDE.md @@ -10,7 +10,7 @@ To run tests, use variations such as the following, depending on which test suit build.cmd vs test build.cmd all test -You can also submit pull requests to http://github.com/Microsoft/visualfsharp and run the tests via continuous integration. Most people do wholesale testing that way. +You can also submit pull requests to https://github.com/dotnet/fsharp and run the tests via continuous integration. Most people do wholesale testing that way. ## Prerequisites diff --git a/VisualFSharp.sln b/VisualFSharp.sln index 3df52bb4731..4a1e75f446a 100644 --- a/VisualFSharp.sln +++ b/VisualFSharp.sln @@ -158,6 +158,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageSer EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.LanguageServer.UnitTests", "tests\FSharp.Compiler.LanguageServer.UnitTests\FSharp.Compiler.LanguageServer.UnitTests.fsproj", "{AAF2D233-1C38-4090-8FFA-F7C545625E06}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Editor.Helpers", "vsintegration\src\FSharp.Editor.Helpers\FSharp.Editor.Helpers.csproj", "{79255A92-ED00-40BA-9D64-12FCC664A976}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -912,6 +914,18 @@ Global {AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|Any CPU.Build.0 = Release|Any CPU {AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.ActiveCfg = Release|Any CPU {AAF2D233-1C38-4090-8FFA-F7C545625E06}.Release|x86.Build.0 = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.ActiveCfg = Debug|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Debug|x86.Build.0 = Debug|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.ActiveCfg = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|Any CPU.Build.0 = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.ActiveCfg = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Proto|x86.Build.0 = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Release|Any CPU.Build.0 = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.ActiveCfg = Release|Any CPU + {79255A92-ED00-40BA-9D64-12FCC664A976}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -986,6 +1000,7 @@ Global {8EC30B2E-F1F9-4A98-BBB5-DD0CF6C84DDC} = {647810D0-5307-448F-99A2-E83917010DAE} {60BAFFA5-6631-4328-B044-2E012AB76DCA} = {B8DDA694-7939-42E3-95E5-265C2217C142} {AAF2D233-1C38-4090-8FFA-F7C545625E06} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449} + {79255A92-ED00-40BA-9D64-12FCC664A976} = {4C7B48D7-19AF-4AE7-9D1D-3BB289D5480D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37} diff --git a/eng/Build.ps1 b/eng/Build.ps1 index a296764692d..54e6ff5e2b7 100644 --- a/eng/Build.ps1 +++ b/eng/Build.ps1 @@ -68,6 +68,7 @@ function Print-Usage() { Write-Host "" Write-Host "Actions:" Write-Host " -restore Restore packages (short: -r)" + Write-Host " -norestore Don't restore packages" Write-Host " -build Build main solution (short: -b)" Write-Host " -rebuild Rebuild main solution" Write-Host " -pack Build NuGet packages, VS insertion manifests and installer" @@ -106,6 +107,7 @@ function Process-Arguments() { Print-Usage exit 0 } + $script:nodeReuse = $False; if ($testAll) { $script:testDesktop = $True @@ -143,7 +145,7 @@ function Process-Arguments() { } function Update-Arguments() { - if (-Not (Test-Path "$ArtifactsDir\Bootstrap\fsc.exe")) { + if (-Not (Test-Path "$ArtifactsDir\Bootstrap\fsc\fsc.exe")) { $script:bootstrap = $True } } @@ -177,7 +179,6 @@ function BuildSolution() { /p:Publish=$publish ` /p:ContinuousIntegrationBuild=$ci ` /p:OfficialBuildId=$officialBuildId ` - /p:BootstrapBuildPath=$bootstrapDir ` /p:QuietRestore=$quietRestore ` /p:QuietRestoreBinaryLog=$binaryLog ` /p:TestTargetFrameworks=$testTargetFrameworks ` @@ -211,7 +212,7 @@ function UpdatePath() { } function VerifyAssemblyVersions() { - $fsiPath = Join-Path $ArtifactsDir "bin\fsi\Proto\net472\fsi.exe" + $fsiPath = Join-Path $ArtifactsDir "bin\fsi\Proto\net472\publish\fsi.exe" # Only verify versions on CI or official build if ($ci -or $official) { diff --git a/eng/Signing.props b/eng/Signing.props new file mode 100644 index 00000000000..da17d32fa4d --- /dev/null +++ b/eng/Signing.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index dcd15fa1e8b..3f53c799b29 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,9 +3,9 @@ - + https://github.com/dotnet/arcade - 670f6ee1a619a2a7c84cfdfe2a1c84fbe94e1c6b + b21c24996a73aa62b7a1ee69f546b9d2eb084f29 diff --git a/eng/Versions.props b/eng/Versions.props index d288bf98a9a..3fc7a08c130 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -92,7 +92,7 @@ 4.3.0 4.3.0 4.3.0 - 4.4.0 + 4.5.0 $(RoslynVersion) $(RoslynVersion) @@ -115,6 +115,7 @@ 16.0.467 16.0.28727 16.0.28729 + 16.1.3121 16.0.467 16.0.467 16.0.467 @@ -177,4 +178,4 @@ 5.22.2.1 2.0.187 - \ No newline at end of file + diff --git a/eng/build-utils.ps1 b/eng/build-utils.ps1 index d1e5dd85d55..335379b2f73 100644 --- a/eng/build-utils.ps1 +++ b/eng/build-utils.ps1 @@ -178,7 +178,7 @@ function Get-PackageDir([string]$name, [string]$version = "") { return $p } -function Run-MSBuild([string]$projectFilePath, [string]$buildArgs = "", [string]$logFileName = "", [switch]$parallel = $true, [switch]$summary = $true, [switch]$warnAsError = $true, [string]$configuration = $script:configuration) { +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 # 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. @@ -216,10 +216,6 @@ function Run-MSBuild([string]$projectFilePath, [string]$buildArgs = "", [string] $args += " /p:ContinuousIntegrationBuild=true" } - if ($bootstrapDir -ne "") { - $args += " /p:BootstrapBuildPath=$bootstrapDir" - } - $args += " $buildArgs" $args += " $projectFilePath" $args += " $properties" @@ -241,15 +237,15 @@ function Make-BootstrapBuild() { Create-Directory $dir # prepare FsLex and Fsyacc - Run-MSBuild "$RepoRoot\src\buildtools\buildtools.proj" "/restore /t:Build" -logFileName "BuildTools" -configuration $bootstrapConfiguration - Copy-Item "$ArtifactsDir\bin\fslex\$bootstrapConfiguration\netcoreapp2.1\*" -Destination $dir - Copy-Item "$ArtifactsDir\bin\fsyacc\$bootstrapConfiguration\netcoreapp2.1\*" -Destination $dir + Run-MSBuild "$RepoRoot\src\buildtools\buildtools.proj" "/restore /t:Publish" -logFileName "BuildTools" -configuration $bootstrapConfiguration + Copy-Item "$ArtifactsDir\bin\fslex\$bootstrapConfiguration\netcoreapp2.1\publish" -Destination "$dir\fslex" -Force -Recurse + Copy-Item "$ArtifactsDir\bin\fsyacc\$bootstrapConfiguration\netcoreapp2.1\publish" -Destination "$dir\fsyacc" -Force -Recurse # prepare compiler $projectPath = "$RepoRoot\proto.proj" - Run-MSBuild $projectPath "/restore /t:Build" -logFileName "Bootstrap" -configuration $bootstrapConfiguration - Copy-Item "$ArtifactsDir\bin\fsc\$bootstrapConfiguration\$bootstrapTfm\*" -Destination $dir - Copy-Item "$ArtifactsDir\bin\fsi\$bootstrapConfiguration\$bootstrapTfm\*" -Destination $dir + Run-MSBuild $projectPath "/restore /t:Publish" -logFileName "Bootstrap" -configuration $bootstrapConfiguration + Copy-Item "$ArtifactsDir\bin\fsc\$bootstrapConfiguration\$bootstrapTfm\publish" -Destination "$dir\fsc" -Force -Recurse + Copy-Item "$ArtifactsDir\bin\fsi\$bootstrapConfiguration\$bootstrapTfm\publish" -Destination "$dir\fsi" -Force -Recurse return $dir } diff --git a/eng/build.sh b/eng/build.sh index 8ce74bfa523..fceb485349a 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -13,7 +13,9 @@ usage() echo " --binaryLog Create MSBuild binary log (short: -bl)" echo "" echo "Actions:" + echo " --bootstrap Force the build of the bootstrap compiler" echo " --restore Restore projects required to build (short: -r)" + echo " --norestore Don't restore projects required to build" echo " --build Build all projects (short: -b)" echo " --rebuild Rebuild all projects" echo " --pack Build nuget packages" @@ -54,6 +56,7 @@ test_core_clr=false configuration="Debug" verbosity='minimal' binary_log=false +force_bootstrap=false ci=false skip_analyzers=false prepare_machine=false @@ -88,6 +91,9 @@ while [[ $# > 0 ]]; do --binarylog|-bl) binary_log=true ;; + --bootstrap) + force_bootstrap=true + ;; --restore|-r) restore=true ;; @@ -170,7 +176,11 @@ function TestUsingNUnit() { projectname="${projectname%.*}" testlogpath="$artifacts_dir/TestResults/$configuration/${projectname}_$targetframework.xml" args="test \"$testproject\" --no-restore --no-build -c $configuration -f $targetframework --test-adapter-path . --logger \"nunit;LogFilePath=$testlogpath\"" - "$DOTNET_INSTALL_DIR/dotnet" $args + "$DOTNET_INSTALL_DIR/dotnet" $args || { + local exit_code=$? + echo "dotnet test failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code + } } function BuildSolution { @@ -205,17 +215,33 @@ function BuildSolution { quiet_restore=true fi + # Node reuse fails because multiple different versions of FSharp.Build.dll get loaded into MSBuild nodes + node_reuse=false + # build bootstrap tools bootstrap_config=Proto - MSBuild "$repo_root/src/buildtools/buildtools.proj" \ - /restore \ - /p:Configuration=$bootstrap_config \ - /t:Build - bootstrap_dir=$artifacts_dir/Bootstrap - mkdir -p "$bootstrap_dir" - cp $artifacts_dir/bin/fslex/$bootstrap_config/netcoreapp2.1/* $bootstrap_dir - cp $artifacts_dir/bin/fsyacc/$bootstrap_config/netcoreapp2.1/* $bootstrap_dir + if [[ "$force_bootstrap" == true ]]; then + rm -fr $bootstrap_dir + fi + if [ ! -f "$bootstrap_dir/fslex.dll" ]; then + MSBuild "$repo_root/src/buildtools/buildtools.proj" \ + /restore \ + /p:Configuration=$bootstrap_config \ + /t:Publish + + mkdir -p "$bootstrap_dir" + cp -pr $artifacts_dir/bin/fslex/$bootstrap_config/netcoreapp2.1/publish $bootstrap_dir/fslex + cp -pr $artifacts_dir/bin/fsyacc/$bootstrap_config/netcoreapp2.1/publish $bootstrap_dir/fsyacc + fi + if [ ! -f "$bootstrap_dir/fsc.exe" ]; then + MSBuild "$repo_root/proto.proj" \ + /restore \ + /p:Configuration=$bootstrap_config \ + /t:Publish + + cp -pr $artifacts_dir/bin/fsc/$bootstrap_config/netcoreapp2.1/publish $bootstrap_dir/fsc + fi # do real build MSBuild $toolset_build_proj \ diff --git a/eng/common/PSScriptAnalyzerSettings.psd1 b/eng/common/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 00000000000..4c1ea7c98ea --- /dev/null +++ b/eng/common/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,11 @@ +@{ + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidUsingInvokeExpression', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUseCmdletCorrectly', + 'PSStandardDSCFunctionsInResource', + 'PSUseIdenticalMandatoryParametersForDSC', + 'PSUseIdenticalParametersForDSC') +} \ No newline at end of file diff --git a/eng/common/PublishToPackageFeed.proj b/eng/common/PublishToPackageFeed.proj index 9120b2d2129..a1b1333723e 100644 --- a/eng/common/PublishToPackageFeed.proj +++ b/eng/common/PublishToPackageFeed.proj @@ -54,6 +54,7 @@ https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json https://dotnetfeed.blob.core.windows.net/aspnet-entityframework6/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" Write-Host " -binaryLog Output binary log (short: -bl)" Write-Host " -help Print help and exit" @@ -77,6 +79,7 @@ function Build { InitializeCustomToolset $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "Build.binlog") } else { "" } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { "" } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -88,6 +91,7 @@ function Build { MSBuild $toolsetBuildProj ` $bl ` + $platformArg ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` @@ -129,9 +133,8 @@ try { Build } catch { - Write-Host $_ - Write-Host $_.Exception Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/build.sh b/eng/common/build.sh index ce846d888df..6236fc4d38c 100644 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -66,6 +66,7 @@ ci=false warn_as_error=true node_reuse=true binary_log=false +pipelines_log=false projects='' configuration='Debug' @@ -92,6 +93,9 @@ while [[ $# > 0 ]]; do -binarylog|-bl) binary_log=true ;; + -pipelineslog|-pl) + pipelines_log=true + ;; -restore|-r) restore=true ;; @@ -146,6 +150,7 @@ while [[ $# > 0 ]]; do done if [[ "$ci" == true ]]; then + pipelines_log=true binary_log=true node_reuse=false fi diff --git a/eng/common/cross/armel/tizen-fetch.sh b/eng/common/cross/armel/tizen-fetch.sh index ba16e991c7e..ed70e0a86eb 100644 --- a/eng/common/cross/armel/tizen-fetch.sh +++ b/eng/common/cross/armel/tizen-fetch.sh @@ -157,15 +157,15 @@ fetch_tizen_pkgs() Inform "Initialize arm base" fetch_tizen_pkgs_init standard base Inform "fetch common packages" -fetch_tizen_pkgs armv7l gcc glibc glibc-devel libicu libicu-devel +fetch_tizen_pkgs armv7l gcc glibc glibc-devel libicu libicu-devel libatomic fetch_tizen_pkgs noarch linux-glibc-devel Inform "fetch coreclr packages" -fetch_tizen_pkgs armv7l lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel tizen-release lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +fetch_tizen_pkgs armv7l lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu Inform "fetch corefx packages" fetch_tizen_pkgs armv7l libcom_err libcom_err-devel zlib zlib-devel libopenssl libopenssl-devel krb5 krb5-devel libcurl libcurl-devel Inform "Initialize standard unified" fetch_tizen_pkgs_init standard unified Inform "fetch corefx packages" -fetch_tizen_pkgs armv7l gssdp gssdp-devel +fetch_tizen_pkgs armv7l gssdp gssdp-devel tizen-release diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 34d3d2ba1fe..7c4e122651c 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -181,7 +181,7 @@ if [ -z "$__RootfsDir" ] && [ ! -z "$ROOTFS_DIR" ]; then fi if [ -z "$__RootfsDir" ]; then - __RootfsDir="$__CrossDir/rootfs/$__BuildArch" + __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" fi if [ -d "$__RootfsDir" ]; then diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index dea7cdd903b..8854d979f37 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -11,10 +11,10 @@ function InstallDarcCli ($darcVersion) { $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" - $toolList = Invoke-Expression "& `"$dotnet`" tool list -g" + $toolList = & "$dotnet" tool list -g if ($toolList -like "*$darcCliPackageName*") { - Invoke-Expression "& `"$dotnet`" tool uninstall $darcCliPackageName -g" + & "$dotnet" tool uninstall $darcCliPackageName -g } # If the user didn't explicitly specify the darc version, @@ -22,12 +22,12 @@ function InstallDarcCli ($darcVersion) { if (-not $darcVersion) { $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content } - + $arcadeServicesSource = 'https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json' Write-Host "Installing Darc CLI version $darcVersion..." Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." - Invoke-Expression "& `"$dotnet`" tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g } InstallDarcCli $darcVersion diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 index 5987943fd6f..0b629b8301a 100644 --- a/eng/common/dotnet-install.ps1 +++ b/eng/common/dotnet-install.ps1 @@ -8,9 +8,14 @@ Param( . $PSScriptRoot\tools.ps1 +$dotnetRoot = Join-Path $RepoRoot ".dotnet" + +$installdir = $dotnetRoot try { - $dotnetRoot = Join-Path $RepoRoot ".dotnet" - InstallDotNet $dotnetRoot $version $architecture $runtime $true + if ($architecture -and $architecture.Trim() -eq "x86") { + $installdir = Join-Path $installdir "x86" + } + InstallDotNet $installdir $version $architecture $runtime $true } catch { Write-Host $_ @@ -19,4 +24,4 @@ catch { ExitWithExitCode 1 } -ExitWithExitCode 0 \ No newline at end of file +ExitWithExitCode 0 diff --git a/eng/common/generate-graph-files.ps1 b/eng/common/generate-graph-files.ps1 index a05b84f7987..b056e4c1ac2 100644 --- a/eng/common/generate-graph-files.ps1 +++ b/eng/common/generate-graph-files.ps1 @@ -25,7 +25,7 @@ function CheckExitCode ([string]$stage) try { Push-Location $PSScriptRoot - + Write-Host "Installing darc..." . .\darc-init.ps1 -darcVersion $darcVersion CheckExitCode "Running darc-init" @@ -40,9 +40,9 @@ try { $darcExe = "$env:USERPROFILE\.dotnet\tools" $darcExe = Resolve-Path "$darcExe\darc.exe" - + Create-Directory $outputFolder - + # Generate 3 graph descriptions: # 1. Flat with coherency information # 2. Graphviz (dot) file @@ -51,26 +51,26 @@ try { $graphVizImageFilePath = "$outputFolder\graph.png" $normalGraphFilePath = "$outputFolder\graph-full.txt" $flatGraphFilePath = "$outputFolder\graph-flat.txt" - $baseOptions = "get-dependency-graph --github-pat $gitHubPat --azdev-pat $azdoPat --password $barToken" - + $baseOptions = @( "--github-pat", "$gitHubPat", "--azdev-pat", "$azdoPat", "--password", "$barToken" ) + if ($includeToolset) { Write-Host "Toolsets will be included in the graph..." - $baseOptions += " --include-toolset" + $baseOptions += @( "--include-toolset" ) } Write-Host "Generating standard dependency graph..." - Invoke-Expression "& `"$darcExe`" $baseOptions --output-file $normalGraphFilePath" + & "$darcExe" get-dependency-graph @baseOptions --output-file $normalGraphFilePath CheckExitCode "Generating normal dependency graph" Write-Host "Generating flat dependency graph and graphviz file..." - Invoke-Expression "& `"$darcExe`" $baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath" + & "$darcExe" get-dependency-graph @baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath CheckExitCode "Generating flat and graphviz dependency graph" Write-Host "Generating graph image $graphVizFilePath" $dotFilePath = Join-Path $installBin "graphviz\$graphvizVersion\release\bin\dot.exe" - Invoke-Expression "& `"$dotFilePath`" -Tpng -o'$graphVizImageFilePath' `"$graphVizFilePath`"" + & "$dotFilePath" -Tpng -o"$graphVizImageFilePath" "$graphVizFilePath" CheckExitCode "Generating graphviz image" - + Write-Host "'$graphVizFilePath', '$flatGraphFilePath', '$normalGraphFilePath' and '$graphVizImageFilePath' created!" } catch { diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index a4306bd37e1..9d18645f455 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -79,28 +79,27 @@ try { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value - $LocalInstallerCommand = $InstallerPath - $LocalInstallerCommand += " -ToolName $ToolName" - $LocalInstallerCommand += " -InstallPath $InstallBin" - $LocalInstallerCommand += " -BaseUri $BaseUri" - $LocalInstallerCommand += " -CommonLibraryDirectory $EngCommonBaseDir" - $LocalInstallerCommand += " -Version $ToolVersion" + $LocalInstallerArguments = @{ ToolName = "$ToolName" } + $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } + $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } + $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } + $LocalInstallerArguments += @{ Version = "$ToolVersion" } if ($Verbose) { - $LocalInstallerCommand += " -Verbose" + $LocalInstallerArguments += @{ Verbose = $True } } if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { if($Force) { - $LocalInstallerCommand += " -Force" + $LocalInstallerArguments += @{ Force = $True } } } if ($Clean) { - $LocalInstallerCommand += " -Clean" + $LocalInstallerArguments += @{ Clean = $True } } Write-Verbose "Installing $ToolName version $ToolVersion" - Write-Verbose "Executing '$LocalInstallerCommand'" - Invoke-Expression "$LocalInstallerCommand" + Write-Verbose "Executing '$InstallerPath $LocalInstallerArguments'" + & $InstallerPath @LocalInstallerArguments if ($LASTEXITCODE -Ne "0") { $errMsg = "$ToolName installation failed" if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 new file mode 100644 index 00000000000..8b8bafd6a89 --- /dev/null +++ b/eng/common/internal-feed-operations.ps1 @@ -0,0 +1,135 @@ +param( + [Parameter(Mandatory=$true)][string] $Operation, + [string] $AuthToken, + [string] $CommitSha, + [string] $RepoName, + [switch] $IsFeedPrivate +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. $PSScriptRoot\tools.ps1 + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified +# internal builds +function SetupCredProvider { + param( + [string] $AuthToken + ) + + # Install the Cred Provider NuGet plugin + Write-Host "Setting up Cred Provider NuGet plugin in the agent..." + Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' + + Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." + Invoke-WebRequest $url -OutFile installcredprovider.ps1 + + Write-Host "Installing plugin..." + .\installcredprovider.ps1 -Force + + Write-Host "Deleting local copy of 'installcredprovider.ps1'..." + Remove-Item .\installcredprovider.ps1 + + if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { + Write-Host "CredProvider plugin was not installed correctly!" + ExitWithExitCode 1 + } + else { + Write-Host "CredProvider plugin was installed correctly!" + } + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + $nugetConfigPath = "$RepoRoot\NuGet.config" + + if (-Not (Test-Path -Path $nugetConfigPath)) { + Write-Host "NuGet.config file not found in repo's root!" + ExitWithExitCode 1 + } + + $endpoints = New-Object System.Collections.ArrayList + $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} + + if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { + foreach ($stableRestoreResource in $nugetConfigPackageSources) { + $trimmedResource = ([string]$stableRestoreResource).Trim() + [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) + } + } + + if (($endpoints | Measure-Object).Count -gt 0) { + # Create the JSON object. It should look like '{"endpointCredentials": [{"endpoint":"http://example.index.json", "username":"optional", "password":"accesstoken"}]}' + $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress + + # Create the environment variables the AzDo way + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ + 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' + 'issecret' = 'false' + } + + # We don't want sessions cached since we will be updating the endpoints quite frequently + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ + 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' + 'issecret' = 'false' + } + } + else + { + Write-Host "No internal endpoints found in NuGet.config" + } +} + +#Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + $dotnetTempDir = "$RepoRoot\dotnet" + $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + $dotnet = "$dotnetTempDir\dotnet.exe" + $restoreProjPath = "$PSScriptRoot\restore.proj" + + Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + '' | Out-File "$restoreProjPath" + + & $dotnet restore $restoreProjPath + + Write-Host "Arcade SDK restored!" + + if (Test-Path -Path $restoreProjPath) { + Remove-Item $restoreProjPath + } + + if (Test-Path -Path $dotnetTempDir) { + Remove-Item $dotnetTempDir -Recurse + } +} + +try { + Push-Location $PSScriptRoot + + if ($Operation -like "setup") { + SetupCredProvider $AuthToken + } + elseif ($Operation -like "install-restore") { + InstallDotNetSdkAndRestoreArcade + } + else { + Write-Host "Unknown operation '$Operation'!" + ExitWithExitCode 1 + } +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} +finally { + Pop-Location +} diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh new file mode 100644 index 00000000000..1ff654d2ffc --- /dev/null +++ b/eng/common/internal-feed-operations.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +set -e + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. +# This should ONLY be called from identified internal builds +function SetupCredProvider { + local authToken=$1 + + # Install the Cred Provider NuGet plugin + echo "Setting up Cred Provider NuGet plugin in the agent..."... + echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" + + echo "Writing the contents of 'installcredprovider.ps1' locally..." + local installcredproviderPath="installcredprovider.sh" + if command -v curl > /dev/null; then + curl $url > "$installcredproviderPath" + else + wget -q -O "$installcredproviderPath" "$url" + fi + + echo "Installing plugin..." + . "$installcredproviderPath" + + echo "Deleting local copy of 'installcredprovider.sh'..." + rm installcredprovider.sh + + if [ ! -d "$HOME/.nuget/plugins" ]; then + echo "CredProvider plugin was not installed correctly!" + ExitWithExitCode 1 + else + echo "CredProvider plugin was installed correctly!" + fi + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + local nugetConfigPath="$repo_root/NuGet.config" + + if [ ! "$nugetConfigPath" ]; then + echo "NuGet.config file not found in repo's root!" + ExitWithExitCode 1 + fi + + local endpoints='[' + local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` + local pattern="value=\"(.*)\"" + + for value in $nugetConfigPackageValues + do + if [[ $value =~ $pattern ]]; then + local endpoint="${BASH_REMATCH[1]}" + endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," + fi + done + + endpoints=${endpoints%?} + endpoints+=']' + + if [ ${#endpoints} -gt 2 ]; then + # Create the JSON object. It should look like '{"endpointCredentials": [{"endpoint":"http://example.index.json", "username":"optional", "password":"accesstoken"}]}' + local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" + + echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" + echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" + else + echo "No internal endpoints found in NuGet.config" + fi +} + +# Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + local dotnetTempDir="$repo_root/dotnet" + local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + local restoreProjPath="$repo_root/eng/common/restore.proj" + + echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + echo "" > "$restoreProjPath" + + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + local res=`$dotnetTempDir/dotnet restore $restoreProjPath` + echo "Arcade SDK restored!" + + # Cleanup + if [ "$restoreProjPath" ]; then + rm "$restoreProjPath" + fi + + if [ "$dotnetTempDir" ]; then + rm -r $dotnetTempDir + fi +} + +source="${BASH_SOURCE[0]}" +operation='' +authToken='' +repoName='' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | awk '{print tolower($0)}')" + case "$opt" in + --operation) + operation=$2 + shift + ;; + --authtoken) + authToken=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ "$operation" = "setup" ]; then + SetupCredProvider $authToken +elif [ "$operation" = "install-restore" ]; then + InstallDotNetSdkAndRestoreArcade +else + echo "Unknown operation '$operation'!" +fi diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 index f286ae0cde2..7a34c7e8a42 100644 --- a/eng/common/native/CommonLibrary.psm1 +++ b/eng/common/native/CommonLibrary.psm1 @@ -209,7 +209,7 @@ function New-ScriptShim { Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") } - Invoke-Expression "$ShimDirectory\WinShimmer\winshimmer.exe $ShimName $ToolFilePath $ShimDirectory" + & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory return $True } catch { diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 new file mode 100644 index 00000000000..7b61376f8aa --- /dev/null +++ b/eng/common/pipeline-logging-functions.ps1 @@ -0,0 +1,233 @@ +# Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. + +# NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 + +$script:loggingCommandPrefix = '##vso[' +$script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? + New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } + New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } + New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } + New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } +) +# TODO: BUG: Escape % ??? +# TODO: Add test to verify don't need to escape "=". + +function Write-PipelineTelemetryError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Category, + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + $PSBoundParameters.Remove("Category") | Out-Null + + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + $PSBoundParameters.Remove("Message") | Out-Null + $PSBoundParameters.Add("Message", $Message) + + Write-PipelineTaskError @PSBoundParameters +} + +function Write-PipelineTaskError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + if(!$ci) { + if($Type -eq 'error') { + Write-Host $Message -ForegroundColor Red + return + } + elseif ($Type -eq 'warning') { + Write-Host $Message -ForegroundColor Yellow + return + } + } + + if(($Type -ne 'error') -and ($Type -ne 'warning')) { + Write-Host $Message + return + } + if(-not $PSBoundParameters.ContainsKey('Type')) { + $PSBoundParameters.Add('Type', 'error') + } + Write-LogIssue @PSBoundParameters + } + + function Write-PipelineSetVariable { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [string]$Value, + [switch]$Secret, + [switch]$AsOutput) + + if($ci) { + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ + 'variable' = $Name + 'isSecret' = $Secret + 'isOutput' = 'true' + } -AsOutput:$AsOutput + } + } + + function Write-PipelinePrependPath { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$Path, + [switch]$AsOutput) + if($ci) { + Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput + } + } + +<######################################## +# Private functions. +########################################> +function Format-LoggingCommandData { + [CmdletBinding()] + param([string]$Value, [switch]$Reverse) + + if (!$Value) { + return '' + } + + if (!$Reverse) { + foreach ($mapping in $script:loggingCommandEscapeMappings) { + $Value = $Value.Replace($mapping.Token, $mapping.Replacement) + } + } else { + for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { + $mapping = $script:loggingCommandEscapeMappings[$i] + $Value = $Value.Replace($mapping.Replacement, $mapping.Token) + } + } + + return $Value +} + +function Format-LoggingCommand { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Area, + [Parameter(Mandatory = $true)] + [string]$Event, + [string]$Data, + [hashtable]$Properties) + + # Append the preamble. + [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder + $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) + + # Append the properties. + if ($Properties) { + $first = $true + foreach ($key in $Properties.Keys) { + [string]$value = Format-LoggingCommandData $Properties[$key] + if ($value) { + if ($first) { + $null = $sb.Append(' ') + $first = $false + } else { + $null = $sb.Append(';') + } + + $null = $sb.Append("$key=$value") + } + } + } + + # Append the tail and output the value. + $Data = Format-LoggingCommandData $Data + $sb.Append(']').Append($Data).ToString() +} + +function Write-LoggingCommand { + [CmdletBinding(DefaultParameterSetName = 'Parameters')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Area, + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Event, + [Parameter(ParameterSetName = 'Parameters')] + [string]$Data, + [Parameter(ParameterSetName = 'Parameters')] + [hashtable]$Properties, + [Parameter(Mandatory = $true, ParameterSetName = 'Object')] + $Command, + [switch]$AsOutput) + + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput + return + } + + $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties + if ($AsOutput) { + $command + } else { + Write-Host $command + } +} + +function Write-LogIssue { + [CmdletBinding()] + param( + [ValidateSet('warning', 'error')] + [Parameter(Mandatory = $true)] + [string]$Type, + [string]$Message, + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ + 'type' = $Type + 'code' = $ErrCode + 'sourcepath' = $SourcePath + 'linenumber' = $LineNumber + 'columnnumber' = $ColumnNumber + } + if ($AsOutput) { + return $command + } + + if ($Type -eq 'error') { + $foregroundColor = $host.PrivateData.ErrorForegroundColor + $backgroundColor = $host.PrivateData.ErrorBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Red + $backgroundColor = [System.ConsoleColor]::Black + } + } else { + $foregroundColor = $host.PrivateData.WarningForegroundColor + $backgroundColor = $host.PrivateData.WarningBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Yellow + $backgroundColor = [System.ConsoleColor]::Black + } + } + + Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor +} \ No newline at end of file diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh new file mode 100644 index 00000000000..6098f9a5438 --- /dev/null +++ b/eng/common/pipeline-logging-functions.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +function Write-PipelineTelemetryError { + local telemetry_category='' + local function_args=() + local message='' + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + case "$opt" in + -category|-c) + telemetry_category=$2 + shift + ;; + -*) + function_args+=("$1 $2") + shift + ;; + *) + message=$* + ;; + esac + shift + done + + if [[ "$ci" != true ]]; then + echo "$message" >&2 + return + fi + + message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" + function_args+=("$message") + + Write-PipelineTaskError $function_args +} + +function Write-PipelineTaskError { + if [[ "$ci" != true ]]; then + echo "$@" >&2 + return + fi + + message_type="error" + sourcepath='' + linenumber='' + columnnumber='' + error_code='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + case "$opt" in + -type|-t) + message_type=$2 + shift + ;; + -sourcepath|-s) + sourcepath=$2 + shift + ;; + -linenumber|-ln) + linenumber=$2 + shift + ;; + -columnnumber|-cn) + columnnumber=$2 + shift + ;; + -errcode|-e) + error_code=$2 + shift + ;; + *) + break + ;; + esac + + shift + done + + message="##vso[task.logissue" + + message="$message type=$message_type" + + if [ -n "$sourcepath" ]; then + message="$message;sourcepath=$sourcepath" + fi + + if [ -n "$linenumber" ]; then + message="$message;linenumber=$linenumber" + fi + + if [ -n "$columnnumber" ]; then + message="$message;columnnumber=$columnnumber" + fi + + if [ -n "$error_code" ]; then + message="$message;code=$error_code" + fi + + message="$message]$*" + echo "$message" +} + diff --git a/eng/common/post-build/dotnetsymbol-init.ps1 b/eng/common/post-build/dotnetsymbol-init.ps1 new file mode 100644 index 00000000000..e7659b98c8c --- /dev/null +++ b/eng/common/post-build/dotnetsymbol-init.ps1 @@ -0,0 +1,29 @@ +param ( + $dotnetsymbolVersion = $null +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. $PSScriptRoot\..\tools.ps1 + +$verbosity = "minimal" + +function Installdotnetsymbol ($dotnetsymbolVersion) { + $dotnetsymbolPackageName = "dotnet-symbol" + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$dotnetsymbolPackageName*") -and ($toolList -like "*$dotnetsymbolVersion*")) { + Write-Host "dotnet-symbol version $dotnetsymbolVersion is already installed." + } + else { + Write-Host "Installing dotnet-symbol version $dotnetsymbolVersion..." + Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + & "$dotnet" tool install $dotnetsymbolPackageName --version $dotnetsymbolVersion --verbosity $verbosity --global + } +} + +Installdotnetsymbol $dotnetsymbolVersion diff --git a/eng/common/post-build/sourcelink-cli-init.ps1 b/eng/common/post-build/sourcelink-cli-init.ps1 new file mode 100644 index 00000000000..9eaa25b3b50 --- /dev/null +++ b/eng/common/post-build/sourcelink-cli-init.ps1 @@ -0,0 +1,29 @@ +param ( + $sourcelinkCliVersion = $null +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. $PSScriptRoot\..\tools.ps1 + +$verbosity = "minimal" + +function InstallSourcelinkCli ($sourcelinkCliVersion) { + $sourcelinkCliPackageName = "sourcelink" + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) { + Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed." + } + else { + Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." + Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity $verbosity --global + } +} + +InstallSourcelinkCli $sourcelinkCliVersion diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 new file mode 100644 index 00000000000..8abd684e9e5 --- /dev/null +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -0,0 +1,224 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$true)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade + [Parameter(Mandatory=$true)][string] $GHCommit, # GitHub commit SHA used to build the packages + [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. $PSScriptRoot\..\tools.ps1 + +# Cache/HashMap (File -> Exist flag) used to consult whether a file exist +# in the repository at a specific commit point. This is populated by inserting +# all files present in the repo at a specific commit point. +$global:RepoFiles = @{} + +$ValidatePackage = { + param( + [string] $PackagePath # Full path to a Symbols.NuGet package + ) + + . $using:PSScriptRoot\..\tools.ps1 + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + # Extensions for which we'll look for SourceLink information + # For now we'll only care about Portable & Embedded PDBs + $RelevantExtensions = @(".dll", ".exe", ".pdb") + + Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + $FailedFiles = 0 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $FileName = $_.FullName + $Extension = [System.IO.Path]::GetExtension($_.Name) + $FakeName = -Join((New-Guid), $Extension) + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName + + # We ignore resource DLLs + if ($FileName.EndsWith(".resources.dll")) { + return + } + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + + $ValidateFile = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $RealPath, + [ref] $FailedFiles + ) + + $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools" + $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe" + $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String + + if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { + $NumFailedLinks = 0 + + # We only care about Http addresses + $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches + + if ($Matches.Count -ne 0) { + $Matches.Value | + ForEach-Object { + $Link = $_ + $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/" + + $FilePath = $Link.Replace($CommitUrl, "") + $Status = 200 + $Cache = $using:RepoFiles + + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + # Only GitHub links are valid + if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match "github" -or $Uri.Host -match "githubusercontent")) { + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + $Status = 0 + } + } + catch { + write-host $_ + $Status = 0 + } + } + + if ($Status -ne 200) { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ + } + } + } + + if ($NumFailedLinks -ne 0) { + $FailedFiles.value++ + $global:LASTEXITCODE = 1 + } + } + } + + &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) + } + } + catch { + + } + finally { + $zip.Dispose() + } + + if ($FailedFiles -eq 0) { + Write-Host "Passed." + } + else { + Write-PipelineTaskError "$PackagePath has broken SourceLink links." + } +} + +function ValidateSourceLinkLinks { + if (!($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { + if (!($GHRepoName -Match "^[^\s-]+-[^\s]+$")) { + Write-PipelineTaskError "GHRepoName should be in the format / or -" + ExitWithExitCode 1 + } + else { + $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2'; + } + } + + if (!($GHCommit -Match "^[0-9a-fA-F]{40}$")) { + Write-PipelineTaskError "GHCommit should be a 40 chars hexadecimal string" + ExitWithExitCode 1 + } + + $RepoTreeURL = -Join("http://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") + $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") + + try { + # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash + $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree + + foreach ($file in $Data) { + $Extension = [System.IO.Path]::GetExtension($file.path) + + if ($CodeExtensions.Contains($Extension)) { + $RepoFiles[$file.path] = 1 + } + } + } + catch { + Write-PipelineTaskError "Problems downloading the list of files from the repo. Url used: $RepoTreeURL" + Write-Host $_ + ExitWithExitCode 1 + } + + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + # Process each NuGet package in parallel + $Jobs = @() + Get-ChildItem "$InputPath\*.symbols.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +function CheckExitCode ([string]$stage) { + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-PipelineTaskError "Something failed while '$stage'. Check for errors above. Exiting now..." + ExitWithExitCode $exitCode + } +} + +try { + Write-Host "Installing SourceLink CLI..." + Get-Location + . $PSScriptRoot\sourcelink-cli-init.ps1 -sourcelinkCliVersion $SourcelinkCliVersion + CheckExitCode "Running sourcelink-cli-init" + + Measure-Command { ValidateSourceLinkLinks } +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 new file mode 100644 index 00000000000..69456854e04 --- /dev/null +++ b/eng/common/post-build/symbols-validation.ps1 @@ -0,0 +1,186 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$true)][string] $DotnetSymbolVersion # Version of dotnet symbol to use +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. $PSScriptRoot\..\tools.ps1 + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +function FirstMatchingSymbolDescriptionOrDefault { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $SymbolsPath + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + "\" + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, ".pdb") + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") + + $dotnetsymbolExe = "$env:USERPROFILE\.dotnet\tools" + $dotnetsymbolExe = Resolve-Path "$dotnetsymbolExe\dotnet-symbol.exe" + + & $dotnetsymbolExe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null + + if (Test-Path $PdbPath) { + return "PDB" + } + elseif (Test-Path $NGenPdb) { + return "NGen PDB" + } + elseif (Test-Path $SODbg) { + return "DBG for SO" + } + elseif (Test-Path $DylibDwarf) { + return "Dwarf for Dylib" + } + elseif (Test-Path $SymbolPath) { + return "Module" + } + else { + return $null + } +} + +function CountMissingSymbols { + param( + [string] $PackagePath # Path to a NuGet package + ) + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + # Extensions for which we'll look for symbols + $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") + + # How many files are missing symbol information + $MissingSymbols = 0 + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $PackageGuid = New-Guid + $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" + + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + + Get-ChildItem -Recurse $ExtractPath | + Where-Object {$RelevantExtensions -contains $_.Extension} | + ForEach-Object { + if ($_.FullName -Match "\\ref\\") { + Write-Host "`t Ignoring reference assembly file" $_.FullName + return + } + + $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath + $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath + + Write-Host -NoNewLine "`t Checking file" $_.FullName "... " + + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host "No symbols found on MSDL or SymWeb!" + } + else { + if ($SymbolsOnMSDL -eq $null) { + Write-Host "No symbols found on MSDL!" + } + else { + Write-Host "No symbols found on SymWeb!" + } + } + } + } + + Pop-Location + + return $MissingSymbols +} + +function CheckSymbolsAvailable { + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $FileName = $_.Name + + # These packages from Arcade-Services include some native libraries that + # our current symbol uploader can't handle. Below is a workaround until + # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. + if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + + Write-Host "Validating $FileName " + $Status = CountMissingSymbols "$InputPath\$FileName" + + if ($Status -ne 0) { + Write-PipelineTaskError "Missing symbols for $Status modules in the package $FileName" + ExitWithExitCode $exitCode + } + + Write-Host + } +} + +function CheckExitCode ([string]$stage) { + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-PipelineTaskError "Something failed while '$stage'. Check for errors above. Exiting now..." + ExitWithExitCode $exitCode + } +} + +try { + Write-Host "Installing dotnet symbol ..." + Get-Location + . $PSScriptRoot\dotnetsymbol-init.ps1 -dotnetsymbolVersion $DotnetSymbolVersion + CheckExitCode "Running dotnetsymbol-init" + + CheckSymbolsAvailable +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/NuGet.config b/eng/common/sdl/NuGet.config new file mode 100644 index 00000000000..0c5451c1141 --- /dev/null +++ b/eng/common/sdl/NuGet.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 new file mode 100644 index 00000000000..0635f26fb63 --- /dev/null +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -0,0 +1,97 @@ +Param( + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_SOURCESDIRECTORY ("artifacts")), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code + [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel="Standard" # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +$LASTEXITCODE = 0 + +#Replace repo names to the format of org/repo +if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; +} +else{ + $RepoName = $Repository; +} + +if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path "tools" "guardian.cmd")) +} else { + $guardianCliLocation = $GuardianCliLocation +} + +$ValidPath = Test-Path $guardianCliLocation + +if ($ValidPath -eq $False) +{ + Write-Host "Invalid Guardian CLI Location." + exit 1 +} + +& $(Join-Path $PSScriptRoot "init-sdl.ps1") -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $ArtifactsDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel +$gdnFolder = Join-Path $ArtifactsDirectory ".gdn" + +if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $ArtifactsDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $ArtifactsDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + } else { + Write-Host "Could not onboard to TSA -- not all required values ($$TsaCodebaseName, $$TsaNotificationEmail, $$TsaCodebaseAdmin, $$TsaBugAreaPath) were specified." + exit 1 + } +} + +if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $ArtifactsDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel +} +if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $ArtifactsDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel +} + +if ($UpdateBaseline) { + & (Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Update baseline" +} + +if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $SourceDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $ArtifactsDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian tsa-publish failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + } else { + Write-Host "Could not publish to TSA -- not all required values ($$TsaBranchName, $$BuildNumber) were specified." + exit 1 + } +} diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 new file mode 100644 index 00000000000..26e01c0673c --- /dev/null +++ b/eng/common/sdl/init-sdl.ps1 @@ -0,0 +1,48 @@ +Param( + [string] $GuardianCliLocation, + [string] $Repository, + [string] $BranchName="master", + [string] $WorkingDirectory, + [string] $AzureDevOpsAccessToken, + [string] $GuardianLoggerLevel="Standard" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +$LASTEXITCODE = 0 + +# Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file +$encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) +$escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0-preview.1" +$zipFile = "$WorkingDirectory/gdn.zip" + +Add-Type -AssemblyName System.IO.Compression.FileSystem +$gdnFolder = (Join-Path $WorkingDirectory ".gdn") +Try +{ + # We try to download the zip; if the request fails (e.g. the file doesn't exist), we catch it and init guardian instead + Write-Host "Downloading gdn folder from internal config repostiory..." + Invoke-WebRequest -Headers @{ "Accept"="application/zip"; "Authorization"="Basic $encodedPat" } -Uri $uri -OutFile $zipFile + if (Test-Path $gdnFolder) { + # Remove the gdn folder if it exists (it shouldn't unless there's too much caching; this is just in case) + Remove-Item -Force -Recurse $gdnFolder + } + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $WorkingDirectory) + Write-Host $gdnFolder +} Catch [System.Net.WebException] { + # if the folder does not exist, we'll do a guardian init and push it to the remote repository + Write-Host "Initializing Guardian..." + Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" + & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Error "Guardian init failed with exit code $LASTEXITCODE." + } + # We create the mainbaseline so it can be edited later + Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" + & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline + if ($LASTEXITCODE -ne 0) { + Write-Error "Guardian baseline failed with exit code $LASTEXITCODE." + } + & $(Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Initialize gdn folder" +} \ No newline at end of file diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config new file mode 100644 index 00000000000..b054737df13 --- /dev/null +++ b/eng/common/sdl/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/sdl/push-gdn.ps1 b/eng/common/sdl/push-gdn.ps1 new file mode 100644 index 00000000000..79c707d6d8a --- /dev/null +++ b/eng/common/sdl/push-gdn.ps1 @@ -0,0 +1,51 @@ +Param( + [string] $Repository, + [string] $BranchName="master", + [string] $GdnFolder, + [string] $AzureDevOpsAccessToken, + [string] $PushReason +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +$LASTEXITCODE = 0 + +# We create the temp directory where we'll store the sdl-config repository +$sdlDir = Join-Path $env:TEMP "sdl" +if (Test-Path $sdlDir) { + Remove-Item -Force -Recurse $sdlDir +} + +Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" +git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir +if ($LASTEXITCODE -ne 0) { + Write-Error "Git clone failed with exit code $LASTEXITCODE." +} +# We copy the .gdn folder from our local run into the git repository so it can be committed +$sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) ".gdn" +if (Get-Command Robocopy) { + Robocopy /S $GdnFolder $sdlRepositoryFolder +} else { + rsync -r $GdnFolder $sdlRepositoryFolder +} +# cd to the sdl-config directory so we can run git there +Push-Location $sdlDir +# git add . --> git commit --> git push +Write-Host "git add ." +git add . +if ($LASTEXITCODE -ne 0) { + Write-Error "Git add failed with exit code $LASTEXITCODE." +} +Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" +git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" +if ($LASTEXITCODE -ne 0) { + Write-Error "Git commit failed with exit code $LASTEXITCODE." +} +Write-Host "git push" +git push +if ($LASTEXITCODE -ne 0) { + Write-Error "Git push failed with exit code $LASTEXITCODE." +} + +# Return to the original directory +Pop-Location \ No newline at end of file diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 new file mode 100644 index 00000000000..e6a86d03a21 --- /dev/null +++ b/eng/common/sdl/run-sdl.ps1 @@ -0,0 +1,65 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $TargetDirectory, + [string] $GdnFolder, + [string[]] $ToolsList, + [string] $UpdateBaseline, + [string] $GuardianLoggerLevel="Standard" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +$LASTEXITCODE = 0 + +# We store config files in the r directory of .gdn +Write-Host $ToolsList +$gdnConfigPath = Join-Path $GdnFolder "r" +$ValidPath = Test-Path $GuardianCliLocation + +if ($ValidPath -eq $False) +{ + Write-Host "Invalid Guardian CLI Location." + exit 1 +} + +foreach ($tool in $ToolsList) { + $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" + $config = $False + Write-Host $tool + # We have to manually configure tools that run on source to look at the source directory only + if ($tool -eq "credscan") { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory : $TargetDirectory `"" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory : $TargetDirectory " + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + $config = $True + } + if ($tool -eq "policheck") { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target : $TargetDirectory `"" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target : $TargetDirectory " + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + $config = $True + } + + Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel --config $gdnConfigFile $config" + if ($config) { + & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel --config $gdnConfigFile + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian run for $tool using $gdnConfigFile failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + } else { + & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian run for $tool failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE + } + } +} + diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml new file mode 100644 index 00000000000..2f669fd95ba --- /dev/null +++ b/eng/common/templates/job/execute-sdl.yml @@ -0,0 +1,37 @@ +parameters: + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + continueOnError: false # optional: determines whether to continue the build if the step errors; + dependsOn: '' # Optional: dependencies of the job + +jobs: +- job: Run_SDL + dependsOn: ${{ parameters.dependsOn }} + displayName: Run SDL tool + variables: + - group: DotNet-VSTS-Bot + steps: + - checkout: self + clean: true + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + - task: NuGetCommand@2 + displayName: 'Install Guardian' + inputs: + restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + feedsToUse: config + nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config + externalFeedCredentials: GuardianConnect + restoreDirectory: $(Build.SourcesDirectory)\.packages + - ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: eng/common/sdl/execute-all-sdl-tools.ps1 ${{ parameters.overrideParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.continueOnError }} + - ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: eng/common/sdl/execute-all-sdl-tools.ps1 + -GuardianPackageName Microsoft.Guardian.Cli.0.3.2 + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index 620bd3c62e7..01442216816 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -59,6 +59,30 @@ jobs: /p:Configuration=$(_BuildConfig) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} + - task: powershell@2 + displayName: Create BARBuildId Artifact + inputs: + targetType: inline + script: | + Add-Content -Path "$(Build.StagingDirectory)/BARBuildId.txt" -Value $(BARBuildId) + - task: powershell@2 + displayName: Create Channels Artifact + inputs: + targetType: inline + script: | + Add-Content -Path "$(Build.StagingDirectory)/Channels.txt" -Value "$(DefaultChannels)" + - task: PublishBuildArtifacts@1 + displayName: Publish BAR BuildId to VSTS + inputs: + PathtoPublish: '$(Build.StagingDirectory)/BARBuildId.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + - task: PublishBuildArtifacts@1 + displayName: Publish Channels to VSTS + inputs: + PathtoPublish: '$(Build.StagingDirectory)/Channels.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - task: PublishBuildArtifacts@1 displayName: Publish Logs to VSTS diff --git a/eng/common/templates/post-build/channels/public-dev-release.yml b/eng/common/templates/post-build/channels/public-dev-release.yml new file mode 100644 index 00000000000..c61eaa927d9 --- /dev/null +++ b/eng/common/templates/post-build/channels/public-dev-release.yml @@ -0,0 +1,145 @@ +parameters: + enableSymbolValidation: true + +stages: +- stage: Publish + dependsOn: validate + variables: + - template: ../common-variables.yml + displayName: Developer Channel + jobs: + - template: ../setup-maestro-vars.yml + + - job: + displayName: Symbol Publishing + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicDevRelease_30_Channel_Id) + variables: + - group: DotNet-Symbol-Server-Pats + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download PDB Artifacts + inputs: + buildType: current + artifactName: PDBArtifacts + continueOnError: true + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts + + - task: PowerShell@2 + displayName: Publish + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishToSymbolServers -restore -msbuildEngine dotnet + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:PDBArtifactsDirectory='$(Build.ArtifactStagingDirectory)/PDBArtifacts/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:Configuration=Release + + - job: + displayName: Publish to Static Feed + dependsOn: setupMaestroVars + variables: + - group: DotNet-Blob-Feed + - group: Publish-Build-Assets + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicDevRelease_30_Channel_Id) + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Asset Manifests + inputs: + buildType: current + artifactName: AssetManifests + + - task: PowerShell@2 + displayName: Publish + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishToPackageFeed -restore -msbuildEngine dotnet + /p:AccountKeyToStaticFeed='$(dotnetfeed-storage-access-key-1)' + /p:BARBuildId=$(BARBuildId) + /p:MaestroApiEndpoint='https://maestro-prod.westus2.cloudapp.azure.com' + /p:BuildAssetRegistryToken='$(MaestroAccessToken)' + /p:ManifestsBasePath='$(Build.ArtifactStagingDirectory)/AssetManifests/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts/' + /p:ArtifactsCategory='$(_DotNetArtifactsCategory)' + /p:OverrideAssetsWithSameName=true + /p:PassIfExistingItemIdentical=true + /p:Configuration=Release + + +- stage: PublishValidation + displayName: Publish Validation + variables: + - template: ../common-variables.yml + jobs: + - template: ../setup-maestro-vars.yml + + - ${{ if eq(parameters.enableSymbolValidation, 'true') }}: + - job: + displayName: Symbol Availability + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicDevRelease_30_Channel_Id) + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts + + - task: PowerShell@2 + displayName: Check Symbol Availability + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/symbols-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ -ExtractPath $(Agent.BuildDirectory)/Temp/ -DotnetSymbolVersion $(SymbolToolVersion) + + - job: + displayName: Gather Drop + dependsOn: setupMaestroVars + variables: + BARBuildId: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicDevRelease_30_Channel_Id) + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Setup Darc CLI + inputs: + targetType: filePath + filePath: '$(Build.SourcesDirectory)/eng/common/darc-init.ps1' + + - task: PowerShell@2 + displayName: Run Darc gather-drop + inputs: + targetType: inline + script: | + darc gather-drop --non-shipping --continue-on-error --id $(BARBuildId) --output-dir $(Agent.BuildDirectory)/Temp/Drop/ --bar-uri https://maestro-prod.westus2.cloudapp.azure.com/ --password $(MaestroAccessToken) --latest-location + + - template: ../promote-build.yml + parameters: + ChannelId: ${{ variables.PublicDevRelease_30_Channel_Id }} diff --git a/eng/common/templates/post-build/channels/public-validation-release.yml b/eng/common/templates/post-build/channels/public-validation-release.yml new file mode 100644 index 00000000000..23725c6d620 --- /dev/null +++ b/eng/common/templates/post-build/channels/public-validation-release.yml @@ -0,0 +1,91 @@ +stages: +- stage: PVR_Publish + dependsOn: validate + variables: + - template: ../common-variables.yml + displayName: Validation Channel + jobs: + - template: ../setup-maestro-vars.yml + + - job: + displayName: Publish to Static Feed + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicValidationRelease_30_Channel_Id) + variables: + - group: DotNet-Blob-Feed + - group: Publish-Build-Assets + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Asset Manifests + inputs: + buildType: current + artifactName: AssetManifests + + - task: PowerShell@2 + displayName: Publish + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishToPackageFeed -restore -msbuildEngine dotnet + /p:AccountKeyToStaticFeed='$(dotnetfeed-storage-access-key-1)' + /p:BARBuildId=$(BARBuildId) + /p:MaestroApiEndpoint='https://maestro-prod.westus2.cloudapp.azure.com' + /p:BuildAssetRegistryToken='$(MaestroAccessToken)' + /p:ManifestsBasePath='$(Build.ArtifactStagingDirectory)/AssetManifests/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts/' + /p:ArtifactsCategory='$(_DotNetArtifactsCategory)' + /p:OverrideAssetsWithSameName=true + /p:PassIfExistingItemIdentical=true + /p:Configuration=Release + + +- stage: PVR_PublishValidation + displayName: Publish Validation + variables: + - template: ../common-variables.yml + jobs: + - template: ../setup-maestro-vars.yml + + - job: + displayName: Gather Drop + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], variables.PublicValidationRelease_30_Channel_Id) + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + - group: Publish-Build-Assets + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Setup Darc CLI + inputs: + targetType: filePath + filePath: '$(Build.SourcesDirectory)/eng/common/darc-init.ps1' + + - task: PowerShell@2 + displayName: Run Darc gather-drop + inputs: + targetType: inline + script: | + darc gather-drop --non-shipping --continue-on-error --id $(BARBuildId) --output-dir $(Agent.BuildDirectory)/Temp/Drop/ --bar-uri https://maestro-prod.westus2.cloudapp.azure.com --password $(MaestroAccessToken) --latest-location + + - template: ../promote-build.yml + parameters: + ChannelId: ${{ variables.PublicValidationRelease_30_Channel_Id }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml new file mode 100644 index 00000000000..97b48d97fec --- /dev/null +++ b/eng/common/templates/post-build/common-variables.yml @@ -0,0 +1,9 @@ +variables: + # .NET Core 3 Dev + PublicDevRelease_30_Channel_Id: 3 + + # .NET Tools - Validation + PublicValidationRelease_30_Channel_Id: 9 + + SourceLinkCLIVersion: 3.0.0 + SymbolToolVersion: 1.0.1 diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml new file mode 100644 index 00000000000..6b74475a6f8 --- /dev/null +++ b/eng/common/templates/post-build/post-build.yml @@ -0,0 +1,59 @@ +parameters: + enableSourceLinkValidation: true + enableSigningValidation: true + enableSymbolValidation: true + +stages: +- stage: validate + dependsOn: build + displayName: Validate + jobs: + - ${{ if eq(parameters.enableSigningValidation, 'true') }}: + - job: + displayName: Signing Validation + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine dotnet + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:Configuration=Release + + - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: + - job: + displayName: SourceLink Validation + variables: + - template: common-variables.yml + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + +- template: \eng\common\templates\post-build\channels\public-dev-release.yml + parameters: + enableSymbolValidation: ${{ parameters.enableSymbolValidation }} + +- template: \eng\common\templates\post-build\channels\public-validation-release.yml diff --git a/eng/common/templates/post-build/promote-build.yml b/eng/common/templates/post-build/promote-build.yml new file mode 100644 index 00000000000..d00317003b7 --- /dev/null +++ b/eng/common/templates/post-build/promote-build.yml @@ -0,0 +1,28 @@ +parameters: + ChannelId: 0 + +jobs: +- job: + displayName: Promote Build + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], ${{ parameters.ChannelId }}) + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + - name: ChannelId + value: ${{ parameters.ChannelId }} + - group: Publish-Build-Assets + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Add Build to Channel + inputs: + targetType: inline + script: | + $headers = @{ + "Accept" = "application/json" + "Authorization" = "Bearer $(MaestroAccessToken)" + } + Invoke-RestMethod -Method Post -Headers $headers -Uri https://maestro-prod.westus2.cloudapp.azure.com/api/channels/$(ChannelId)/builds/$(BARBuildId)?api-version=2019-01-16 + enabled: false diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml new file mode 100644 index 00000000000..9de585a94ac --- /dev/null +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -0,0 +1,38 @@ +jobs: +- job: setupMaestroVars + displayName: Setup Maestro Vars + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + script: | + # This is needed to make Write-PipelineSetVariable works in this context + if ($env:BUILD_BUILDNUMBER -ne "" -and $env:BUILD_BUILDNUMBER -ne $null) { + $ci = $true + + . "$(Build.SourcesDirectory)/eng/common/tools.ps1" + + $BarId = Get-Content "$(Build.StagingDirectory)/ReleaseConfigs/BARBuildId.txt" + Write-PipelineSetVariable -Name 'BARBuildId' -Value $BarId + + Write-Host "Asked Write-PipelineSetVariable to create BARBuildId with value '$BarId'" + + $Channels = "" + Get-Content "$(Build.StagingDirectory)/ReleaseConfigs/Channels.txt" | ForEach-Object { $Channels += "$_ ," } + Write-PipelineSetVariable -Name 'InitialChannels' -Value "$Channels" + + Write-Host "Asked Write-PipelineSetVariable to create InitialChannels with value '$Channels'" + } + else { + Write-Host "This step can only be run in an Azure DevOps CI environment." + } diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml index d1ce577db5b..05df886f55f 100644 --- a/eng/common/templates/steps/send-to-helix.yml +++ b/eng/common/templates/steps/send-to-helix.yml @@ -5,6 +5,7 @@ parameters: HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job HelixPreCommands: '' # optional -- commands to run before Helix work item execution HelixPostCommands: '' # optional -- commands to run after Helix work item execution WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects @@ -35,6 +36,7 @@ steps: HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} @@ -64,6 +66,7 @@ steps: HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 9ca177b82a3..60741f03901 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -35,7 +35,7 @@ # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } -# True to attempt using .NET Core already that meets requirements specified in global.json +# True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } @@ -76,7 +76,7 @@ function Exec-Process([string]$command, [string]$commandArgs) { $finished = $false try { - while (-not $process.WaitForExit(100)) { + while (-not $process.WaitForExit(100)) { # Non-blocking loop done to allow ctr-c interrupts } @@ -134,7 +134,7 @@ function InitializeDotNetCli([bool]$install) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { - Write-Host "Unable to find dotnet with SDK version '$dotnetSdkVersion'" -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } @@ -147,12 +147,10 @@ function InitializeDotNetCli([bool]$install) { # It also ensures that VS msbuild will use the downloaded sdk targets. $env:PATH = "$dotnetRoot;$env:PATH" - if ($ci) { - # Make Sure that our bootstrapped dotnet cli is avaliable in future steps of the Azure Pipelines build - Write-Host "##vso[task.prependpath]$dotnetRoot" - Write-Host "##vso[task.setvariable variable=DOTNET_MULTILEVEL_LOOKUP]0" - Write-Host "##vso[task.setvariable variable=DOTNET_SKIP_FIRST_TIME_EXPERIENCE]1" - } + # Make Sure that our bootstrapped dotnet cli is avaliable in future steps of the Azure Pipelines build + Write-PipelinePrependPath -Path $dotnetRoot + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' + Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot } @@ -184,13 +182,13 @@ function InstallDotNet([string] $dotnetRoot, [string] $version, [string] $archit & $installScript @installParameters if ($lastExitCode -ne 0) { - Write-Host "Failed to install dotnet cli (exit code '$lastExitCode')." -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet cli (exit code '$lastExitCode')." ExitWithExitCode $lastExitCode } } # -# Locates Visual Studio MSBuild installation. +# Locates Visual Studio MSBuild installation. # The preference order for MSBuild to use is as follows: # # 1. MSBuild from an active VS command prompt @@ -207,13 +205,17 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { "15.9" } - $vsMinVersion = [Version]::new($vsMinVersionStr) + $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { $msbuildCmd = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { - if ($msbuildCmd.Version -ge $vsMinVersion) { + # Workaround for https://github.com/dotnet/roslyn/issues/35793 + # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0]) + + if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path } @@ -252,7 +254,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { $env:VSINSTALLDIR = $vsInstallDir Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") - + $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" if (Test-Path $vsSdkInstallDir) { Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir @@ -287,13 +289,13 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # # The following properties of tools.vs are recognized: -# "version": "{major}.{minor}" +# "version": "{major}.{minor}" # Two part minimal VS version, e.g. "15.9", "16.0", etc. -# "components": ["componentId1", "componentId2", ...] +# "components": ["componentId1", "componentId2", ...] # Array of ids of workload components that must be available in the VS instance. # See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 # -# Returns JSON describing the located VS instance (same format as returned by vswhere), +# Returns JSON describing the located VS instance (same format as returned by vswhere), # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ @@ -313,8 +315,8 @@ function LocateVisualStudio([object]$vsRequirements = $null){ } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $args = @("-latest", "-prerelease", "-format", "json", "-requires", "Microsoft.Component.MSBuild") - + $args = @("-latest", "-prerelease", "-format", "json", "-requires", "Microsoft.Component.MSBuild", "-products", "*") + if (Get-Member -InputObject $vsRequirements -Name "version") { $args += "-version" $args += $vsRequirements.version @@ -324,7 +326,7 @@ function LocateVisualStudio([object]$vsRequirements = $null){ foreach ($component in $vsRequirements.components) { $args += "-requires" $args += $component - } + } } $vsInfo =& $vsWhereExe $args | ConvertFrom-Json @@ -354,7 +356,7 @@ function InitializeBuildTool() { if ($msbuildEngine -eq "dotnet") { if (!$dotnetRoot) { - Write-Host "/global.json must specify 'tools.dotnet'." -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } @@ -363,13 +365,13 @@ function InitializeBuildTool() { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { - Write-Host $_ -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472" } } else { - Write-Host "Unexpected value of -msbuildEngine: '$msbuildEngine'." -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } @@ -381,12 +383,12 @@ function GetDefaultMSBuildEngine() { if (Get-Member -InputObject $GlobalJson.tools -Name "vs") { return "vs" } - + if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { return "dotnet" } - Write-Host "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } @@ -411,11 +413,13 @@ function GetSdkTaskProject([string]$taskName) { function InitializeNativeTools() { if (Get-Member -InputObject $GlobalJson -Name "native-tools") { - $nativeArgs="" + $nativeArgs= @{} if ($ci) { - $nativeArgs = "-InstallDirectory $ToolsDir" + $nativeArgs = @{ + InstallDirectory = "$ToolsDir" + } } - Invoke-Expression "& `"$PSScriptRoot/init-tools-native.ps1`" $nativeArgs" + & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs } } @@ -437,7 +441,7 @@ function InitializeToolset() { } if (-not $restore) { - Write-Host "Toolset version $toolsetVersion has not been restored." -ForegroundColor Red + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } @@ -497,11 +501,13 @@ function MSBuild() { function MSBuild-Core() { if ($ci) { if (!$binaryLog) { - throw "Binary log must be enabled in CI build." + Write-PipelineTaskError -Message "Binary log must be enabled in CI build." + ExitWithExitCode 1 } if ($nodeReuse) { - throw "Node reuse must be disabled in CI build." + Write-PipelineTaskError -Message "Node reuse must be disabled in CI build." + ExitWithExitCode 1 } } @@ -509,8 +515,8 @@ function MSBuild-Core() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" - if ($warnAsError) { - $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" + if ($warnAsError) { + $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" } foreach ($arg in $args) { @@ -518,29 +524,29 @@ function MSBuild-Core() { $cmdArgs += " `"$arg`"" } } - + $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { - Write-Host "Build failed." -ForegroundColor Red + Write-PipelineTaskError -Message "Build failed." $buildLog = GetMSBuildBinaryLogCommandLineArgument $args - if ($buildLog -ne $null) { - Write-Host "See log: $buildLog" -ForegroundColor DarkGray + if ($buildLog -ne $null) { + Write-Host "See log: $buildLog" -ForegroundColor DarkGray } ExitWithExitCode $exitCode } } -function GetMSBuildBinaryLogCommandLineArgument($arguments) { +function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() if ($arg.StartsWith("/bl:", "OrdinalIgnoreCase")) { return $arg.Substring("/bl:".Length) - } - + } + if ($arg.StartsWith("/binaryLogger:", "OrdinalIgnoreCase")) { return $arg.Substring("/binaryLogger:".Length) } @@ -550,6 +556,8 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { return $null } +. $PSScriptRoot\pipeline-logging-functions.ps1 + $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") $EngRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $ArtifactsDir = Join-Path $RepoRoot "artifacts" @@ -565,11 +573,8 @@ Create-Directory $ToolsetDir Create-Directory $TempDir Create-Directory $LogDir -if ($ci) { - Write-Host "##vso[task.setvariable variable=Artifacts]$ArtifactsDir" - Write-Host "##vso[task.setvariable variable=Artifacts.Toolset]$ToolsetDir" - Write-Host "##vso[task.setvariable variable=Artifacts.Log]$LogDir" - - $env:TEMP = $TempDir - $env:TMP = $TempDir -} +Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir +Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir +Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir +Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir +Write-PipelineSetVariable -Name 'TMP' -Value $TempDir diff --git a/eng/common/tools.sh b/eng/common/tools.sh index df3eb8bce07..f39aab57b99 100644 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -1,8 +1,20 @@ +#!/usr/bin/env bash + # Initialize variables if they aren't already defined. # CI mode - set to true on CI server for PR validation build or official build. ci=${ci:-false} +# Set to true to use the pipelines logger which will enable Azure logging output. +# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md +# This flag is meant as a temporary opt-opt for the feature while validate it across +# our consumers. It will be deleted in the future. +if [[ "$ci" == true ]]; then + pipelines_log=${pipelines_log:-true} +else + pipelines_log=${pipelines_log:-false} +fi + # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. configuration=${configuration:-'Debug'} @@ -65,7 +77,7 @@ function ReadGlobalVersion { local pattern="\"$key\" *: *\"(.*)\"" if [[ ! $line =~ $pattern ]]; then - echo "Error: Cannot find \"$key\" in $global_json_file" >&2 + Write-PipelineTelemetryError -category 'InitializeTools' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi @@ -126,7 +138,7 @@ function InitializeDotNetCli { if [[ "$install" == true ]]; then InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" else - echo "Unable to find dotnet with SDK version '$dotnet_sdk_version'" >&2 + Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" ExitWithExitCode 1 fi fi @@ -137,7 +149,7 @@ function InitializeDotNetCli { export PATH="$dotnet_root:$PATH" if [[ $ci == true ]]; then - # Make Sure that our bootstrapped dotnet cli is avaliable in future steps of the Azure Pipelines build + # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build echo "##vso[task.prependpath]$dotnet_root" echo "##vso[task.setvariable variable=DOTNET_MULTILEVEL_LOOKUP]0" echo "##vso[task.setvariable variable=DOTNET_SKIP_FIRST_TIME_EXPERIENCE]1" @@ -179,7 +191,7 @@ function InstallDotNet { fi bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg || { local exit_code=$? - echo "Failed to install dotnet SDK (exit code '$exit_code')." >&2 + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK (exit code '$exit_code')." ExitWithExitCode $exit_code } } @@ -216,6 +228,7 @@ function InitializeBuildTool { # return values _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" + _InitializeBuildToolFramework="netcoreapp2.1" } function GetNuGetPackageCachePath { @@ -264,7 +277,7 @@ function InitializeToolset { fi if [[ "$restore" != true ]]; then - echo "Toolset version $toolsetVersion has not been restored." >&2 + Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." ExitWithExitCode 2 fi @@ -276,12 +289,12 @@ function InitializeToolset { fi echo '' > "$proj" - MSBuild "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" + MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" local toolset_build_proj=`cat "$toolset_location_file"` if [[ ! -a "$toolset_build_proj" ]]; then - echo "Invalid toolset path: $toolset_build_proj" >&2 + Write-PipelineTelemetryError -category 'InitializeToolset' "Invalid toolset path: $toolset_build_proj" ExitWithExitCode 3 fi @@ -304,14 +317,27 @@ function StopProcesses { } function MSBuild { + local args=$@ + if [[ "$pipelines_log" == true ]]; then + InitializeBuildTool + InitializeToolset + local toolset_dir="${_InitializeToolset%/*}" + local logger_path="$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" + args=( "${args[@]}" "-logger:$logger_path" ) + fi + + MSBuild-Core ${args[@]} +} + +function MSBuild-Core { if [[ "$ci" == true ]]; then if [[ "$binary_log" != true ]]; then - echo "Binary log must be enabled in CI build." >&2 + Write-PipelineTaskError "Binary log must be enabled in CI build." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then - echo "Node reuse must be disabled in CI build." >&2 + Write-PipelineTaskError "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi @@ -325,11 +351,13 @@ function MSBuild { "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || { local exit_code=$? - echo "Build failed (exit code '$exit_code')." >&2 + Write-PipelineTaskError "Build failed (exit code '$exit_code')." ExitWithExitCode $exit_code } } +. "$scriptroot/pipeline-logging-functions.sh" + ResolvePath "${BASH_SOURCE[0]}" _script_dir=`dirname "$_ResolvePath"` @@ -362,4 +390,4 @@ mkdir -p "$log_dir" if [[ $ci == true ]]; then export TEMP="$temp_dir" export TMP="$temp_dir" -fi \ No newline at end of file +fi diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props index ae7833a18b8..5d145af11cf 100644 --- a/eng/targets/Settings.props +++ b/eng/targets/Settings.props @@ -7,4 +7,8 @@ $(ArtifactsBinDir) + + true + + diff --git a/fcs/Directory.Build.props b/fcs/Directory.Build.props index c56fd7cba6d..2841a5fb34f 100644 --- a/fcs/Directory.Build.props +++ b/fcs/Directory.Build.props @@ -31,7 +31,6 @@ $(FSharpSourcesRoot)\..\packages\FSharp.Compiler.Tools.4.1.27\tools fsi.exe - $(ArtifactsBinDir)\FSharp.Build\Proto\net472 4.6.2 net461 diff --git a/fcs/Directory.Build.targets b/fcs/Directory.Build.targets index 19b6fcd667d..3c45a524874 100644 --- a/fcs/Directory.Build.targets +++ b/fcs/Directory.Build.targets @@ -1,28 +1,29 @@ - + - <__TargetFilePath>@(CopyAndSubstituteText->'$(IntermediateOutputPath)%(Filename)%(Extension)') - <__TargetFileName>@(CopyAndSubstituteText->'%(Filename)%(Extension)') + <__TargetFilePath>@(NoneSubstituteText->'$(IntermediateOutputPath)%(Filename)%(Extension)') + <__TargetFileName>@(NoneSubstituteText->'%(Filename)%(Extension)') - <_ReplacementText>$([System.IO.File]::ReadAllText('%(CopyAndSubstituteText.FullPath)')) - <_ReplacementText Condition="'%(CopyAndSubstituteText.Pattern1)' != ''">$(_ReplacementText.Replace('%(CopyAndSubstituteText.Pattern1)', '%(CopyAndSubstituteText.Replacement1)')) - <_ReplacementText Condition="'%(CopyAndSubstituteText.Pattern2)' != ''">$(_ReplacementText.Replace('%(CopyAndSubstituteText.Pattern2)', '%(CopyAndSubstituteText.Replacement2)')) + <_ReplacementText>$([System.IO.File]::ReadAllText('%(NoneSubstituteText.FullPath)')) + <_ReplacementText Condition="'%(NoneSubstituteText.Pattern1)' != ''">$(_ReplacementText.Replace('%(NoneSubstituteText.Pattern1)', '%(NoneSubstituteText.Replacement1)')) + <_ReplacementText Condition="'%(NoneSubstituteText.Pattern2)' != ''">$(_ReplacementText.Replace('%(NoneSubstituteText.Pattern2)', '%(NoneSubstituteText.Replacement2)')) + + <_CopyToOutputDirectory Condition="'%(NoneSubstituteText.CopyToOutputDirectory)' != ''">%(NoneSubstituteText.CopyToOutputDirectory) + <_CopyToOutputDirectory Condition="'%(NoneSubstituteText.CopyToOutputDirectory)' == ''">Never - + - - + diff --git a/fcs/FSharp.Compiler.Service.MSBuild.v12/FSharp.Compiler.Service.MSBuild.v12.fsproj b/fcs/FSharp.Compiler.Service.MSBuild.v12/FSharp.Compiler.Service.MSBuild.v12.fsproj index 9f7b3d20b29..486401ae8be 100644 --- a/fcs/FSharp.Compiler.Service.MSBuild.v12/FSharp.Compiler.Service.MSBuild.v12.fsproj +++ b/fcs/FSharp.Compiler.Service.MSBuild.v12/FSharp.Compiler.Service.MSBuild.v12.fsproj @@ -17,7 +17,7 @@ F#, compiler, msbuild - + Service/MSBuildReferenceResolver.fs diff --git a/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 9f16ede65d4..c3125e32d13 100644 --- a/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -73,10 +73,10 @@ Program.fs - + {{FSCoreVersion}} $(FSCoreVersion) - + diff --git a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj index 6b2d04cb626..2bfaa4aa128 100644 --- a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj +++ b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj @@ -184,9 +184,6 @@ ErrorLogging/ErrorResolutionHints.fs - - ReferenceResolution/ReferenceResolver.fs - --unicode --lexlib Internal.Utilities.Text.Lexing AbsIL/illex.fsl @@ -267,6 +264,12 @@ AbsIL/ilreflect.fs + + ReferenceResolution/ReferenceResolver.fs + + + ReferenceResolution/SimulatedMSBuildReferenceResolver.fs + CompilerLocation/CompilerLocationUtils.fs @@ -588,9 +591,6 @@ Service/reshapedmsbuild.fs - - Service/SimulatedMSBuildReferenceResolver.fs - Service/ServiceDeclarationLists.fsi @@ -621,6 +621,12 @@ Service/QuickParse.fs + + Service/FSharpCheckerResults.fsi + + + Service/FSharpCheckerResults.fs + Service/service.fsi diff --git a/fcs/build.fsx b/fcs/build.fsx index ec53ced9c23..cf0e0d8deda 100644 --- a/fcs/build.fsx +++ b/fcs/build.fsx @@ -67,7 +67,7 @@ let releaseDir = Path.Combine(__SOURCE_DIRECTORY__, "../artifacts/bin/fcs/Releas let release = LoadReleaseNotes (__SOURCE_DIRECTORY__ + "/RELEASE_NOTES.md") let isAppVeyorBuild = buildServer = BuildServer.AppVeyor let isJenkinsBuild = buildServer = BuildServer.Jenkins -let isVersionTag tag = Version.TryParse tag |> fst +let isVersionTag (tag: string) = Version.TryParse tag |> fst let hasRepoVersionTag = isAppVeyorBuild && AppVeyorEnvironment.RepoTag && isVersionTag AppVeyorEnvironment.RepoTagName let assemblyVersion = if hasRepoVersionTag then AppVeyorEnvironment.RepoTagName else release.NugetVersion diff --git a/global.json b/global.json index 6d63a523a7d..899ac158788 100644 --- a/global.json +++ b/global.json @@ -10,7 +10,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19264.13", + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19320.1", "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19069.2" } } diff --git a/proto.proj b/proto.proj index 84103f6fdf8..b0ee288977f 100644 --- a/proto.proj +++ b/proto.proj @@ -28,6 +28,10 @@ + + + + diff --git a/src/buildtools/buildtools.proj b/src/buildtools/buildtools.proj index 593f086dd07..630bb678561 100644 --- a/src/buildtools/buildtools.proj +++ b/src/buildtools/buildtools.proj @@ -2,7 +2,8 @@ Debug - + true + @@ -10,23 +11,23 @@ - + - + - + - + - + diff --git a/src/buildtools/buildtools.targets b/src/buildtools/buildtools.targets index 303ab00825d..185fd4d0599 100644 --- a/src/buildtools/buildtools.targets +++ b/src/buildtools/buildtools.targets @@ -20,7 +20,7 @@ BeforeTargets="CoreCompile"> - $(ArtifactsDir)\Bootstrap\fslex.dll + $(ArtifactsDir)\Bootstrap\fslex\fslex.dll @@ -43,7 +43,7 @@ BeforeTargets="CoreCompile"> - $(ArtifactsDir)\Bootstrap\fsyacc.dll + $(ArtifactsDir)\Bootstrap\fsyacc\fsyacc.dll diff --git a/src/fsharp/FSharp.Build/FSharp.Build.fsproj b/src/fsharp/FSharp.Build/FSharp.Build.fsproj index ef4d013dc1f..0bf32ed19a7 100644 --- a/src/fsharp/FSharp.Build/FSharp.Build.fsproj +++ b/src/fsharp/FSharp.Build/FSharp.Build.fsproj @@ -29,13 +29,13 @@ - + Microsoft.FSharp.NetSdk.props {{FSharpCoreShippedPackageVersion}} $(FSharpCoreShippedPackageVersion) {{FSharpCorePreviewPackageVersion}} $(FSharpCorePreviewPackageVersion) - + diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj b/src/fsharp/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj index 1e0ca2f912a..3180a0a52fb 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj +++ b/src/fsharp/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj @@ -11,8 +11,11 @@ - + + + + @@ -28,4 +31,19 @@ + + + <_PublishedProjectOutputGroupFiles Include="$(PublishDir)\**" /> + + + + + + + + + diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/JsonDUConverter.fs b/src/fsharp/FSharp.Compiler.LanguageServer/JsonDUConverter.fs new file mode 100644 index 00000000000..ae8575195dd --- /dev/null +++ b/src/fsharp/FSharp.Compiler.LanguageServer/JsonDUConverter.fs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.LanguageServer + +open System +open FSharp.Reflection +open Newtonsoft.Json + +type JsonDUConverter() = + inherit JsonConverter() + override __.CanConvert(typ) = FSharpType.IsUnion(typ) + override __.WriteJson(writer, value, _serializer) = + writer.WriteValue(value.ToString().ToLowerInvariant()) + override __.ReadJson(reader, typ, x, serializer) = + let cases = FSharpType.GetUnionCases(typ) + let str = serializer.Deserialize(reader, typeof) :?> string + let case = cases |> Array.find (fun c -> String.Compare(c.Name, str, StringComparison.OrdinalIgnoreCase) = 0) + FSharpValue.MakeUnion(case, [||]) diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/JsonOptionConverter.fs b/src/fsharp/FSharp.Compiler.LanguageServer/JsonOptionConverter.fs new file mode 100644 index 00000000000..937dda00e46 --- /dev/null +++ b/src/fsharp/FSharp.Compiler.LanguageServer/JsonOptionConverter.fs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.LanguageServer + +open System +open FSharp.Reflection +open Newtonsoft.Json + +type JsonOptionConverter() = + inherit JsonConverter() + override __.CanConvert(typ) = typ.IsGenericType && typ.GetGenericTypeDefinition() = typedefof> + override __.WriteJson(writer, value, serializer) = + let value = match value with + | null -> null + | _ -> + let _, fields = FSharpValue.GetUnionFields(value, value.GetType()) + fields.[0] + serializer.Serialize(writer, value) + override __.ReadJson(reader, typ, _, serializer) = + let innerType = typ.GetGenericArguments().[0] + let innerType = + if innerType.IsValueType then (typedefof>).MakeGenericType([|innerType|]) + else innerType + let value = serializer.Deserialize(reader, innerType) + let cases = FSharpType.GetUnionCases(typ) + if value = null then FSharpValue.MakeUnion(cases.[0], [||]) + else FSharpValue.MakeUnion(cases.[1], [|value|]) diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/LspExternalAccess.fs b/src/fsharp/FSharp.Compiler.LanguageServer/LspExternalAccess.fs new file mode 100644 index 00000000000..e6fa760d1cc --- /dev/null +++ b/src/fsharp/FSharp.Compiler.LanguageServer/LspExternalAccess.fs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.LanguageServer + +open StreamJsonRpc + +[] +module FunctionNames = + [] + let OptionsSet = "options/set" + +type Options = + { usePreviewTextHover: bool } + static member Default() = + { usePreviewTextHover = false } + +module Extensions = + type JsonRpc with + member jsonRpc.SetOptionsAsync (options: Options) = + async { + do! jsonRpc.InvokeAsync(OptionsSet, options) |> Async.AwaitTask + } diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs b/src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs index 7a919bc74de..97479eef267 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs +++ b/src/fsharp/FSharp.Compiler.LanguageServer/LspTypes.fs @@ -2,8 +2,11 @@ namespace FSharp.Compiler.LanguageServer -// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specification. The properties on these -// types are camlCased to match the underlying JSON properties to avoid attributes on every field: +open Newtonsoft.Json.Linq +open Newtonsoft.Json + +// Interfaces as defined at https://microsoft.github.io/language-server-protocol/specification. The properties on +// these types are camlCased to match the underlying JSON properties to avoid attributes on every field: // [] /// Represents a zero-based line and column of a text document. @@ -27,11 +30,11 @@ type DiagnosticRelatedInformation = type Diagnostic = { range: Range - severity: int + severity: int option code: string - source: string // "F#" + source: string option // "F#" message: string - relatedInformation: DiagnosticRelatedInformation[] } + relatedInformation: DiagnosticRelatedInformation[] option } static member Error = 1 static member Warning = 2 static member Information = 3 @@ -41,9 +44,23 @@ type PublishDiagnosticsParams = { uri: DocumentUri diagnostics: Diagnostic[] } -type InitializeParams = string // TODO: +type ClientCapabilities = + { workspace: JToken option // TODO: WorkspaceClientCapabilities + textDocument: JToken option // TODO: TextDocumentCapabilities + experimental: JToken option + supportsVisualStudioExtensions: bool option } + +[)>] +type Trace = + | Off + | Messages + | Verbose -// Note, this type has many more optional values that can be expanded as support is added. +type WorkspaceFolder = + { uri: DocumentUri + name: string } + +/// Note, this type has many more optional values that can be expanded as support is added. type ServerCapabilities = { hoverProvider: bool } static member DefaultCapabilities() = @@ -52,6 +69,7 @@ type ServerCapabilities = type InitializeResult = { capabilities: ServerCapabilities } +[)>] type MarkupKind = | PlainText | Markdown @@ -66,7 +84,3 @@ type Hover = type TextDocumentIdentifier = { uri: DocumentUri } - -type TextDocumentPositionParams = - { textDocument: TextDocumentIdentifier - position: Position } diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs b/src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs index 80e06e9781b..d1e614cb297 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs +++ b/src/fsharp/FSharp.Compiler.LanguageServer/Methods.fs @@ -2,21 +2,66 @@ namespace FSharp.Compiler.LanguageServer +open System +open System.Runtime.InteropServices +open System.Threading +open Newtonsoft.Json.Linq open StreamJsonRpc // https://microsoft.github.io/language-server-protocol/specification type Methods(state: State) = + /// Helper to run Async<'T> with a CancellationToken. + let runAsync (cancellationToken: CancellationToken) (computation: Async<'T>) = Async.StartAsTask(computation, cancellationToken=cancellationToken) + + member __.State = state + + //-------------------------------------------------------------------------- + // official LSP methods + //-------------------------------------------------------------------------- + [] - member __.Initialize (args: InitializeParams) = - async { - // note, it's important that this method is `async` because unit tests can then properly verify that the - // JSON RPC handling of async methods is correct - return ServerCapabilities.DefaultCapabilities() - } |> Async.StartAsTask + member __.Initialize + ( + processId: Nullable, + [] rootPath: string, + [] rootUri: DocumentUri, + [] initializationOptions: JToken, + capabilities: ClientCapabilities, + [] trace: string, + [] workspaceFolders: WorkspaceFolder[] + ) = + { InitializeResult.capabilities = ServerCapabilities.DefaultCapabilities() } + + [] + member __.Initialized () = () [] - member __.Shutdown() = state.DoShutdown() + member __.Shutdown(): obj = state.DoShutdown(); null + + [] + member __.Exit() = state.DoExit() + + [] + member __.cancelRequest (id: JToken) = state.DoCancel() [] - member __.TextDocumentHover (args: TextDocumentPositionParams) = TextDocument.Hover(state, args) |> Async.StartAsTask + member __.TextDocumentHover + ( + textDocument: TextDocumentIdentifier, + position: Position, + [] cancellationToken: CancellationToken + ) = + TextDocument.Hover state textDocument position |> runAsync cancellationToken + + //-------------------------------------------------------------------------- + // unofficial LSP methods that we implement separately + //-------------------------------------------------------------------------- + + [] + member __.OptionsSet + ( + options: Options + ) = + sprintf "got options %A" options |> Console.Error.WriteLine + state.Options <- options diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/Server.fs b/src/fsharp/FSharp.Compiler.LanguageServer/Server.fs index 87f6d1c2ffe..071ad6b2264 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/Server.fs +++ b/src/fsharp/FSharp.Compiler.LanguageServer/Server.fs @@ -8,9 +8,13 @@ open StreamJsonRpc type Server(sendingStream: Stream, receivingStream: Stream) = + let formatter = JsonMessageFormatter() + let converter = JsonOptionConverter() // special handler to convert between `Option<'T>` and `obj/null`. + do formatter.JsonSerializer.Converters.Add(converter) + let handler = new HeaderDelimitedMessageHandler(sendingStream, receivingStream, formatter) let state = State() let methods = Methods(state) - let rpc = new JsonRpc(sendingStream, receivingStream, methods) + let rpc = new JsonRpc(handler, methods) member __.StartListening() = rpc.StartListening() @@ -18,6 +22,7 @@ type Server(sendingStream: Stream, receivingStream: Stream) = member __.WaitForExitAsync() = async { do! Async.AwaitEvent (state.Shutdown) + do! Async.AwaitEvent (state.Exit) } interface IDisposable with diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/State.fs b/src/fsharp/FSharp.Compiler.LanguageServer/State.fs index 53cacbb8c0d..5ca2d3f845f 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/State.fs +++ b/src/fsharp/FSharp.Compiler.LanguageServer/State.fs @@ -5,8 +5,22 @@ namespace FSharp.Compiler.LanguageServer type State() = let shutdownEvent = new Event<_>() + let exitEvent = new Event<_>() + let cancelEvent = new Event<_>() [] member __.Shutdown = shutdownEvent.Publish + [] + member __.Exit = exitEvent.Publish + + [] + member __.Cancel = cancelEvent.Publish + member __.DoShutdown() = shutdownEvent.Trigger() + + member __.DoExit() = exitEvent.Trigger() + + member __.DoCancel() = cancelEvent.Trigger() + + member val Options = Options.Default() with get, set diff --git a/src/fsharp/FSharp.Compiler.LanguageServer/TextDocument.fs b/src/fsharp/FSharp.Compiler.LanguageServer/TextDocument.fs index eef57581c5b..0c737965051 100644 --- a/src/fsharp/FSharp.Compiler.LanguageServer/TextDocument.fs +++ b/src/fsharp/FSharp.Compiler.LanguageServer/TextDocument.fs @@ -2,26 +2,23 @@ namespace FSharp.Compiler.LanguageServer +open System + module TextDocument = - let Hover(state: State, args: TextDocumentPositionParams) = + let Hover (state: State) (textDocument: TextDocumentIdentifier) (position: Position) = async { - return { - Hover.contents = { - MarkupContent.kind = MarkupKind.PlainText - value = "TODO" + Console.Error.WriteLine("hover at " + position.line.ToString() + "," + position.character.ToString()) + if not state.Options.usePreviewTextHover then return None + else + let startCol, endCol = + if position.character = 0 then 0, 1 + else position.character, position.character + 1 + return Some { contents = { kind = MarkupKind.PlainText + value = "serving textDocument/hover from LSP" } + range = Some { start = { line = position.line; character = startCol } + ``end`` = { line = position.line; character = endCol } } } - range = Some({ - Range.start = { - Position.line = 0 - character = 0 - } - ``end`` = { - Position.line = 0 - character = 0 - } - }) - } } let PublishDiagnostics(state: State) = diff --git a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj index 1bb82db552e..d468a8a9968 100644 --- a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj +++ b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj @@ -40,21 +40,10 @@ - - - - - - - - - - - @@ -193,10 +182,7 @@ ErrorLogging\ErrorResolutionHints.fs - - - ReferenceResolution\ReferenceResolver.fs - + --unicode --lexlib Internal.Utilities.Text.Lexing AbsIL\illex.fsl @@ -277,6 +263,21 @@ AbsIL\ilreflect.fs + + ReferenceResolution\ReferenceResolver.fs + + + + + ReferenceResolution/LegacyMSBuildReferenceResolver.fsi + + + ReferenceResolution/LegacyMSBuildReferenceResolver.fs + + + + ReferenceResolution/SimulatedMSBuildReferenceResolver.fs + CompilerLocation\CompilerLocationUtils.fs @@ -632,9 +633,6 @@ Service/reshapedmsbuild.fs - - Service/SimulatedMSBuildReferenceResolver.fs - Service/ExternalSymbol.fsi @@ -647,6 +645,12 @@ Service/QuickParse.fs + + Service/FSharpCheckerResults.fsi + + + Service/FSharpCheckerResults.fs + Service/service.fsi @@ -677,7 +681,7 @@ Service/ServiceAnalysis.fs - + FSIstrings.txt @@ -688,10 +692,6 @@ InteractiveSession/fsi.fs - - - Misc/MSBuildReferenceResolver.fs - Misc/LegacyHostedCompilerForTesting.fs diff --git a/src/fsharp/FSharp.Core/local.fs b/src/fsharp/FSharp.Core/local.fs index 9fb452907f4..0f5c69160d0 100644 --- a/src/fsharp/FSharp.Core/local.fs +++ b/src/fsharp/FSharp.Core/local.fs @@ -294,16 +294,16 @@ module internal List = | [], xs2 -> invalidArgDifferentListLength "list1" "list2" xs2.Length | xs1, [] -> invalidArgDifferentListLength "list2" "list1" xs1.Length - let rec map3ToFreshConsTail cons (f:OptimizedClosures.FSharpFunc<_, _, _, _>) xs1 xs2 xs3 = + let rec map3ToFreshConsTail cons (f:OptimizedClosures.FSharpFunc<_, _, _, _>) xs1 xs2 xs3 cut = match xs1, xs2, xs3 with | [], [], [] -> setFreshConsTail cons [] | h1 :: t1, h2 :: t2, h3 :: t3 -> let cons2 = freshConsNoTail (f.Invoke(h1, h2, h3)) setFreshConsTail cons cons2 - map3ToFreshConsTail cons2 f t1 t2 t3 + map3ToFreshConsTail cons2 f t1 t2 t3 (cut + 1) | xs1, xs2, xs3 -> - invalidArg3ListsDifferent "list1" "list2" "list3" xs1.Length xs2.Length xs3.Length + invalidArg3ListsDifferent "list1" "list2" "list3" (xs1.Length + cut) (xs2.Length + cut) (xs3.Length + cut) let map3 mapping xs1 xs2 xs3 = match xs1, xs2, xs3 with @@ -311,7 +311,7 @@ module internal List = | h1 :: t1, h2 :: t2, h3 :: t3 -> let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) let cons = freshConsNoTail (f.Invoke(h1, h2, h3)) - map3ToFreshConsTail cons f t1 t2 t3 + map3ToFreshConsTail cons f t1 t2 t3 1 cons | xs1, xs2, xs3 -> invalidArg3ListsDifferent "list1" "list2" "list3" xs1.Length xs2.Length xs3.Length diff --git a/src/fsharp/MSBuildReferenceResolver.fs b/src/fsharp/LegacyMSBuildReferenceResolver.fs similarity index 99% rename from src/fsharp/MSBuildReferenceResolver.fs rename to src/fsharp/LegacyMSBuildReferenceResolver.fs index 1a49c6a0fca..8425f6197ba 100644 --- a/src/fsharp/MSBuildReferenceResolver.fs +++ b/src/fsharp/LegacyMSBuildReferenceResolver.fs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -module internal FSharp.Compiler.MSBuildReferenceResolver +module LegacyMSBuildReferenceResolver open System open System.IO @@ -16,6 +16,7 @@ module internal FSharp.Compiler.MSBuildReferenceResolver open Microsoft.Build.Tasks open Microsoft.Build.Utilities open Microsoft.Build.Framework + open FSharp.Compiler // Reflection wrapper for properties type System.Object with @@ -367,7 +368,7 @@ module internal FSharp.Compiler.MSBuildReferenceResolver resolvedFiles - let Resolver = + let getResolver () = { new ReferenceResolver.Resolver with member __.HighestInstalledNetFrameworkVersion() = HighestInstalledRefAssembliesOrDotNETFramework() member __.DotNetFrameworkReferenceAssembliesRootDirectory = DotNetFrameworkReferenceAssembliesRootDirectory diff --git a/src/fsharp/LegacyMSBuildReferenceResolver.fsi b/src/fsharp/LegacyMSBuildReferenceResolver.fsi new file mode 100644 index 00000000000..1b4aa72858d --- /dev/null +++ b/src/fsharp/LegacyMSBuildReferenceResolver.fsi @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. +module LegacyMSBuildReferenceResolver + +open FSharp.Compiler + +val getResolver: unit -> ReferenceResolver.Resolver \ No newline at end of file diff --git a/src/fsharp/NicePrint.fs b/src/fsharp/NicePrint.fs index ab45f12c137..5d56543e992 100644 --- a/src/fsharp/NicePrint.fs +++ b/src/fsharp/NicePrint.fs @@ -1405,12 +1405,24 @@ module InfoMemberPrinting = | None -> tagProperty | Some vref -> tagProperty >> mkNav vref.DefinitionRange let nameL = DemangleOperatorNameAsLayout tagProp pinfo.PropertyName + let getterSetter = + match pinfo.HasGetter, pinfo.HasSetter with + | (true, false) -> + wordL (tagKeyword "with") ^^ wordL (tagText "get") + | (false, true) -> + wordL (tagKeyword "with") ^^ wordL (tagText "set") + | (true, true) -> + wordL (tagKeyword "with") ^^ wordL (tagText "get, set") + | (false, false) -> + emptyL + wordL (tagText (FSComp.SR.typeInfoProperty())) ^^ layoutTyconRef denv pinfo.ApparentEnclosingTyconRef ^^ SepL.dot ^^ nameL ^^ RightL.colon ^^ - layoutType denv rty + layoutType denv rty ^^ + getterSetter let formatMethInfoToBufferFreeStyle amap m denv os (minfo: MethInfo) = let _, resL = prettyLayoutOfMethInfoFreeStyle amap m denv emptyTyparInst minfo diff --git a/src/fsharp/PatternMatchCompilation.fs b/src/fsharp/PatternMatchCompilation.fs index ddd8fc129c7..0bf4df1c256 100644 --- a/src/fsharp/PatternMatchCompilation.fs +++ b/src/fsharp/PatternMatchCompilation.fs @@ -30,7 +30,6 @@ type ActionOnFailure = | FailFilter [] -/// Represents type-checked patterns type Pattern = | TPat_const of Const * range | TPat_wild of range (* note = TPat_disjs([], m), but we haven't yet removed that duplication *) @@ -720,7 +719,9 @@ let rec erasePartialPatterns inpp = | TPat_range _ | TPat_null _ | TPat_isinst _ -> inpp -and erasePartials inps = List.map erasePartialPatterns inps + +and erasePartials inps = + List.map erasePartialPatterns inps //--------------------------------------------------------------------------- @@ -740,118 +741,115 @@ let CompilePatternBasic (clausesL: TypedMatchClause list) inputTy resultTy = - // Add the targets to a match builder - // Note the input expression has already been evaluated and saved into a variable. - // Hence no need for a new sequence point. - let mbuilder = new MatchBuilder(NoSequencePointAtInvisibleBinding, exprm) - clausesL |> List.iteri (fun _i c -> mbuilder.AddTarget c.Target |> ignore) - - // Add the incomplete or rethrow match clause on demand, printing a - // warning if necessary (only if it is ever exercised) - let incompleteMatchClauseOnce = ref None + // Add the targets to a match builder. + // Note the input expression has already been evaluated and saved into a variable, + // hence no need for a new sequence point. + let matchBuilder = MatchBuilder (NoSequencePointAtInvisibleBinding, exprm) + clausesL |> List.iter (fun c -> matchBuilder.AddTarget c.Target |> ignore) + + // Add the incomplete or rethrow match clause on demand, + // printing a warning if necessary (only if it is ever exercised). + let mutable incompleteMatchClauseOnce = None let getIncompleteMatchClause refuted = - // This is lazy because emit a - // warning when the lazy thunk gets evaluated - match !incompleteMatchClauseOnce with + // This is lazy because emit a warning when the lazy thunk gets evaluated. + match incompleteMatchClauseOnce with | None -> - (* Emit the incomplete match warning *) - if warnOnIncomplete then - match actionOnFailure with - | ThrowIncompleteMatchException | IgnoreWithWarning -> - let ignoreWithWarning = (actionOnFailure = IgnoreWithWarning) - match ShowCounterExample g denv matchm refuted with - | Some(text, failingWhenClause, true) -> - warning (EnumMatchIncomplete(ignoreWithWarning, Some(text, failingWhenClause), matchm)) - | Some(text, failingWhenClause, false) -> - warning (MatchIncomplete(ignoreWithWarning, Some(text, failingWhenClause), matchm)) - | None -> - warning (MatchIncomplete(ignoreWithWarning, None, matchm)) - | _ -> - () - - let throwExpr = - match actionOnFailure with - | FailFilter -> - // Return 0 from the .NET exception filter - mkInt g matchm 0 - - | Rethrow -> - // Rethrow unmatched try-catch exn. No sequence point at the target since its not - // real code. - mkReraise matchm resultTy - - | Throw -> - // We throw instead of rethrow on unmatched try-catch in a computation expression. But why? - // Because this isn't a real .NET exception filter/handler but just a function we're passing - // to a computation expression builder to simulate one. - mkThrow matchm resultTy (exprForVal matchm origInputVal) - - | ThrowIncompleteMatchException -> - mkThrow matchm resultTy - (mkExnExpr(mk_MFCore_tcref g.fslibCcu "MatchFailureException", - [ mkString g matchm matchm.FileName - mkInt g matchm matchm.StartLine - mkInt g matchm matchm.StartColumn], matchm)) - - | IgnoreWithWarning -> - mkUnit g matchm - - // We don't emit a sequence point at any of the above cases because they don't correspond to - // user code. - // - // Note we don't emit sequence points at either the succeeding or failing - // targets of filters since if the exception is filtered successfully then we - // will run the handler and hit the sequence point there. - // That sequence point will have the pattern variables bound, which is exactly what we want. - let tg = TTarget(List.empty, throwExpr, SuppressSequencePointAtTarget ) - mbuilder.AddTarget tg |> ignore - let clause = TClause(TPat_wild matchm, None, tg, matchm) - incompleteMatchClauseOnce := Some clause - clause + // Emit the incomplete match warning. + if warnOnIncomplete then + match actionOnFailure with + | ThrowIncompleteMatchException | IgnoreWithWarning -> + let ignoreWithWarning = (actionOnFailure = IgnoreWithWarning) + match ShowCounterExample g denv matchm refuted with + | Some(text, failingWhenClause, true) -> + warning (EnumMatchIncomplete(ignoreWithWarning, Some(text, failingWhenClause), matchm)) + | Some(text, failingWhenClause, false) -> + warning (MatchIncomplete(ignoreWithWarning, Some(text, failingWhenClause), matchm)) + | None -> + warning (MatchIncomplete(ignoreWithWarning, None, matchm)) + | _ -> + () + + let throwExpr = + match actionOnFailure with + | FailFilter -> + // Return 0 from the .NET exception filter. + mkInt g matchm 0 + + | Rethrow -> + // Rethrow unmatched try-catch exn. No sequence point at the target since its not real code. + mkReraise matchm resultTy + + | Throw -> + // We throw instead of rethrow on unmatched try-catch in a computation expression. But why? + // Because this isn't a real .NET exception filter/handler but just a function we're passing + // to a computation expression builder to simulate one. + mkThrow matchm resultTy (exprForVal matchm origInputVal) + + | ThrowIncompleteMatchException -> + mkThrow matchm resultTy + (mkExnExpr(mk_MFCore_tcref g.fslibCcu "MatchFailureException", + [ mkString g matchm matchm.FileName + mkInt g matchm matchm.StartLine + mkInt g matchm matchm.StartColumn], matchm)) + + | IgnoreWithWarning -> + mkUnit g matchm + + // We don't emit a sequence point at any of the above cases because they don't correspond to user code. + // + // Note we don't emit sequence points at either the succeeding or failing targets of filters since if + // the exception is filtered successfully then we will run the handler and hit the sequence point there. + // That sequence point will have the pattern variables bound, which is exactly what we want. + let tg = TTarget(List.empty, throwExpr, SuppressSequencePointAtTarget) + let _ = matchBuilder.AddTarget tg + let clause = TClause(TPat_wild matchm, None, tg, matchm) + incompleteMatchClauseOnce <- Some clause + clause | Some c -> c - // Helpers to get the variables bound at a target. We conceptually add a dummy clause that will always succeed with a "throw" + // Helpers to get the variables bound at a target. + // We conceptually add a dummy clause that will always succeed with a "throw" let clausesA = Array.ofList clausesL - let nclauses = clausesA.Length + let nClauses = clausesA.Length let GetClause i refuted = - if i < nclauses then + if i < nClauses then clausesA.[i] - elif i = nclauses then getIncompleteMatchClause refuted + elif i = nClauses then getIncompleteMatchClause refuted else failwith "GetClause" let GetValsBoundByClause i refuted = (GetClause i refuted).BoundVals let GetWhenGuardOfClause i refuted = (GetClause i refuted).GuardExpr - // Different uses of parameterized active patterns have different identities as far as paths - // are concerned. Here we generate unique numbers that are completely different to any stamp - // by usig negative numbers. + // Different uses of parameterized active patterns have different identities as far as paths are concerned. + // Here we generate unique numbers that are completely different to any stamp by using negative numbers. let genUniquePathId() = - (newUnique()) - // Build versions of these functions which apply a dummy instantiation to the overall type arguments + // Build versions of these functions which apply a dummy instantiation to the overall type arguments. let GetSubExprOfInput, getDiscrimOfPattern = let tyargs = List.map (fun _ -> g.unit_ty) origInputValTypars let unit_tpinst = mkTyparInst origInputValTypars tyargs GetSubExprOfInput g (origInputValTypars, tyargs, unit_tpinst), getDiscrimOfPattern g unit_tpinst - // The main recursive loop of the pattern match compiler + // The main recursive loop of the pattern match compiler. let rec InvestigateFrontiers refuted frontiers = match frontiers with | [] -> failwith "CompilePattern: compile - empty clauses: at least the final clause should always succeed" - | (Frontier (i, active, valMap)) :: rest -> + | Frontier (i, active, valMap) :: rest -> - // Check to see if we've got a succeeding clause. There may still be a 'when' condition for the clause + // Check to see if we've got a succeeding clause. There may still be a 'when' condition for the clause. match active with | [] -> CompileSuccessPointAndGuard i refuted valMap rest | _ -> - (* Otherwise choose a point (i.e. a path) to investigate. *) + // Otherwise choose a point (i.e. a path) to investigate. let (Active(path, subexpr, pat)) = ChooseInvestigationPointLeftToRight frontiers match pat with // All these constructs should have been eliminated in BindProjectionPattern - | TPat_as _ | TPat_tuple _ | TPat_wild _ | TPat_disjs _ | TPat_conjs _ | TPat_recd _ -> failwith "Unexpected pattern" + | TPat_as _ | TPat_tuple _ | TPat_wild _ | TPat_disjs _ | TPat_conjs _ | TPat_recd _ -> + failwith "Unexpected pattern" - // Leaving the ones where we have real work to do + // Leaving the ones where we have real work to do. | _ -> let simulSetOfEdgeDiscrims, fallthroughPathFrontiers = ChooseSimultaneousEdges frontiers path @@ -879,7 +877,6 @@ let CompilePatternBasic finalDecisionTree and CompileSuccessPointAndGuard i refuted valMap rest = - let vs2 = GetValsBoundByClause i refuted let es2 = vs2 |> List.map (fun v -> @@ -907,25 +904,25 @@ let CompilePatternBasic | None -> rhs' - /// Select the set of discriminators which we can handle in one test, or as a series of - /// iterated tests, e.g. in the case of TPat_isinst. Ensure we only take at most one class of `TPat_query` at a time. + /// Select the set of discriminators which we can handle in one test, or as a series of iterated tests, + /// e.g. in the case of TPat_isinst. Ensure we only take at most one class of `TPat_query` at a time. /// Record the rule numbers so we know which rule the TPat_query cam from, so that when we project through /// the frontier we only project the right rule. and ChooseSimultaneousEdges frontiers path = frontiers |> chooseSimultaneousEdgeSet None (fun prevOpt (Frontier (i', active', _)) -> - if isMemOfActives path active' then - let p = lookupActive path active' |> snd - match getDiscrimOfPattern p with - | Some discrim -> - if (match prevOpt with None -> true | Some (EdgeDiscrim(_, discrimPrev, _)) -> discrimsHaveSameSimultaneousClass g discrim discrimPrev) then - Some (EdgeDiscrim(i', discrim, p.Range)), true - else - None, false - - | None -> - None, true - else - None, true) + if isMemOfActives path active' then + let _, p = lookupActive path active' + match getDiscrimOfPattern p with + | Some discrim -> + if (match prevOpt with None -> true | Some (EdgeDiscrim(_, discrimPrev, _)) -> discrimsHaveSameSimultaneousClass g discrim discrimPrev) then + Some (EdgeDiscrim(i', discrim, p.Range)), true + else + None, false + + | None -> + None, true + else + None, true) and IsCopyableInputExpr origInputExpr = match origInputExpr with @@ -1298,13 +1295,13 @@ let CompilePatternBasic mkFrontiers investigations i) |> List.concat) @ - mkFrontiers [([], ValMap<_>.Empty)] nclauses) + mkFrontiers [([], ValMap<_>.Empty)] nClauses) let dtree = InvestigateFrontiers [] frontiers - let targets = mbuilder.CloseTargets() + let targets = matchBuilder.CloseTargets() // Report unused targets @@ -1320,13 +1317,13 @@ let isPartialOrWhenClause (c: TypedMatchClause) = isPatternPartial c.Pattern || let rec CompilePattern g denv amap exprm matchm warnOnUnused actionOnFailure (origInputVal, origInputValTypars, origInputExprOpt) (clausesL: TypedMatchClause list) inputTy resultTy = - match clausesL with - | _ when List.exists isPartialOrWhenClause clausesL -> + match clausesL with + | _ when List.exists isPartialOrWhenClause clausesL -> // Partial clauses cause major code explosion if treated naively // Hence treat any pattern matches with any partial clauses clause-by-clause // First make sure we generate at least some of the obvious incomplete match warnings. - let warnOnUnused = false in (* we can't turn this on since we're pretending all partial's fail in order to control the complexity of this. *) + let warnOnUnused = false // we can't turn this on since we're pretending all partials fail in order to control the complexity of this. let warnOnIncomplete = true let clausesPretendAllPartialFail = List.collect (fun (TClause(p, whenOpt, tg, m)) -> [TClause(erasePartialPatterns p, whenOpt, tg, m)]) clausesL let _ = CompilePatternBasic g denv amap exprm matchm warnOnUnused warnOnIncomplete actionOnFailure (origInputVal, origInputValTypars, origInputExprOpt) clausesPretendAllPartialFail inputTy resultTy @@ -1334,19 +1331,18 @@ let rec CompilePattern g denv amap exprm matchm warnOnUnused actionOnFailure (o let rec atMostOnePartialAtATime clauses = match List.takeUntil isPartialOrWhenClause clauses with - | l, [] -> + | l, [] -> CompilePatternBasic g denv amap exprm matchm warnOnUnused warnOnIncomplete actionOnFailure (origInputVal, origInputValTypars, origInputExprOpt) l inputTy resultTy | l, (h :: t) -> - // Add the partial clause + // Add the partial clause. doGroupWithAtMostOnePartial (l @ [h]) t and doGroupWithAtMostOnePartial group rest = + // Compile the remaining clauses. + let decisionTree, targets = atMostOnePartialAtATime rest - // Compile the remaining clauses - let dtree, targets = atMostOnePartialAtATime rest - - // Make the expression that represents the remaining cases of the pattern match - let expr = mkAndSimplifyMatch NoSequencePointAtInvisibleBinding exprm matchm resultTy dtree targets + // Make the expression that represents the remaining cases of the pattern match. + let expr = mkAndSimplifyMatch NoSequencePointAtInvisibleBinding exprm matchm resultTy decisionTree targets // If the remainder of the match boiled away to nothing interesting. // We measure this simply by seeing if the range of the resulting expression is identical to matchm. @@ -1362,5 +1358,5 @@ let rec CompilePattern g denv amap exprm matchm warnOnUnused actionOnFailure (o atMostOnePartialAtATime clausesL - | _ -> - CompilePatternBasic g denv amap exprm matchm warnOnUnused true actionOnFailure (origInputVal, origInputValTypars, origInputExprOpt) clausesL inputTy resultTy + | _ -> + CompilePatternBasic g denv amap exprm matchm warnOnUnused true actionOnFailure (origInputVal, origInputValTypars, origInputExprOpt) clausesL inputTy resultTy diff --git a/src/fsharp/PatternMatchCompilation.fsi b/src/fsharp/PatternMatchCompilation.fsi index 8394611a16f..b35b5f42b27 100644 --- a/src/fsharp/PatternMatchCompilation.fsi +++ b/src/fsharp/PatternMatchCompilation.fsi @@ -9,7 +9,6 @@ open FSharp.Compiler.Tastops open FSharp.Compiler.TcGlobals open FSharp.Compiler.Range - /// What should the decision tree contain for any incomplete match? type ActionOnFailure = | ThrowIncompleteMatchException @@ -23,19 +22,20 @@ type ActionOnFailure = type Pattern = | TPat_const of Const * range | TPat_wild of range - | TPat_as of Pattern * PatternValBinding * range - | TPat_disjs of Pattern list * range - | TPat_conjs of Pattern list * range + | TPat_as of Pattern * PatternValBinding * range + | TPat_disjs of Pattern list * range + | TPat_conjs of Pattern list * range | TPat_query of (Expr * TType list * (ValRef * TypeInst) option * int * PrettyNaming.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 - | TPat_array of Pattern list * TType * range + | TPat_tuple of TupInfo * Pattern list * TType list * range + | TPat_array of Pattern list * TType * range | TPat_recd of TyconRef * TypeInst * Pattern list * range | TPat_range of char * char * range | TPat_null of range | TPat_isinst of TType * TType * PatternValBinding option * range - member Range : range + + member Range: range and PatternValBinding = | PBind of Val * TypeScheme @@ -43,10 +43,10 @@ and PatternValBinding = and TypedMatchClause = | TClause of Pattern * Expr option * DecisionTreeTarget * range -val ilFieldToTastConst : ILFieldInit -> Tast.Const +val ilFieldToTastConst: ILFieldInit -> Tast.Const /// Compile a pattern into a decision tree and a set of targets. -val internal CompilePattern : +val internal CompilePattern: TcGlobals -> DisplayEnv -> Import.ImportMap -> @@ -66,8 +66,8 @@ val internal CompilePattern : TType -> // result type TType -> - // produce TAST nodes - DecisionTree * DecisionTreeTarget list + // produce TAST nodes + DecisionTree * DecisionTreeTarget list exception internal MatchIncomplete of bool * (string * bool) option * range exception internal RuleNeverMatched of range diff --git a/src/fsharp/SimulatedMSBuildReferenceResolver.fs b/src/fsharp/SimulatedMSBuildReferenceResolver.fs index ef0fdf13c4b..8b47ad9bff7 100644 --- a/src/fsharp/SimulatedMSBuildReferenceResolver.fs +++ b/src/fsharp/SimulatedMSBuildReferenceResolver.fs @@ -10,11 +10,10 @@ open System open System.IO open System.Reflection open Microsoft.Win32 -open FSharp.Compiler open FSharp.Compiler.ReferenceResolver open FSharp.Compiler.AbstractIL.Internal.Library -let internal SimulatedMSBuildResolver = +let private SimulatedMSBuildResolver = let supportedFrameworks = [| "v4.7.2" "v4.7.1" @@ -183,21 +182,7 @@ let internal SimulatedMSBuildResolver = results.ToArray() } -let internal GetBestAvailableResolver() = -#if !FX_RESHAPED_MSBUILD - let tryMSBuild v = - // Detect if MSBuild is on the machine, if so use the resolver from there - let mb = try Assembly.Load(sprintf "Microsoft.Build.Framework, Version=%s.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" v) |> Option.ofObj with _ -> None - let assembly = mb |> Option.bind (fun _ -> try Assembly.Load(sprintf "FSharp.Compiler.Service.MSBuild.v%s" v) |> Option.ofObj with _ -> None) - let ty = assembly |> Option.bind (fun a -> a.GetType("FSharp.Compiler.MSBuildReferenceResolver") |> Option.ofObj) - let obj = ty |> Option.bind (fun ty -> ty.InvokeMember("get_Resolver", BindingFlags.Static ||| BindingFlags.Public ||| BindingFlags.InvokeMethod ||| BindingFlags.NonPublic, null, null, [| |]) |> Option.ofObj) - let resolver = obj |> Option.bind (fun obj -> match obj with :? Resolver as r -> Some r | _ -> None) - resolver - match tryMSBuild "12" with - | Some r -> r - | None -> -#endif - SimulatedMSBuildResolver +let internal getResolver () = SimulatedMSBuildResolver #if INTERACTIVE diff --git a/src/fsharp/TypeChecker.fs b/src/fsharp/TypeChecker.fs index 485b367aed4..824cb2ac205 100644 --- a/src/fsharp/TypeChecker.fs +++ b/src/fsharp/TypeChecker.fs @@ -962,7 +962,7 @@ let AdjustValSynInfoInSignature g ty (SynValInfo(argsData, retData) as sigMD) = /// The ValReprInfo for a value, except the number of typars is not yet inferred type PartialValReprInfo = PartialValReprInfo of ArgReprInfo list list * ArgReprInfo -let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(attrs, isOpt, nm)) = +let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(Attributes attrs, isOpt, nm)) = // Synthesize an artificial "OptionalArgument" attribute for the parameter let optAttrs = if isOpt then @@ -2453,7 +2453,7 @@ type NormalizedBinding = SynBindingKind * bool * (* pesudo/mustinline value? *) bool * (* mutable *) - SynAttributes * + SynAttribute list * XmlDoc * SynValTyparDecls * SynValData * @@ -2583,7 +2583,7 @@ module BindingNormalization = let NormalizeBinding isObjExprBinding cenv (env: TcEnv) b = match b with - | Binding (vis, bkind, isInline, isMutable, attrs, doc, valSynData, p, retInfo, rhsExpr, mBinding, spBind) -> + | Binding (vis, bkind, isInline, isMutable, Attributes attrs, doc, valSynData, p, retInfo, rhsExpr, mBinding, spBind) -> let (NormalizedBindingPat(pat, rhsExpr, valSynData, typars)) = NormalizeBindingPattern cenv cenv.nameResolver isObjExprBinding env valSynData p (NormalizedBindingRhs ([], retInfo, rhsExpr)) NormalizedBinding(vis, bkind, isInline, isMutable, attrs, doc.ToXmlDoc(), typars, valSynData, pat, rhsExpr, mBinding, spBind) @@ -4572,7 +4572,7 @@ and TcTyparOrMeasurePar optKind cenv (env: TcEnv) newOk tpenv (Typar(id, _, _) a and TcTypar cenv env newOk tpenv tp = TcTyparOrMeasurePar (Some TyparKind.Type) cenv env newOk tpenv tp -and TcTyparDecl cenv env (TyparDecl(synAttrs, (Typar(id, _, _) as stp))) = +and TcTyparDecl cenv env (TyparDecl(Attributes synAttrs, (Typar(id, _, _) as stp))) = let attrs = TcAttributes cenv env AttributeTargets.GenericParameter synAttrs let hasMeasureAttr = HasFSharpAttribute cenv.g cenv.g.attrib_MeasureAttribute attrs let hasEqDepAttr = HasFSharpAttribute cenv.g cenv.g.attrib_EqualityConditionalOnAttribute attrs @@ -10763,7 +10763,7 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt spatsL |> List.map (SynInfo.InferSynArgInfoFromSimplePats >> List.map (SynInfo.AttribsOfArgData >> TcAttrs AttributeTargets.Parameter)) let retAttribs = match rtyOpt with - | Some (SynBindingReturnInfo(_, _, retAttrs)) -> TcAttrs AttributeTargets.ReturnValue retAttrs + | Some (SynBindingReturnInfo(_, _, Attributes retAttrs)) -> TcAttrs AttributeTargets.ReturnValue retAttrs | None -> [] let argAndRetAttribs = ArgAndRetAttribs(argAttribs, retAttribs) @@ -12289,7 +12289,7 @@ and TcLetrec overridesOK cenv env tpenv (binds, bindsm, scopem) = let TcAndPublishValSpec (cenv, env, containerInfo: ContainerInfo, declKind, memFlagsOpt, tpenv, valSpfn) = - let (ValSpfn (synAttrs, _, SynValTyparDecls (synTypars, synCanInferTypars, _), _, _, isInline, mutableFlag, doc, vis, literalExprOpt, m)) = valSpfn + let (ValSpfn (Attributes synAttrs, _, SynValTyparDecls (synTypars, synCanInferTypars, _), _, _, isInline, mutableFlag, doc, vis, literalExprOpt, m)) = valSpfn GeneralizationHelpers.CheckDeclaredTyparsPermitted(memFlagsOpt, synTypars, m) let canInferTypars = GeneralizationHelpers.ComputeCanInferExtraGeneralizableTypars (containerInfo.ParentRef, synCanInferTypars, memFlagsOpt) @@ -12405,7 +12405,7 @@ module TcRecdUnionAndEnumDeclarations = begin rfspec - let TcAnonFieldDecl cenv env parent tpenv nm (Field(attribs, isStatic, idOpt, ty, isMutable, xmldoc, vis, m)) = + let TcAnonFieldDecl cenv env parent tpenv nm (Field(Attributes attribs, isStatic, idOpt, ty, isMutable, xmldoc, vis, m)) = let id = (match idOpt with None -> mkSynId m nm | Some id -> id) let f = TcFieldDecl cenv env parent false tpenv (isStatic, attribs, id, idOpt.IsNone, ty, isMutable, xmldoc.ToXmlDoc(), vis, m) match idOpt with @@ -12416,7 +12416,7 @@ module TcRecdUnionAndEnumDeclarations = begin f - let TcNamedFieldDecl cenv env parent isIncrClass tpenv (Field(attribs, isStatic, id, ty, isMutable, xmldoc, vis, m)) = + let TcNamedFieldDecl cenv env parent isIncrClass tpenv (Field(Attributes attribs, isStatic, id, ty, isMutable, xmldoc, vis, m)) = match id with | None -> error (Error(FSComp.SR.tcFieldRequiresName(), m)) | Some id -> TcFieldDecl cenv env parent isIncrClass tpenv (isStatic, attribs, id, false, ty, isMutable, xmldoc.ToXmlDoc(), vis, m) @@ -12453,7 +12453,7 @@ module TcRecdUnionAndEnumDeclarations = begin | _ -> seen.Add(f.Name, sf) - let TcUnionCaseDecl cenv env parent thisTy tpenv (UnionCase (synAttrs, id, args, xmldoc, vis, m)) = + let TcUnionCaseDecl cenv env parent thisTy tpenv (UnionCase(Attributes synAttrs, id, args, xmldoc, vis, m)) = let attrs = TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs // the attributes of a union case decl get attached to the generated "static factory" method let vis, _ = ComputeAccessAndCompPath env None m vis None parent let vis = CombineReprAccess parent vis @@ -12490,7 +12490,7 @@ module TcRecdUnionAndEnumDeclarations = begin let unionCases' = unionCases |> List.map (TcUnionCaseDecl cenv env parent thisTy tpenv) unionCases' |> CheckDuplicates (fun uc -> uc.Id) "union case" - let TcEnumDecl cenv env parent thisTy fieldTy (EnumCase (synAttrs, id, v, xmldoc, m)) = + let TcEnumDecl cenv env parent thisTy fieldTy (EnumCase(Attributes synAttrs, id, v, xmldoc, m)) = let attrs = TcAttributes cenv env AttributeTargets.Field synAttrs match v with | SynConst.Bytes _ @@ -12545,15 +12545,18 @@ let TcTyconMemberSpecs cenv env containerInfo declKind tpenv (augSpfn: SynMember let TcModuleOrNamespaceLidAndPermitAutoResolve tcSink env amap (longId: Ident list) = let ad = env.eAccessRights match longId with - | [] -> Result [] + | [] -> [] | id :: rest -> let m = longId |> List.map (fun id -> id.idRange) |> List.reduce unionRanges match ResolveLongIndentAsModuleOrNamespace tcSink ResultCollectionSettings.AllResults amap m true OpenQualified env.eNameResEnv ad id rest true with - | Result res -> Result res - | Exception err -> raze err + | Result res -> res + | Exception err -> + errorR(err); [] let TcOpenDecl tcSink (g: TcGlobals) amap m scopem env (longId: Ident list) = - let modrefs = ForceRaise (TcModuleOrNamespaceLidAndPermitAutoResolve tcSink env amap longId) + match TcModuleOrNamespaceLidAndPermitAutoResolve tcSink env amap longId with + | [] -> env + | modrefs -> // validate opened namespace names for id in longId do @@ -13556,7 +13559,7 @@ module MutRecBindingChecking = error(Error(FSComp.SR.tcEnumerationsMayNotHaveMembers(), (trimRangeToLine m))) match classMemberDef, containerInfo with - | SynMemberDefn.ImplicitCtor (vis, attrs, spats, thisIdOpt, m), ContainerInfo(_, Some(MemberOrValContainerInfo(tcref, _, baseValOpt, safeInitInfo, _))) -> + | SynMemberDefn.ImplicitCtor (vis, Attributes attrs, SynSimplePats.SimplePats(spats, _), thisIdOpt, m), ContainerInfo(_, Some(MemberOrValContainerInfo(tcref, _, baseValOpt, safeInitInfo, _))) -> if tcref.TypeOrMeasureKind = TyparKind.Measure then error(Error(FSComp.SR.tcMeasureDeclarationsRequireStaticMembers(), m)) @@ -14810,7 +14813,7 @@ let CheckForDuplicateModule env nm m = /// Check 'exception' declarations in implementations and signatures module TcExceptionDeclarations = - let TcExnDefnCore_Phase1A cenv env parent (SynExceptionDefnRepr(synAttrs, UnionCase(_, id, _, _, _, _), _, doc, vis, m)) = + let TcExnDefnCore_Phase1A cenv env parent (SynExceptionDefnRepr(Attributes synAttrs, UnionCase(_, id, _, _, _, _), _, doc, vis, m)) = let attrs = TcAttributes cenv env AttributeTargets.ExnDecl synAttrs if not (String.isUpper id.idText) then errorR(NotUpperCaseConstructor m) let vis, cpath = ComputeAccessAndCompPath env None m vis None parent @@ -15016,7 +15019,7 @@ module EstablishTypeDefinitionCores = match implicitCtorSynPats with | None -> () | Some spats -> - let ctorArgNames, (_, names, _) = TcSimplePatsOfUnknownType cenv true NoCheckCxs env tpenv (SynSimplePats.SimplePats (spats, m)) + let ctorArgNames, (_, names, _) = TcSimplePatsOfUnknownType cenv true NoCheckCxs env tpenv spats for arg in ctorArgNames do let ty = names.[arg].Type let m = names.[arg].Ident.idRange @@ -15075,7 +15078,7 @@ module EstablishTypeDefinitionCores = |> set let TcTyconDefnCore_Phase1A_BuildInitialModule cenv envInitial parent typeNames compInfo decls = - let (ComponentInfo(attribs, _parms, _constraints, longPath, xml, _, vis, im)) = compInfo + let (ComponentInfo(Attributes attribs, _parms, _constraints, longPath, xml, _, vis, im)) = compInfo let id = ComputeModuleName longPath let modAttrs = TcAttributes cenv envInitial AttributeTargets.ModuleDecl attribs let modKind = ComputeModuleOrNamespaceKind cenv.g true typeNames modAttrs id.idText @@ -15146,7 +15149,7 @@ module EstablishTypeDefinitionCores = /// synTyconInfo: Syntactic AST for the name, attributes etc. of the type constructor /// synTyconRepr: Syntactic AST for the RHS of the type definition let private TcTyconDefnCore_Phase1B_EstablishBasicKind cenv inSig envinner (MutRecDefnsPhase1DataForTycon(synTyconInfo, synTyconRepr, _, _, _, _)) (tycon: Tycon) = - let (ComponentInfo(synAttrs, typars, _, _, _, _, _, _)) = synTyconInfo + let (ComponentInfo(Attributes synAttrs, typars, _, _, _, _, _, _)) = synTyconInfo let m = tycon.Range let id = tycon.Id @@ -15816,7 +15819,7 @@ module EstablishTypeDefinitionCores = () | Some spats -> if tycon.IsFSharpStructOrEnumTycon then - let ctorArgNames, (_, names, _) = TcSimplePatsOfUnknownType cenv true CheckCxs envinner tpenv (SynSimplePats.SimplePats (spats, m)) + let ctorArgNames, (_, names, _) = TcSimplePatsOfUnknownType cenv true CheckCxs envinner tpenv spats for arg in ctorArgNames do let ty = names.[arg].Type let id = names.[arg].Ident @@ -16520,7 +16523,7 @@ module TcDeclarations = // Convert autoproperties to let bindings in the pre-list let rec preAutoProps memb = match memb with - | SynMemberDefn.AutoProperty (attribs, isStatic, id, tyOpt, propKind, _, xmlDoc, _access, synExpr, _mGetSet, mWholeAutoProp) -> + | SynMemberDefn.AutoProperty(Attributes attribs, isStatic, id, tyOpt, propKind, _, xmlDoc, _access, synExpr, _mGetSet, mWholeAutoProp) -> // Only the keep the field-targeted attributes let attribs = attribs |> List.filter (fun a -> match a.Target with Some t when t.idText = "field" -> true | _ -> false) let mLetPortion = synExpr.Range @@ -16532,6 +16535,7 @@ module TcDeclarations = | MemberKind.PropertySet | MemberKind.PropertyGetSet -> true | _ -> false + let attribs = mkAttributeList attribs mWholeAutoProp let binding = mkSynBinding (xmlDoc, headPat) (None, false, isMutable, mLetPortion, NoSequencePointAtInvisibleBinding, retInfo, synExpr, synExpr.Range, [], attribs, None) [(SynMemberDefn.LetBindings ([binding], isStatic, false, mWholeAutoProp))] @@ -16546,7 +16550,7 @@ module TcDeclarations = // Convert autoproperties to member bindings in the post-list let rec postAutoProps memb = match memb with - | SynMemberDefn.AutoProperty (attribs, isStatic, id, tyOpt, propKind, memberFlags, xmlDoc, access, _synExpr, mGetSetOpt, _mWholeAutoProp) -> + | SynMemberDefn.AutoProperty(Attributes attribs, isStatic, id, tyOpt, propKind, memberFlags, xmlDoc, access, _synExpr, mGetSetOpt, _mWholeAutoProp) -> let mMemberPortion = id.idRange // Only the keep the non-field-targeted attributes let attribs = attribs |> List.filter (fun a -> match a.Target with Some t when t.idText = "field" -> false | _ -> true) @@ -16566,6 +16570,7 @@ module TcDeclarations = let getter = let rhsExpr = SynExpr.Ident fldId let retInfo = match tyOpt with None -> None | Some ty -> Some (SynReturnInfo((ty, SynInfo.unnamedRetVal), ty.Range)) + let attribs = mkAttributeList attribs mMemberPortion let binding = mkSynBinding (xmlDoc, headPat) (access, false, false, mMemberPortion, NoSequencePointAtInvisibleBinding, retInfo, rhsExpr, rhsExpr.Range, [], attribs, Some (memberFlags MemberKind.Member)) SynMemberDefn.Member (binding, mMemberPortion) yield getter @@ -16619,7 +16624,7 @@ module TcDeclarations = let implicitCtorSynPats = members |> List.tryPick (function - | SynMemberDefn.ImplicitCtor (_, _, spats, _, _) -> Some spats + | SynMemberDefn.ImplicitCtor (_, _, (SynSimplePats.SimplePats _ as spats), _, _) -> Some spats | _ -> None) // An ugly bit of code to pre-determine if a type has a nullary constructor, prior to establishing the @@ -16628,7 +16633,7 @@ module TcDeclarations = members |> List.exists (function | SynMemberDefn.Member(Binding(_, _, _, _, _, _, SynValData(Some memberFlags, _, _), SynPatForConstructorDecl SynPatForNullaryArgs, _, _, _, _), _) -> memberFlags.MemberKind=MemberKind.Constructor - | SynMemberDefn.ImplicitCtor (_, _, spats, _, _) -> isNil spats + | SynMemberDefn.ImplicitCtor (_, _, SynSimplePats.SimplePats(spats, _), _, _) -> isNil spats | _ -> false) let repr = SynTypeDefnSimpleRepr.General(kind, inherits, slotsigs, fields, isConcrete, isIncrClass, implicitCtorSynPats, m) let isAtOriginalTyconDefn = not (isAugmentationTyconDefnRepr repr) @@ -16870,7 +16875,7 @@ let rec TcSignatureElementNonMutRec cenv parent typeNames endm (env: TcEnv) synS let env = List.foldBack (AddLocalVal cenv.tcSink scopem) idvs env return env - | SynModuleSigDecl.NestedModule(ComponentInfo(attribs, _parms, _constraints, longPath, xml, _, vis, im) as compInfo, isRec, mdefs, m) -> + | SynModuleSigDecl.NestedModule(ComponentInfo(Attributes attribs, _parms, _constraints, longPath, xml, _, vis, im) as compInfo, isRec, mdefs, m) -> if isRec then // Treat 'module rec M = ...' as a single mutually recursive definition group 'module M = ...' let modDecl = SynModuleSigDecl.NestedModule(compInfo, false, mdefs, m) @@ -17175,7 +17180,7 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem | SynModuleDecl.DoExpr _ -> return! failwith "unreachable" - | SynModuleDecl.Attributes (synAttrs, _) -> + | SynModuleDecl.Attributes (Attributes synAttrs, _) -> let attrs, _ = TcAttributesWithPossibleTargets false cenv env AttributeTargets.Top synAttrs return ((fun e -> e), attrs), env, env @@ -17190,7 +17195,7 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem let modDecl = SynModuleDecl.NestedModule(compInfo, false, mdefs, isContinuingModule, m) return! TcModuleOrNamespaceElementsMutRec cenv parent typeNames m env None [modDecl] else - let (ComponentInfo(attribs, _parms, _constraints, longPath, xml, _, vis, im)) = compInfo + let (ComponentInfo(Attributes attribs, _parms, _constraints, longPath, xml, _, vis, im)) = compInfo let id = ComputeModuleName longPath let modAttrs = TcAttributes cenv env AttributeTargets.ModuleDecl attribs @@ -17327,7 +17332,7 @@ and TcModuleOrNamespaceElementsMutRec cenv parent typeNames endm envInitial mutR let m = match defs with [] -> endm | _ -> defs |> List.map (fun d -> d.Range) |> List.reduce unionRanges let scopem = (defs, endm) ||> List.foldBack (fun h m -> unionRanges h.Range m) - let (mutRecDefns, (_, _, synAttrs)) = + let (mutRecDefns, (_, _, Attributes synAttrs)) = let rec loop isNamespace attrs defs: (MutRecDefnsInitialData * _) = ((true, true, attrs), defs) ||> List.collectFold (fun (openOk, moduleAbbrevOk, attrs) def -> match ElimModuleDoBinding def with diff --git a/src/fsharp/ast.fs b/src/fsharp/ast.fs index 94f40226214..3c341131fba 100644 --- a/src/fsharp/ast.fs +++ b/src/fsharp/ast.fs @@ -1172,7 +1172,13 @@ and | None -> unionRanges e.Range m | Some x -> unionRanges (unionRanges e.Range m) x.Range -and SynAttributes = SynAttribute list +and + /// List of attributes enclosed in [< ... >]. + SynAttributeList = + { Attributes: SynAttribute list + Range: range } + +and SynAttributes = SynAttributeList list and [] @@ -1304,7 +1310,7 @@ and /// An object oriented type definition. This is not a parse-tree form, but represents the core /// type representation which the type checker splits out from the "ObjectModel" cases of type definitions. - | General of SynTypeDefnKind * (SynType * range * Ident option) list * (SynValSig * MemberFlags) list * SynField list * bool * bool * SynSimplePat list option * range: range + | General of SynTypeDefnKind * (SynType * range * Ident option) list * (SynValSig * MemberFlags) list * SynField list * bool * bool * SynSimplePats option * range: range /// A type defined by using an IL assembly representation. Only used in FSharp.Core. /// @@ -1519,7 +1525,7 @@ and | Member of memberDefn: SynBinding * range: range /// implicit ctor args as a defn line, 'as' specification - | ImplicitCtor of accessiblity: SynAccess option * attributes: SynAttributes * ctorArgs: SynSimplePat list * selfIdentifier: Ident option * range: range + | ImplicitCtor of accessiblity: SynAccess option * attributes: SynAttributes * ctorArgs: SynSimplePats * selfIdentifier: Ident option * range: range /// inherit (args...) as base | ImplicitInherit of inheritType: SynType * inheritArgs: SynExpr * inheritAlias: Ident option * range: range @@ -2118,6 +2124,18 @@ type SynExpr with type SynReturnInfo = SynReturnInfo of (SynType * SynArgInfo) * range: range +let mkAttributeList attrs range = + [{ Attributes = attrs + Range = range }] + +let ConcatAttributesLists (attrsLists: SynAttributeList list) = + attrsLists + |> List.map (fun x -> x.Attributes) + |> List.concat + +let (|Attributes|) synAttributes = + ConcatAttributesLists synAttributes + /// Operations related to the syntactic analysis of arguments of value, function and member definitions and signatures. /// /// Function and member definitions have strongly syntactically constrained arities. We infer @@ -2186,7 +2204,7 @@ module SynInfo = let AritiesOfArgs (SynValInfo(args, _)) = List.map List.length args /// Get the argument attributes from the syntactic information for an argument. - let AttribsOfArgData (SynArgInfo(attribs, _, _)) = attribs + let AttribsOfArgData (SynArgInfo(Attributes attribs, _, _)) = attribs /// Infer the syntactic argument info for a single argument from a simple pattern. let rec InferSynArgInfoFromSimplePat attribs p = diff --git a/src/fsharp/fsc.fs b/src/fsharp/fsc.fs index 27150984738..e4e693e99ca 100644 --- a/src/fsharp/fsc.fs +++ b/src/fsharp/fsc.fs @@ -2176,7 +2176,13 @@ let typecheckAndCompile defaultCopyFSharpCore, exiter: Exiter, errorLoggerProvider, tcImportsCapture, dynamicAssemblyCreator) = use d = new DisposablesTracker() - use e = new SaveAndRestoreConsoleEncoding() + let savedOut = System.Console.Out + use __ = + { new IDisposable with + member __.Dispose() = + try + System.Console.SetOut(savedOut) + with _ -> ()} main0(ctok, argv, legacyReferenceResolver, bannerAlreadyPrinted, reduceMemoryUsage, defaultCopyFSharpCore, exiter, errorLoggerProvider, d) |> main1 diff --git a/src/fsharp/fsc/fsc.fsproj b/src/fsharp/fsc/fsc.fsproj index 8e5dce90af9..370664ee6a4 100644 --- a/src/fsharp/fsc/fsc.fsproj +++ b/src/fsharp/fsc/fsc.fsproj @@ -25,10 +25,10 @@ default.win32manifest PreserveNewest - + {{FSCoreVersion}} $(FSCoreVersion) - + diff --git a/src/fsharp/fscmain.fs b/src/fsharp/fscmain.fs index f74c47e0da6..73e578f3deb 100644 --- a/src/fsharp/fscmain.fs +++ b/src/fsharp/fscmain.fs @@ -62,7 +62,7 @@ module Driver = #if CROSS_PLATFORM_COMPILER SimulatedMSBuildReferenceResolver.SimulatedMSBuildResolver #else - MSBuildReferenceResolver.Resolver + LegacyMSBuildReferenceResolver.getResolver() #endif // This is the only place where ReduceMemoryFlag.No is set. This is because fsc.exe is not a long-running process and diff --git a/src/fsharp/fsi/console.fs b/src/fsharp/fsi/console.fs index aa57916df52..1ac792abc16 100644 --- a/src/fsharp/fsi/console.fs +++ b/src/fsharp/fsi/console.fs @@ -5,7 +5,6 @@ namespace FSharp.Compiler.Interactive open System open System.Text open System.Collections.Generic -open Internal.Utilities /// System.Console.ReadKey appears to return an ANSI character (not the expected the unicode character). /// When this fix flag is true, this byte is converted to a char using the System.Console.InputEncoding. diff --git a/src/fsharp/fsi/fsi.fs b/src/fsharp/fsi/fsi.fs index aa1a55f9492..02b26bf9af1 100644 --- a/src/fsharp/fsi/fsi.fs +++ b/src/fsharp/fsi/fsi.fs @@ -2415,7 +2415,7 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i let legacyReferenceResolver = match legacyReferenceResolver with - | None -> SimulatedMSBuildReferenceResolver.GetBestAvailableResolver() + | None -> SimulatedMSBuildReferenceResolver.getResolver() | Some rr -> rr let tcConfigB = diff --git a/src/fsharp/fsi/fsi.fsproj b/src/fsharp/fsi/fsi.fsproj index d091baf9f93..fb55d4d898f 100644 --- a/src/fsharp/fsi/fsi.fsproj +++ b/src/fsharp/fsi/fsi.fsproj @@ -22,19 +22,19 @@ - + {{FSCoreVersion}} $(FSCoreVersion) - + - - - + + + - + diff --git a/src/fsharp/fsi/fsimain.fs b/src/fsharp/fsi/fsimain.fs index 56ab81077cb..c127642db71 100644 --- a/src/fsharp/fsi/fsimain.fs +++ b/src/fsharp/fsi/fsimain.fs @@ -22,10 +22,8 @@ open System.Windows.Forms #endif open FSharp.Compiler -open FSharp.Compiler.AbstractIL -open FSharp.Compiler.Lib +open FSharp.Compiler.AbstractIL open FSharp.Compiler.Interactive.Shell -open FSharp.Compiler.Interactive open FSharp.Compiler.Interactive.Shell.Settings #nowarn "55" @@ -228,7 +226,7 @@ let evaluateSession(argv: string[]) = #if CROSS_PLATFORM_COMPILER SimulatedMSBuildReferenceResolver.SimulatedMSBuildResolver #else - MSBuildReferenceResolver.Resolver + LegacyMSBuildReferenceResolver.getResolver() #endif // Update the configuration to include 'StartServer', WinFormsEventLoop and 'GetOptionalConsoleReadLine()' let rec fsiConfig = @@ -316,7 +314,13 @@ let evaluateSession(argv: string[]) = let MainMain argv = ignore argv let argv = System.Environment.GetCommandLineArgs() - use e = new SaveAndRestoreConsoleEncoding() + let savedOut = Console.Out + use __ = + { new IDisposable with + member __.Dispose() = + try + Console.SetOut(savedOut) + with _ -> ()} #if !FX_NO_APP_DOMAINS let timesFlag = argv |> Array.exists (fun x -> x = "/times" || x = "--times") diff --git a/src/fsharp/fsiAnyCpu/fsiAnyCpu.fsproj b/src/fsharp/fsiAnyCpu/fsiAnyCpu.fsproj index cbd4f7103a2..92503aaea03 100644 --- a/src/fsharp/fsiAnyCpu/fsiAnyCpu.fsproj +++ b/src/fsharp/fsiAnyCpu/fsiAnyCpu.fsproj @@ -22,10 +22,10 @@ - + {{FSCoreVersion}} $(FSCoreVersion) - + diff --git a/src/fsharp/lex.fsl b/src/fsharp/lex.fsl index 0af96d71c9a..c32c9a641b6 100644 --- a/src/fsharp/lex.fsl +++ b/src/fsharp/lex.fsl @@ -524,10 +524,14 @@ rule token args skip = parse // Construct the new position if args.applyLineDirectives then lexbuf.EndPos <- pos.ApplyLineDirective((match file with Some f -> fileIndexOfFile f | None -> pos.FileIndex), line) - + else + // add a newline when we don't apply a directive since we consumed a newline getting here + newline lexbuf token args skip lexbuf - else - if not skip then (HASH_LINE (LexCont.Token !args.ifdefStack)) else token args skip lexbuf } + else + // add a newline when we don't apply a directive since we consumed a newline getting here + newline lexbuf + (HASH_LINE (LexCont.Token !args.ifdefStack)) } | "<@" { checkExprOp lexbuf; LQUOTE ("<@ @>", false) } | "<@@" { checkExprOp lexbuf; LQUOTE ("<@@ @@>", true) } diff --git a/src/fsharp/lib.fs b/src/fsharp/lib.fs index f121639005f..e7eaf9543fd 100755 --- a/src/fsharp/lib.fs +++ b/src/fsharp/lib.fs @@ -23,15 +23,6 @@ let GetEnvInteger e dflt = match System.Environment.GetEnvironmentVariable(e) wi let dispose (x:System.IDisposable) = match x with null -> () | x -> x.Dispose() -type SaveAndRestoreConsoleEncoding () = - let savedOut = System.Console.Out - - interface System.IDisposable with - member this.Dispose() = - try - System.Console.SetOut(savedOut) - with _ -> () - //------------------------------------------------------------------------- // Library: bits //------------------------------------------------------------------------ diff --git a/src/fsharp/pars.fsy b/src/fsharp/pars.fsy index 453e5c56342..56e6dbfb404 100644 --- a/src/fsharp/pars.fsy +++ b/src/fsharp/pars.fsy @@ -1357,20 +1357,20 @@ attributes: /* One set of custom attributes, including [< ... >] */ -attributeList: - | LBRACK_LESS attributeListElements opt_seps GREATER_RBRACK opt_OBLOCKSEP - { $2 } +attributeList: + | LBRACK_LESS attributeListElements opt_seps GREATER_RBRACK opt_OBLOCKSEP + { mkAttributeList $2 (rhs2 parseState 1 3) } - | LBRACK_LESS error GREATER_RBRACK opt_OBLOCKSEP - { [] } + | LBRACK_LESS error GREATER_RBRACK opt_OBLOCKSEP + { mkAttributeList [] (rhs2 parseState 1 3) } - | LBRACK_LESS attributeListElements opt_seps ends_coming_soon_or_recover + | LBRACK_LESS attributeListElements opt_seps ends_coming_soon_or_recover { if not $4 then reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedLBrackLess()) - $2 } + mkAttributeList $2 (rhs2 parseState 1 2) } - | LBRACK_LESS ends_coming_soon_or_recover + | LBRACK_LESS ends_coming_soon_or_recover { if not $2 then reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedLBrackLess()) - [] } + mkAttributeList [] (rhs parseState 1) } /* One set of custom attributes, not including [< ... >] */ @@ -1644,7 +1644,10 @@ memberCore: let optInline = $1 || optInline // optional attributes are only applied to getters and setters // the "top level" attrs will be applied to both - let optAttrs = optAttrs |> List.map (fun (a:SynAttribute) -> { a with AppliesToGetterAndSetter=true }) + let optAttrs = + optAttrs |> List.map (fun attrList -> + { attrList with Attributes = attrList.Attributes |> List.map (fun a -> { a with AppliesToGetterAndSetter = true } ) }) + let attrs = attrs @ optAttrs let binding = bindingBuilder (visNoLongerUsed,optInline,isMutable,mBindLhs,NoSequencePointAtInvisibleBinding,optReturnType,expr,exprm,[],attrs,Some (memFlagsBuilder MemberKind.Member)) @@ -2732,38 +2735,51 @@ bindingPattern: { let xmlDoc = grabXmlDoc(parseState,1) mkSynBinding (xmlDoc,$1), rhs parseState 1 } -/* sp = v | sp:typ | attrs sp */ +// Subset of patterns allowed to be used in implicit ctors. +// For a better error recovery we could replace these rules with the actual SynPat parsing +// and use allowed patterns only at a later analysis stage reporting errors along the way. simplePattern: - | ident - { SynSimplePat.Id ($1,None,false,false,false,rhs parseState 1) } - | QMARK ident - { SynSimplePat.Id ($2,None,false,false,true,rhs parseState 2) } + | ident + { let m = rhs parseState 1 + SynPat.Named(SynPat.Wild m, $1, false, None, m) } + | QMARK ident + { SynPat.OptionalVal($2, rhs parseState 2) } | simplePattern COLON typeWithTypeConstraints - { let lhsm = lhs parseState - SynSimplePat.Typed($1,$3,lhsm) } + { SynPat.Typed($1, $3, lhs parseState) } | attributes simplePattern %prec paren_pat_attribs - { let lhsm = lhs parseState - SynSimplePat.Attrib($2,$1,lhsm) } + { SynPat.Attrib($2, $1, lhs parseState) } simplePatternCommaList: - | simplePattern - { [$1] } - | simplePattern COMMA simplePatternCommaList - { $1 :: $3 } + | simplePattern + { $1 } + | simplePattern COMMA simplePatternCommaList + { match $3 with + | SynPat.Tuple(_, pats, _) -> SynPat.Tuple(false, $1 :: pats, rhs2 parseState 1 3) + | _ -> SynPat.Tuple(false, [$1; $3], rhs2 parseState 1 3) } simplePatterns: - | LPAREN simplePatternCommaList rparen - { $2 } - | LPAREN rparen - { [] } - | LPAREN simplePatternCommaList recover - { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedParen()) - [] } - | LPAREN error rparen - { (* silent recovery *) [] } - | LPAREN recover - { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedParen()) - [] } + | LPAREN simplePatternCommaList rparen + { let parenPat = SynPat.Paren($2, rhs2 parseState 1 3) + let simplePats, _ = SimplePatsOfPat parseState.SynArgNameGenerator parenPat + simplePats } + | LPAREN rparen + { let pat = SynPat.Const(SynConst.Unit, rhs2 parseState 1 2) + let simplePats, _ = SimplePatsOfPat parseState.SynArgNameGenerator pat + simplePats } + | LPAREN simplePatternCommaList recover + { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedParen()) + let parenPat = SynPat.Paren(SynPat.Tuple(false, [], rhs2 parseState 1 2), rhs2 parseState 1 2) // todo: report parsed pats anyway? + let simplePats, _ = SimplePatsOfPat parseState.SynArgNameGenerator parenPat + simplePats } + | LPAREN error rparen + { let parenPat = SynPat.Paren(SynPat.Wild(rhs parseState 2), rhs2 parseState 1 3) // silent recovery + let simplePats, _ = SimplePatsOfPat parseState.SynArgNameGenerator parenPat + simplePats } + | LPAREN recover + { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedParen()) + let pat = SynPat.Wild(lhs parseState) + let simplePats, _ = SimplePatsOfPat parseState.SynArgNameGenerator pat + simplePats } headBindingPattern: diff --git a/src/fsharp/service/FSharpCheckerResults.fs b/src/fsharp/service/FSharpCheckerResults.fs new file mode 100644 index 00000000000..d8cfe81430a --- /dev/null +++ b/src/fsharp/service/FSharpCheckerResults.fs @@ -0,0 +1,2240 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +// Open up the compiler as an incremental service for parsing, +// type checking and intellisense-like environment-reporting. + +namespace FSharp.Compiler.SourceCodeServices + +open System +open System.Diagnostics +open System.IO +open System.Reflection + +open FSharp.Core.Printf +open FSharp.Compiler +open FSharp.Compiler.AbstractIL +open FSharp.Compiler.AbstractIL.IL +open FSharp.Compiler.AbstractIL.Internal.Library + +open FSharp.Compiler.AccessibilityLogic +open FSharp.Compiler.Ast +open FSharp.Compiler.CompileOps +open FSharp.Compiler.CompileOptions +open FSharp.Compiler.ErrorLogger +open FSharp.Compiler.Lib +open FSharp.Compiler.PrettyNaming +open FSharp.Compiler.Parser +open FSharp.Compiler.Range +open FSharp.Compiler.Lexhelp +open FSharp.Compiler.Layout +open FSharp.Compiler.Tast +open FSharp.Compiler.Tastops +open FSharp.Compiler.TcGlobals +open FSharp.Compiler.Text +open FSharp.Compiler.Infos +open FSharp.Compiler.InfoReader +open FSharp.Compiler.NameResolution +open FSharp.Compiler.TypeChecker +open FSharp.Compiler.SourceCodeServices.SymbolHelpers + +open Internal.Utilities +open Internal.Utilities.Collections +open FSharp.Compiler.AbstractIL.ILBinaryReader + +[] +module internal FSharpCheckerResultsSettings = + + let getToolTipTextSize = GetEnvInteger "FCS_GetToolTipTextCacheSize" 5 + + let maxTypeCheckErrorsOutOfProjectContext = GetEnvInteger "FCS_MaxErrorsOutOfProjectContext" 3 + + /// Maximum time share for a piece of background work before it should (cooperatively) yield + /// to enable other requests to be serviced. Yielding means returning a continuation function + /// (via an Eventually<_> value of case NotYetDone) that can be called as the next piece of work. + let maxTimeShareMilliseconds = + match System.Environment.GetEnvironmentVariable("FCS_MaxTimeShare") with + | null | "" -> 100L + | s -> int64 s + + // Look for DLLs in the location of the service DLL first. + let defaultFSharpBinariesDir = FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(Some(typeof.Assembly.Location)).Value + +[] +type FSharpFindDeclFailureReason = + + // generic reason: no particular information about error + | Unknown of message: string + + // source code file is not available + | NoSourceCode + + // trying to find declaration of ProvidedType without TypeProviderDefinitionLocationAttribute + | ProvidedType of string + + // trying to find declaration of ProvidedMember without TypeProviderDefinitionLocationAttribute + | ProvidedMember of string + +[] +type FSharpFindDeclResult = + + /// declaration not found + reason + | DeclNotFound of FSharpFindDeclFailureReason + + /// found declaration + | DeclFound of range + + /// Indicates an external declaration was found + | ExternalDecl of assembly : string * externalSym : ExternalSymbol + +/// This type is used to describe what was found during the name resolution. +/// (Depending on the kind of the items, we may stop processing or continue to find better items) +[] +type internal NameResResult = + | Members of (ItemWithInst list * DisplayEnv * range) + | Cancel of DisplayEnv * range + | Empty + | TypecheckStaleAndTextChanged + +[] +type ResolveOverloads = +| Yes +| No + +[] +type GetPreciseCompletionListFromExprTypingsResult = + | NoneBecauseTypecheckIsStaleAndTextChanged + | NoneBecauseThereWereTypeErrors + | None + | Some of (ItemWithInst list * DisplayEnv * range) * TType + +type Names = string list + +[] +type SemanticClassificationType = + | ReferenceType + | ValueType + | UnionCase + | Function + | Property + | MutableVar + | Module + | Printf + | ComputationExpression + | IntrinsicFunction + | Enumeration + | Interface + | TypeArgument + | Operator + | Disposable + +/// A TypeCheckInfo represents everything we get back from the typecheck of a file. +/// It acts like an in-memory database about the file. +/// It is effectively immutable and not updated: when we re-typecheck we just drop the previous +/// scope object on the floor and make a new one. +[] +type internal TypeCheckInfo + (// Information corresponding to miscellaneous command-line options (--define, etc). + _sTcConfig: TcConfig, + g: TcGlobals, + // The signature of the assembly being checked, up to and including the current file + ccuSigForFile: ModuleOrNamespaceType, + thisCcu: CcuThunk, + tcImports: TcImports, + tcAccessRights: AccessorDomain, + projectFileName: string, + mainInputFileName: string, + sResolutions: TcResolutions, + sSymbolUses: TcSymbolUses, + // This is a name resolution environment to use if no better match can be found. + sFallback: NameResolutionEnv, + loadClosure : LoadClosure option, + reactorOps : IReactorOperations, + textSnapshotInfo:obj option, + implFileOpt: TypedImplFile option, + openDeclarations: OpenDeclaration[]) = + + let textSnapshotInfo = defaultArg textSnapshotInfo null + let (|CNR|) (cnr:CapturedNameResolution) = + (cnr.Pos, cnr.Item, cnr.ItemOccurence, cnr.DisplayEnv, cnr.NameResolutionEnv, cnr.AccessorDomain, cnr.Range) + + // These strings are potentially large and the editor may choose to hold them for a while. + // Use this cache to fold together data tip text results that are the same. + // Is not keyed on 'Names' collection because this is invariant for the current position in + // this unchanged file. Keyed on lineStr though to prevent a change to the currently line + // being available against a stale scope. + let getToolTipTextCache = AgedLookup>(getToolTipTextSize,areSimilar=(fun (x,y) -> x = y)) + + let amap = tcImports.GetImportMap() + let infoReader = new InfoReader(g,amap) + let ncenv = new NameResolver(g,amap,infoReader,NameResolution.FakeInstantiationGenerator) + let cenv = SymbolEnv(g, thisCcu, Some ccuSigForFile, tcImports, amap, infoReader) + + /// Find the most precise naming environment for the given line and column + let GetBestEnvForPos cursorPos = + + let mutable bestSoFar = None + + // Find the most deeply nested enclosing scope that contains given position + sResolutions.CapturedEnvs |> ResizeArray.iter (fun (possm,env,ad) -> + if rangeContainsPos possm cursorPos then + match bestSoFar with + | Some (bestm,_,_) -> + if rangeContainsRange bestm possm then + bestSoFar <- Some (possm,env,ad) + | None -> + bestSoFar <- Some (possm,env,ad)) + + let mostDeeplyNestedEnclosingScope = bestSoFar + + // Look for better subtrees on the r.h.s. of the subtree to the left of where we are + // Should really go all the way down the r.h.s. of the subtree to the left of where we are + // This is all needed when the index is floating free in the area just after the environment we really want to capture + // We guarantee to only refine to a more nested environment. It may not be strictly + // the right environment, but will always be at least as rich + + let bestAlmostIncludedSoFar = ref None + + sResolutions.CapturedEnvs |> ResizeArray.iter (fun (possm,env,ad) -> + // take only ranges that strictly do not include cursorPos (all ranges that touch cursorPos were processed during 'Strict Inclusion' part) + if rangeBeforePos possm cursorPos && not (posEq possm.End cursorPos) then + let contained = + match mostDeeplyNestedEnclosingScope with + | Some (bestm,_,_) -> rangeContainsRange bestm possm + | None -> true + + if contained then + match !bestAlmostIncludedSoFar with + | Some (rightm:range,_,_) -> + if posGt possm.End rightm.End || + (posEq possm.End rightm.End && posGt possm.Start rightm.Start) then + bestAlmostIncludedSoFar := Some (possm,env,ad) + | _ -> bestAlmostIncludedSoFar := Some (possm,env,ad)) + + let resEnv = + match !bestAlmostIncludedSoFar, mostDeeplyNestedEnclosingScope with + | Some (_,env,ad), None -> env, ad + | Some (_,almostIncludedEnv,ad), Some (_,mostDeeplyNestedEnv,_) + when almostIncludedEnv.eFieldLabels.Count >= mostDeeplyNestedEnv.eFieldLabels.Count -> + almostIncludedEnv,ad + | _ -> + match mostDeeplyNestedEnclosingScope with + | Some (_,env,ad) -> + env,ad + | None -> + sFallback,AccessibleFromSomeFSharpCode + let pm = mkRange mainInputFileName cursorPos cursorPos + + resEnv,pm + + /// The items that come back from ResolveCompletionsInType are a bit + /// noisy. Filter a few things out. + /// + /// e.g. prefer types to constructors for FSharpToolTipText + let FilterItemsForCtors filterCtors (items: ItemWithInst list) = + let items = items |> List.filter (fun item -> match item.Item with (Item.CtorGroup _) when filterCtors = ResolveTypeNamesToTypeRefs -> false | _ -> true) + items + + // Filter items to show only valid & return Some if there are any + let ReturnItemsOfType (items: ItemWithInst list) g denv (m:range) filterCtors hasTextChangedSinceLastTypecheck = + let items = + items + |> RemoveDuplicateItems g + |> RemoveExplicitlySuppressed g + |> FilterItemsForCtors filterCtors + + if not (isNil items) then + if hasTextChangedSinceLastTypecheck(textSnapshotInfo, m) then + NameResResult.TypecheckStaleAndTextChanged // typecheck is stale, wait for second-chance IntelliSense to bring up right result + else + NameResResult.Members (items, denv, m) + else NameResResult.Empty + + let GetCapturedNameResolutions endOfNamesPos resolveOverloads = + + let quals = + match resolveOverloads with + | ResolveOverloads.Yes -> sResolutions.CapturedNameResolutions + | ResolveOverloads.No -> sResolutions.CapturedMethodGroupResolutions + + let quals = quals |> ResizeArray.filter (fun cnr -> posEq cnr.Pos endOfNamesPos) + + quals + + /// Looks at the exact name resolutions that occurred during type checking + /// If 'membersByResidue' is specified, we look for members of the item obtained + /// from the name resolution and filter them by the specified residue (?) + let GetPreciseItemsFromNameResolution(line, colAtEndOfNames, membersByResidue, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck) = + let endOfNamesPos = mkPos line colAtEndOfNames + + // Logic below expects the list to be in reverse order of resolution + let cnrs = GetCapturedNameResolutions endOfNamesPos resolveOverloads |> ResizeArray.toList |> List.rev + + match cnrs, membersByResidue with + + // If we're looking for members using a residue, we'd expect only + // a single item (pick the first one) and we need the residue (which may be "") + | CNR(_,Item.Types(_,(ty::_)), _, denv, nenv, ad, m)::_, Some _ -> + let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad true ty + let items = List.map ItemWithNoInst items + ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck + + // Value reference from the name resolution. Primarily to disallow "let x.$ = 1" + // In most of the cases, value references can be obtained from expression typings or from environment, + // so we wouldn't have to handle values here. However, if we have something like: + // let varA = "string" + // let varA = if b then 0 else varA. + // then the expression typings get confused (thinking 'varA:int'), so we use name resolution even for usual values. + + | CNR(_, Item.Value(vref), occurence, denv, nenv, ad, m)::_, Some _ -> + if (occurence = ItemOccurence.Binding || occurence = ItemOccurence.Pattern) then + // Return empty list to stop further lookup - for value declarations + NameResResult.Cancel(denv, m) + else + // If we have any valid items for the value, then return completions for its type now. + // Adjust the type in case this is the 'this' pointer stored in a reference cell. + let ty = StripSelfRefCell(g, vref.BaseOrThisInfo, vref.TauType) + // patch accessibility domain to remove protected members if accessing NormalVal + let ad = + match vref.BaseOrThisInfo, ad with + | ValBaseOrThisInfo.NormalVal, AccessibleFrom(paths, Some tcref) -> + let tcref = generalizedTyconRef tcref + // check that type of value is the same or subtype of tcref + // yes - allow access to protected members + // no - strip ability to access protected members + if FSharp.Compiler.TypeRelations.TypeFeasiblySubsumesType 0 g amap m tcref FSharp.Compiler.TypeRelations.CanCoerce ty then + ad + else + AccessibleFrom(paths, None) + | _ -> ad + + let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad false ty + let items = List.map ItemWithNoInst items + ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck + + // No residue, so the items are the full resolution of the name + | CNR(_, _, _, denv, _, _, m) :: _, None -> + let items = + cnrs + |> List.map (fun cnr -> cnr.ItemWithInst) + // "into" is special magic syntax, not an identifier or a library call. It is part of capturedNameResolutions as an + // implementation detail of syntax coloring, but we should not report name resolution results for it, to prevent spurious QuickInfo. + |> List.filter (fun item -> match item.Item with Item.CustomOperation(CustomOperations.Into,_,_) -> false | _ -> true) + ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck + | _, _ -> NameResResult.Empty + + let TryGetTypeFromNameResolution(line, colAtEndOfNames, membersByResidue, resolveOverloads) = + let endOfNamesPos = mkPos line colAtEndOfNames + let items = GetCapturedNameResolutions endOfNamesPos resolveOverloads |> ResizeArray.toList |> List.rev + + match items, membersByResidue with + | CNR(_,Item.Types(_,(ty::_)),_,_,_,_,_)::_, Some _ -> Some ty + | CNR(_, Item.Value(vref), occurence,_,_,_,_)::_, Some _ -> + if (occurence = ItemOccurence.Binding || occurence = ItemOccurence.Pattern) then None + else Some (StripSelfRefCell(g, vref.BaseOrThisInfo, vref.TauType)) + | _, _ -> None + + let CollectParameters (methods: MethInfo list) amap m: Item list = + methods + |> List.collect (fun meth -> + match meth.GetParamDatas(amap, m, meth.FormalMethodInst) with + | x::_ -> x |> List.choose(fun (ParamData(_isParamArray, _isInArg, _isOutArg, _optArgInfo, _callerInfo, name, _, ty)) -> + match name with + | Some n -> Some (Item.ArgName(n, ty, Some (ArgumentContainer.Method meth))) + | None -> None + ) + | _ -> [] + ) + + let GetNamedParametersAndSettableFields endOfExprPos hasTextChangedSinceLastTypecheck = + let cnrs = GetCapturedNameResolutions endOfExprPos ResolveOverloads.No |> ResizeArray.toList |> List.rev + let result = + match cnrs with + | CNR(_, Item.CtorGroup(_, ((ctor::_) as ctors)), _, denv, nenv, ad, m) ::_ -> + let props = ResolveCompletionsInType ncenv nenv ResolveCompletionTargets.SettablePropertiesAndFields m ad false ctor.ApparentEnclosingType + let parameters = CollectParameters ctors amap m + let items = props @ parameters + Some (denv, m, items) + | CNR(_, Item.MethodGroup(_, methods, _), _, denv, nenv, ad, m) ::_ -> + let props = + methods + |> List.collect (fun meth -> + let retTy = meth.GetFSharpReturnTy(amap, m, meth.FormalMethodInst) + ResolveCompletionsInType ncenv nenv ResolveCompletionTargets.SettablePropertiesAndFields m ad false retTy + ) + let parameters = CollectParameters methods amap m + let items = props @ parameters + Some (denv, m, items) + | _ -> + None + match result with + | None -> + NameResResult.Empty + | Some (denv, m, items) -> + let items = List.map ItemWithNoInst items + ReturnItemsOfType items g denv m TypeNameResolutionFlag.ResolveTypeNamesToTypeRefs hasTextChangedSinceLastTypecheck + + /// finds captured typing for the given position + let GetExprTypingForPosition(endOfExprPos) = + let quals = + sResolutions.CapturedExpressionTypings + |> Seq.filter (fun (pos,ty,denv,_,_,_) -> + // We only want expression types that end at the particular position in the file we are looking at. + let isLocationWeCareAbout = posEq pos endOfExprPos + // Get rid of function types. True, given a 2-arg curried function "f x y", it is legal to do "(f x).GetType()", + // but you almost never want to do this in practice, and we choose not to offer up any intellisense for + // F# function types. + let isFunction = isFunTy denv.g ty + isLocationWeCareAbout && not isFunction) + |> Seq.toArray + + let thereWereSomeQuals = not (Array.isEmpty quals) + // filter out errors + + let quals = quals + |> Array.filter (fun (_,ty,denv,_,_,_) -> not (isTyparTy denv.g ty && (destTyparTy denv.g ty).IsFromError)) + thereWereSomeQuals, quals + + /// obtains captured typing for the given position + /// if type of captured typing is record - returns list of record fields + let GetRecdFieldsForExpr(r : range) = + let _, quals = GetExprTypingForPosition(r.End) + let bestQual = + match quals with + | [||] -> None + | quals -> + quals |> Array.tryFind (fun (_,_,_,_,_,rq) -> + ignore(r) // for breakpoint + posEq r.Start rq.Start) + match bestQual with + | Some (_,ty,denv,_nenv,ad,m) when isRecdTy denv.g ty -> + let items = NameResolution.ResolveRecordOrClassFieldsOfType ncenv m ad ty false + Some (items, denv, m) + | _ -> None + + /// Looks at the exact expression types at the position to the left of the + /// residue then the source when it was typechecked. + let GetPreciseCompletionListFromExprTypings(parseResults:FSharpParseFileResults, endOfExprPos, filterCtors, hasTextChangedSinceLastTypecheck: (obj * range -> bool)) = + + let thereWereSomeQuals, quals = GetExprTypingForPosition(endOfExprPos) + + match quals with + | [| |] -> + if thereWereSomeQuals then + GetPreciseCompletionListFromExprTypingsResult.NoneBecauseThereWereTypeErrors + else + GetPreciseCompletionListFromExprTypingsResult.None + | _ -> + let bestQual, textChanged = + match parseResults.ParseTree with + | Some(input) -> + match UntypedParseImpl.GetRangeOfExprLeftOfDot(endOfExprPos,Some(input)) with // TODO we say "colAtEndOfNames" everywhere, but that's not really a good name ("foo . $" hit Ctrl-Space at $) + | Some( exprRange) -> + if hasTextChangedSinceLastTypecheck(textSnapshotInfo, exprRange) then + None, true // typecheck is stale, wait for second-chance IntelliSense to bring up right result + else + // See bug 130733. We have an up-to-date sync parse, and know the exact range of the prior expression. + // The quals all already have the same ending position, so find one with a matching starting position, if it exists. + // If not, then the stale typecheck info does not have a capturedExpressionTyping for this exact expression, and the + // user can wait for typechecking to catch up and second-chance intellisense to give the right result. + let qual = + quals |> Array.tryFind (fun (_,_,_,_,_,r) -> + ignore(r) // for breakpoint + posEq exprRange.Start r.Start) + qual, false + | None -> + // TODO In theory I think we should never get to this code path; it would be nice to add an assert. + // In practice, we do get here in some weird cases like "2.0 .. 3.0" and hitting Ctrl-Space in between the two dots of the range operator. + // I wasn't able to track down what was happening in those weird cases, not worth worrying about, it doesn't manifest as a product bug or anything. + None, false + | _ -> None, false + + match bestQual with + | Some bestQual -> + let (_,ty,denv,nenv,ad,m) = bestQual + let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad false ty + let items = items |> List.map ItemWithNoInst + let items = items |> RemoveDuplicateItems g + let items = items |> RemoveExplicitlySuppressed g + let items = items |> FilterItemsForCtors filterCtors + GetPreciseCompletionListFromExprTypingsResult.Some((items,denv,m), ty) + | None -> + if textChanged then GetPreciseCompletionListFromExprTypingsResult.NoneBecauseTypecheckIsStaleAndTextChanged + else GetPreciseCompletionListFromExprTypingsResult.None + + /// Find items in the best naming environment. + let GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, showObsolete) = + let items = NameResolution.ResolvePartialLongIdent ncenv nenv (ConstraintSolver.IsApplicableMethApprox g amap m) m ad plid showObsolete + let items = items |> List.map ItemWithNoInst + let items = items |> RemoveDuplicateItems g + let items = items |> RemoveExplicitlySuppressed g + let items = items |> FilterItemsForCtors filterCtors + (items, nenv.DisplayEnv, m) + + /// Find items in the best naming environment. + let GetEnvironmentLookupResolutionsAtPosition(cursorPos, plid, filterCtors, showObsolete) = + let (nenv,ad),m = GetBestEnvForPos cursorPos + GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, showObsolete) + + /// Find record fields in the best naming environment. + let GetClassOrRecordFieldsEnvironmentLookupResolutions(cursorPos, plid) = + let (nenv, ad),m = GetBestEnvForPos cursorPos + let items = NameResolution.ResolvePartialLongIdentToClassOrRecdFields ncenv nenv m ad plid false + let items = items |> List.map ItemWithNoInst + let items = items |> RemoveDuplicateItems g + let items = items |> RemoveExplicitlySuppressed g + items, nenv.DisplayEnv, m + + /// Resolve a location and/or text to items. + // Three techniques are used + // - look for an exact known name resolution from type checking + // - use the known type of an expression, e.g. (expr).Name, to generate an item list + // - lookup an entire name in the name resolution environment, e.g. A.B.Name, to generate an item list + // + // The overall aim is to resolve as accurately as possible based on what we know from type inference + + let GetBaseClassCandidates = function + | Item.ModuleOrNamespaces _ -> true + | Item.Types(_, ty::_) when (isClassTy g ty) && not (isSealedTy g ty) -> true + | _ -> false + + let GetInterfaceCandidates = function + | Item.ModuleOrNamespaces _ -> true + | Item.Types(_, ty::_) when (isInterfaceTy g ty) -> true + | _ -> false + + + // Return only items with the specified name + let FilterDeclItemsByResidue (getItem: 'a -> Item) residue (items: 'a list) = + let attributedResidue = residue + "Attribute" + let nameMatchesResidue name = (residue = name) || (attributedResidue = name) + + items |> List.filter (fun x -> + let item = getItem x + let n1 = item.DisplayName + match item with + | Item.Types _ -> nameMatchesResidue n1 + | Item.CtorGroup (_, meths) -> + nameMatchesResidue n1 || + meths |> List.exists (fun meth -> + let tcref = meth.ApparentEnclosingTyconRef + tcref.IsProvided || nameMatchesResidue tcref.DisplayName) + | _ -> residue = n1) + + /// Post-filter items to make sure they have precisely the right name + /// This also checks that there are some remaining results + /// exactMatchResidueOpt = Some _ -- means that we are looking for exact matches + let FilterRelevantItemsBy (getItem: 'a -> Item) (exactMatchResidueOpt : _ option) check (items: 'a list, denv, m) = + + // can throw if type is in located in non-resolved CCU: i.e. bigint if reference to System.Numerics is absent + let safeCheck item = try check item with _ -> false + + // Are we looking for items with precisely the given name? + if not (isNil items) && exactMatchResidueOpt.IsSome then + let items = items |> FilterDeclItemsByResidue getItem exactMatchResidueOpt.Value |> List.filter safeCheck + if not (isNil items) then Some(items, denv, m) else None + else + // When (items = []) we must returns Some([],..) and not None + // because this value is used if we want to stop further processing (e.g. let x.$ = ...) + let items = items |> List.filter safeCheck + Some(items, denv, m) + + /// Post-filter items to make sure they have precisely the right name + /// This also checks that there are some remaining results + let (|FilterRelevantItems|_|) getItem exactMatchResidueOpt orig = + FilterRelevantItemsBy getItem exactMatchResidueOpt (fun _ -> true) orig + + /// Find the first non-whitespace position in a line prior to the given character + let FindFirstNonWhitespacePosition (lineStr: string) i = + if i >= lineStr.Length then None + else + let mutable p = i + while p >= 0 && System.Char.IsWhiteSpace(lineStr.[p]) do + p <- p - 1 + if p >= 0 then Some p else None + + let CompletionItem (ty: ValueOption) (assemblySymbol: ValueOption) (item: ItemWithInst) = + let kind = + match item.Item with + | Item.MethodGroup (_, minfo :: _, _) -> CompletionItemKind.Method minfo.IsExtensionMember + | Item.RecdField _ + | Item.Property _ -> CompletionItemKind.Property + | Item.Event _ -> CompletionItemKind.Event + | Item.ILField _ + | Item.Value _ -> CompletionItemKind.Field + | Item.CustomOperation _ -> CompletionItemKind.CustomOperation + | _ -> CompletionItemKind.Other + + { ItemWithInst = item + MinorPriority = 0 + Kind = kind + IsOwnMember = false + Type = match ty with ValueSome x -> Some x | _ -> None + Unresolved = match assemblySymbol with ValueSome x -> Some x.UnresolvedSymbol | _ -> None } + + let DefaultCompletionItem item = CompletionItem ValueNone ValueNone item + + let getItem (x: ItemWithInst) = x.Item + let GetDeclaredItems (parseResultsOpt: FSharpParseFileResults option, lineStr: string, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, + filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, allSymbols: unit -> AssemblySymbol list) = + + // Are the last two chars (except whitespaces) = ".." + let isLikeRangeOp = + match FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1) with + | Some x when x >= 1 && lineStr.[x] = '.' && lineStr.[x - 1] = '.' -> true + | _ -> false + + // if last two chars are .. and we are not in range operator context - no completion + if isLikeRangeOp && not isInRangeOperator then None else + + // Try to use the exact results of name resolution during type checking to generate the results + // This is based on position (i.e. colAtEndOfNamesAndResidue). This is not used if a residueOpt is given. + let nameResItems = + match residueOpt with + | None -> GetPreciseItemsFromNameResolution(line, colAtEndOfNamesAndResidue, None, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) + | Some residue -> + // deals with cases when we have spaces between dot and\or identifier, like A . $ + // if this is our case - then we need to locate end position of the name skipping whitespaces + // this allows us to handle cases like: let x . $ = 1 + match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with + | Some p when lineStr.[p] = '.' -> + match FindFirstNonWhitespacePosition lineStr (p - 1) with + | Some colAtEndOfNames -> + let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based + GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) + | None -> NameResResult.Empty + | _ -> NameResResult.Empty + + // Normalize to form A.B.C.D where D is the residue. It may be empty for "A.B.C." + // residueOpt = Some when we are looking for the exact match + let plid, exactMatchResidueOpt = + match origLongIdentOpt, residueOpt with + | None, _ -> [], None + | Some(origLongIdent), Some _ -> origLongIdent, None + | Some(origLongIdent), None -> + System.Diagnostics.Debug.Assert(not (isNil origLongIdent), "origLongIdent is empty") + // note: as above, this happens when we are called for "precise" resolution - (F1 keyword, data tip etc..) + let plid, residue = List.frontAndBack origLongIdent + plid, Some residue + + let pos = mkPos line loc + let (nenv, ad), m = GetBestEnvForPos pos + + let getType() = + match NameResolution.TryToResolveLongIdentAsType ncenv nenv m plid with + | Some x -> tryDestAppTy g x + | None -> + match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with + | Some p when lineStr.[p] = '.' -> + match FindFirstNonWhitespacePosition lineStr (p - 1) with + | Some colAtEndOfNames -> + let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based + match TryGetTypeFromNameResolution(line, colAtEndOfNames, residueOpt, resolveOverloads) with + | Some x -> tryDestAppTy g x + | _ -> ValueNone + | None -> ValueNone + | _ -> ValueNone + + match nameResItems with + | NameResResult.TypecheckStaleAndTextChanged -> None // second-chance intellisense will try again + | NameResResult.Cancel(denv,m) -> Some([], denv, m) + | NameResResult.Members(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m)) -> + // lookup based on name resolution results successful + Some (items |> List.map (CompletionItem (getType()) ValueNone), denv, m) + | _ -> + match origLongIdentOpt with + | None -> None + | Some _ -> + + // Try to use the type of the expression on the left to help generate a completion list + let qualItems, thereIsADotInvolved = + match parseResultsOpt with + | None -> + // Note, you will get here if the 'reason' is not CompleteWord/MemberSelect/DisplayMemberList, as those are currently the + // only reasons we do a sync parse to have the most precise and likely-to-be-correct-and-up-to-date info. So for example, + // if you do QuickInfo hovering over A in "f(x).A()", you will only get a tip if typechecking has a name-resolution recorded + // for A, not if merely we know the capturedExpressionTyping of f(x) and you very recently typed ".A()" - in that case, + // you won't won't get a tip until the typechecking catches back up. + GetPreciseCompletionListFromExprTypingsResult.None, false + | Some parseResults -> + + match UntypedParseImpl.TryFindExpressionASTLeftOfDotLeftOfCursor(mkPos line colAtEndOfNamesAndResidue,parseResults.ParseTree) with + | Some(pos,_) -> + GetPreciseCompletionListFromExprTypings(parseResults, pos, filterCtors, hasTextChangedSinceLastTypecheck), true + | None -> + // Can get here in a case like: if "f xxx yyy" is legal, and we do "f xxx y" + // We have no interest in expression typings, those are only useful for dot-completion. We want to fallback + // to "Use an environment lookup as the last resort" below + GetPreciseCompletionListFromExprTypingsResult.None, false + + match qualItems,thereIsADotInvolved with + | GetPreciseCompletionListFromExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty), _ + // Initially we only use the expression typings when looking up, e.g. (expr).Nam or (expr).Name1.Nam + // These come through as an empty plid and residue "". Otherwise we try an environment lookup + // and then return to the qualItems. This is because the expression typings are a little inaccurate, primarily because + // it appears we're getting some typings recorded for non-atomic expressions like "f x" + when isNil plid -> + // lookup based on expression typings successful + Some (items |> List.map (CompletionItem (tryDestAppTy g ty) ValueNone), denv, m) + | GetPreciseCompletionListFromExprTypingsResult.NoneBecauseThereWereTypeErrors, _ -> + // There was an error, e.g. we have "." and there is an error determining the type of + // In this case, we don't want any of the fallback logic, rather, we want to produce zero results. + None + | GetPreciseCompletionListFromExprTypingsResult.NoneBecauseTypecheckIsStaleAndTextChanged, _ -> + // we want to report no result and let second-chance intellisense kick in + None + | _, true when isNil plid -> + // If the user just pressed '.' after an _expression_ (not a plid), it is never right to show environment-lookup top-level completions. + // The user might by typing quickly, and the LS didn't have an expression type right before the dot yet. + // Second-chance intellisense will bring up the correct list in a moment. + None + | _ -> + // Use an environment lookup as the last resort + let envItems, denv, m = GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, residueOpt.IsSome) + + let envResult = + match nameResItems, (envItems, denv, m), qualItems with + + // First, use unfiltered name resolution items, if they're not empty + | NameResResult.Members(items, denv, m), _, _ when not (isNil items) -> + // lookup based on name resolution results successful + ValueSome(items |> List.map (CompletionItem (getType()) ValueNone), denv, m) + + // If we have nonempty items from environment that were resolved from a type, then use them... + // (that's better than the next case - here we'd return 'int' as a type) + | _, FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), _ when not (isNil items) -> + // lookup based on name and environment successful + ValueSome(items |> List.map (CompletionItem (getType()) ValueNone), denv, m) + + // Try again with the qualItems + | _, _, GetPreciseCompletionListFromExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty) -> + ValueSome(items |> List.map (CompletionItem (tryDestAppTy g ty) ValueNone), denv, m) + + | _ -> ValueNone + + let globalResult = + match origLongIdentOpt with + | None | Some [] -> + let globalItems = + allSymbols() + |> List.filter (fun x -> not x.Symbol.IsExplicitlySuppressed) + |> List.filter (fun x -> + match x.Symbol with + | :? FSharpMemberOrFunctionOrValue as m when m.IsConstructor && filterCtors = ResolveTypeNamesToTypeRefs -> false + | _ -> true) + + let getItem (x: AssemblySymbol) = x.Symbol.Item + + match globalItems, denv, m with + | FilterRelevantItems getItem exactMatchResidueOpt (globalItemsFiltered, denv, m) when not (isNil globalItemsFiltered) -> + globalItemsFiltered + |> List.map(fun globalItem -> CompletionItem (getType()) (ValueSome globalItem) (ItemWithNoInst globalItem.Symbol.Item)) + |> fun r -> ValueSome(r, denv, m) + | _ -> ValueNone + | _ -> ValueNone // do not return unresolved items after dot + + match envResult, globalResult with + | ValueSome (items, denv, m), ValueSome (gItems,_,_) -> Some (items @ gItems, denv, m) + | ValueSome x, ValueNone -> Some x + | ValueNone, ValueSome y -> Some y + | ValueNone, ValueNone -> None + + + let toCompletionItems (items: ItemWithInst list, denv: DisplayEnv, m: range ) = + items |> List.map DefaultCompletionItem, denv, m + + /// Get the auto-complete items at a particular location. + let GetDeclItemsForNamesAtPosition(ctok: CompilationThreadToken, parseResultsOpt: FSharpParseFileResults option, origLongIdentOpt: string list option, + residueOpt:string option, lastDotPos: int option, line:int, lineStr:string, colAtEndOfNamesAndResidue, filterCtors, resolveOverloads, + getAllSymbols: unit -> AssemblySymbol list, hasTextChangedSinceLastTypecheck: (obj * range -> bool)) + : (CompletionItem list * DisplayEnv * CompletionContext option * range) option = + RequireCompilationThread ctok // the operations in this method need the reactor thread + + let loc = + match colAtEndOfNamesAndResidue with + | pastEndOfLine when pastEndOfLine >= lineStr.Length -> lineStr.Length + | atDot when lineStr.[atDot] = '.' -> atDot + 1 + | atStart when atStart = 0 -> 0 + | otherwise -> otherwise - 1 + + // Look for a "special" completion context + let completionContext = + parseResultsOpt + |> Option.bind (fun x -> x.ParseTree) + |> Option.bind (fun parseTree -> UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseTree, lineStr)) + + let res = + match completionContext with + // Invalid completion locations + | Some CompletionContext.Invalid -> None + + // Completion at 'inherit C(...)" + | Some (CompletionContext.Inherit(InheritanceContext.Class, (plid, _))) -> + GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) + |> FilterRelevantItemsBy getItem None (getItem >> GetBaseClassCandidates) + |> Option.map toCompletionItems + + // Completion at 'interface ..." + | Some (CompletionContext.Inherit(InheritanceContext.Interface, (plid, _))) -> + GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) + |> FilterRelevantItemsBy getItem None (getItem >> GetInterfaceCandidates) + |> Option.map toCompletionItems + + // Completion at 'implement ..." + | Some (CompletionContext.Inherit(InheritanceContext.Unknown, (plid, _))) -> + GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) + |> FilterRelevantItemsBy getItem None (getItem >> (fun t -> GetBaseClassCandidates t || GetInterfaceCandidates t)) + |> Option.map toCompletionItems + + // Completion at ' { XXX = ... } " + | Some(CompletionContext.RecordField(RecordContext.New(plid, _))) -> + // { x. } can be either record construction or computation expression. Try to get all visible record fields first + match GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, plid) |> toCompletionItems with + | [],_,_ -> + // no record fields found, return completion list as if we were outside any computation expression + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, false, fun() -> []) + | result -> Some(result) + + // Completion at ' { XXX = ... with ... } " + | Some(CompletionContext.RecordField(RecordContext.CopyOnUpdate(r, (plid, _)))) -> + match GetRecdFieldsForExpr(r) with + | None -> + Some (GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, plid)) + |> Option.map toCompletionItems + | Some (items, denv, m) -> + Some (List.map ItemWithNoInst items, denv, m) + |> Option.map toCompletionItems + + // Completion at ' { XXX = ... with ... } " + | Some(CompletionContext.RecordField(RecordContext.Constructor(typeName))) -> + Some(GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, [typeName])) + |> Option.map toCompletionItems + + // Completion at ' SomeMethod( ... ) ' with named arguments + | Some(CompletionContext.ParameterList (endPos, fields)) -> + let results = GetNamedParametersAndSettableFields endPos hasTextChangedSinceLastTypecheck + + let declaredItems = + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, + hasTextChangedSinceLastTypecheck, false, getAllSymbols) + + match results with + | NameResResult.Members(items, denv, m) -> + let filtered = + items + |> RemoveDuplicateItems g + |> RemoveExplicitlySuppressed g + |> List.filter (fun item -> not (fields.Contains item.Item.DisplayName)) + |> List.map (fun item -> + { ItemWithInst = item + Kind = CompletionItemKind.Argument + MinorPriority = 0 + IsOwnMember = false + Type = None + Unresolved = None }) + match declaredItems with + | None -> Some (toCompletionItems (items, denv, m)) + | Some (declItems, declaredDisplayEnv, declaredRange) -> Some (filtered @ declItems, declaredDisplayEnv, declaredRange) + | _ -> declaredItems + + | Some(CompletionContext.AttributeApplication) -> + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) + |> Option.map (fun (items, denv, m) -> + items + |> List.filter (fun cItem -> + match cItem.Item with + | Item.ModuleOrNamespaces _ -> true + | _ when IsAttribute infoReader cItem.Item -> true + | _ -> false), denv, m) + + | Some(CompletionContext.OpenDeclaration) -> + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) + |> Option.map (fun (items, denv, m) -> + items |> List.filter (fun x -> match x.Item with Item.ModuleOrNamespaces _ -> true | _ -> false), denv, m) + + // Completion at '(x: ...)" + | Some (CompletionContext.PatternType) -> + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) + |> Option.map (fun (items, denv, m) -> + items + |> List.filter (fun cItem -> + match cItem.Item with + | Item.ModuleOrNamespaces _ + | Item.Types _ + | Item.UnqualifiedType _ + | Item.ExnCase _ -> true + | _ -> false), denv, m) + + // Other completions + | cc -> + match residueOpt |> Option.bind Seq.tryHead with + | Some ''' -> + // The last token in + // let x = 'E + // is Ident with text "'E", however it's either unfinished char literal or generic parameter. + // We should not provide any completion in the former case, and we don't provide it for the latter one for now + // because providing generic parameters list is context aware, which we don't have here (yet). + None + | _ -> + let isInRangeOperator = (match cc with Some (CompletionContext.RangeOperator) -> true | _ -> false) + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, getAllSymbols) + + res |> Option.map (fun (items, denv, m) -> items, denv, completionContext, m) + + /// Return 'false' if this is not a completion item valid in an interface file. + let IsValidSignatureFileItem item = + match item with + | Item.Types _ | Item.ModuleOrNamespaces _ -> true + | _ -> false + + /// Find the most precise display context for the given line and column. + member __.GetBestDisplayEnvForPos cursorPos = GetBestEnvForPos cursorPos + + member __.GetVisibleNamespacesAndModulesAtPosition(cursorPos: pos) : ModuleOrNamespaceRef list = + let (nenv, ad), m = GetBestEnvForPos cursorPos + NameResolution.GetVisibleNamespacesAndModulesAtPoint ncenv nenv m ad + + /// Determines if a long ident is resolvable at a specific point. + member __.IsRelativeNameResolvable(cursorPos: pos, plid: string list, item: Item) : bool = + ErrorScope.Protect + Range.range0 + (fun () -> + /// Find items in the best naming environment. + let (nenv, ad), m = GetBestEnvForPos cursorPos + NameResolution.IsItemResolvable ncenv nenv m ad plid item) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in IsRelativeNameResolvable: '%s'" msg) + false) + + /// Determines if a long ident is resolvable at a specific point. + member scope.IsRelativeNameResolvableFromSymbol(cursorPos: pos, plid: string list, symbol: FSharpSymbol) : bool = + scope.IsRelativeNameResolvable(cursorPos, plid, symbol.Item) + + /// Get the auto-complete items at a location + member __.GetDeclarations (ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck) = + let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllEntities, hasTextChangedSinceLastTypecheck) with + | None -> FSharpDeclarationListInfo.Empty + | Some (items, denv, ctx, m) -> + let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items + let getAccessibility item = FSharpSymbol.GetAccessibility (FSharpSymbol.Create(cenv, item)) + let currentNamespaceOrModule = + parseResultsOpt + |> Option.bind (fun x -> x.ParseTree) + |> Option.map (fun parsedInput -> UntypedParseImpl.GetFullNameOfSmallestModuleOrNamespaceAtPoint(parsedInput, mkPos line 0)) + let isAttributeApplication = ctx = Some CompletionContext.AttributeApplication + FSharpDeclarationListInfo.Create(infoReader,m,denv,getAccessibility,items,reactorOps,currentNamespaceOrModule,isAttributeApplication)) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarations: '%s'" msg) + FSharpDeclarationListInfo.Error msg) + + /// Get the symbols for auto-complete items at a location + member __.GetDeclarationListSymbols (ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck) = + let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllEntities, hasTextChangedSinceLastTypecheck) with + | None -> List.Empty + | Some (items, denv, _, m) -> + let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items + + //do filtering like Declarationset + let items = items |> RemoveExplicitlySuppressedCompletionItems g + + // Sort by name. For things with the same name, + // - show types with fewer generic parameters first + // - show types before over other related items - they usually have very useful XmlDocs + let items = + items |> List.sortBy (fun d -> + let n = + match d.Item with + | Item.Types (_,(TType_app(tcref,_) :: _)) -> 1 + tcref.TyparsNoRange.Length + // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name + | Item.FakeInterfaceCtor (TType_app(tcref,_)) + | Item.DelegateCtor (TType_app(tcref,_)) -> 1000 + tcref.TyparsNoRange.Length + // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name + | Item.CtorGroup (_, (cinfo :: _)) -> 1000 + 10 * cinfo.DeclaringTyconRef.TyparsNoRange.Length + | _ -> 0 + (d.Item.DisplayName,n)) + + // Remove all duplicates. We've put the types first, so this removes the DelegateCtor and DefaultStructCtor's. + let items = items |> RemoveDuplicateCompletionItems g + + // Group by compiled name for types, display name for functions + // (We don't want types with the same display name to be grouped as overloads) + let items = + items |> List.groupBy (fun d -> + match d.Item with + | Item.Types (_,(TType_app(tcref,_) :: _)) + | Item.ExnCase tcref -> tcref.LogicalName + | Item.UnqualifiedType(tcref :: _) + | Item.FakeInterfaceCtor (TType_app(tcref,_)) + | Item.DelegateCtor (TType_app(tcref,_)) -> tcref.CompiledName + | Item.CtorGroup (_, (cinfo :: _)) -> + cinfo.ApparentEnclosingTyconRef.CompiledName + | _ -> d.Item.DisplayName) + + // Filter out operators (and list) + let items = + // Check whether this item looks like an operator. + let isOpItem(nm, item: CompletionItem list) = + match item |> List.map (fun x -> x.Item) with + | [Item.Value _] + | [Item.MethodGroup(_,[_],_)] -> IsOperatorName nm + | [Item.UnionCase _] -> IsOperatorName nm + | _ -> false + + let isFSharpList nm = (nm = "[]") // list shows up as a Type and a UnionCase, only such entity with a symbolic name, but want to filter out of intellisense + + items |> List.filter (fun (nm,items) -> not (isOpItem(nm,items)) && not(isFSharpList nm)) + + let items = + // Filter out duplicate names + items |> List.map (fun (_nm,itemsWithSameName) -> + match itemsWithSameName with + | [] -> failwith "Unexpected empty bag" + | items -> + items + |> List.map (fun item -> let symbol = FSharpSymbol.Create(cenv, item.Item) + FSharpSymbolUse(g, denv, symbol, ItemOccurence.Use, m))) + + //end filtering + items) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarationListSymbols: '%s'" msg) + []) + + /// Get the "reference resolution" tooltip for at a location + member __.GetReferenceResolutionStructuredToolTipText(ctok, line,col) = + + RequireCompilationThread ctok // the operations in this method need the reactor thread but the reasons why are not yet grounded + + let pos = mkPos line col + let isPosMatch(pos, ar:AssemblyReference) : bool = + let isRangeMatch = (Range.rangeContainsPos ar.Range pos) + let isNotSpecialRange = not (Range.equals ar.Range rangeStartup) && not (Range.equals ar.Range range0) && not (Range.equals ar.Range rangeCmdArgs) + let isMatch = isRangeMatch && isNotSpecialRange + isMatch + + let dataTipOfReferences() = + let matches = + match loadClosure with + | None -> [] + | Some(loadClosure) -> + loadClosure.References + |> List.map snd + |> List.concat + |> List.filter(fun ar->isPosMatch(pos, ar.originalReference)) + + match matches with + | resolved::_ // Take the first seen + | [resolved] -> + let tip = wordL (TaggedTextOps.tagStringLiteral((resolved.prepareToolTip ()).TrimEnd([|'\n'|]))) + FSharpStructuredToolTipText.FSharpToolTipText [FSharpStructuredToolTipElement.Single(tip, FSharpXmlDoc.None)] + + | [] -> FSharpStructuredToolTipText.FSharpToolTipText [] + + ErrorScope.Protect Range.range0 + dataTipOfReferences + (fun err -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetReferenceResolutionStructuredToolTipText: '%s'" err) + FSharpToolTipText [FSharpStructuredToolTipElement.CompositionError err]) + + // GetToolTipText: return the "pop up" (or "Quick Info") text given a certain context. + member __.GetStructuredToolTipText(ctok, line, lineStr, colAtEndOfNames, names) = + let Compute() = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, None,Some(names),None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []),fun _ -> false) with + | None -> FSharpToolTipText [] + | Some(items, denv, _, m) -> + FSharpToolTipText(items |> List.map (fun x -> FormatStructuredDescriptionOfItem false infoReader m denv x.ItemWithInst))) + (fun err -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetStructuredToolTipText: '%s'" err) + FSharpToolTipText [FSharpStructuredToolTipElement.CompositionError err]) + + // See devdiv bug 646520 for rationale behind truncating and caching these quick infos (they can be big!) + let key = line,colAtEndOfNames,lineStr + match getToolTipTextCache.TryGet (ctok, key) with + | Some res -> res + | None -> + let res = Compute() + getToolTipTextCache.Put(ctok, key,res) + res + + member __.GetF1Keyword (ctok, line, lineStr, colAtEndOfNames, names) : string option = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, None, Some names, None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []), fun _ -> false) with // F1 Keywords do not distinguish between overloads + | None -> None + | Some (items: CompletionItem list, _,_, _) -> + match items with + | [] -> None + | [item] -> + GetF1Keyword g item.Item + | _ -> + // handle new Type() + let allTypes, constr, ty = + List.fold + (fun (allTypes,constr,ty) (item: CompletionItem) -> + match item.Item, constr, ty with + | (Item.Types _) as t, _, None -> allTypes, constr, Some t + | (Item.Types _), _, _ -> allTypes, constr, ty + | (Item.CtorGroup _), None, _ -> allTypes, Some item.Item, ty + | _ -> false, None, None) + (true,None,None) items + match allTypes, constr, ty with + | true, Some (Item.CtorGroup(_, _) as item), _ + -> GetF1Keyword g item + | true, _, Some ty + -> GetF1Keyword g ty + | _ -> None + ) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetF1Keyword: '%s'" msg) + None) + + member __.GetMethods (ctok, line, lineStr, colAtEndOfNames, namesOpt) = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, None,namesOpt,None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.No,(fun() -> []),fun _ -> false) with + | None -> FSharpMethodGroup("",[| |]) + | Some (items, denv, _, m) -> + // GetDeclItemsForNamesAtPosition returns Items.Types and Item.CtorGroup for `new T(|)`, + // the Item.Types is not needed here as it duplicates (at best) parameterless ctor. + let ctors = items |> List.filter (fun x -> match x.Item with Item.CtorGroup _ -> true | _ -> false) + let items = + match ctors with + | [] -> items + | ctors -> ctors + FSharpMethodGroup.Create(infoReader, m, denv, items |> List.map (fun x -> x.ItemWithInst))) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetMethods: '%s'" msg) + FSharpMethodGroup(msg,[| |])) + + member __.GetMethodsAsSymbols (ctok, line, lineStr, colAtEndOfNames, names) = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None,line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []),fun _ -> false) with + | None | Some ([],_,_,_) -> None + | Some (items, denv, _, m) -> + let allItems = items |> List.collect (fun item -> SymbolHelpers.FlattenItems g m item.Item) + let symbols = allItems |> List.map (fun item -> FSharpSymbol.Create(cenv, item)) + Some (symbols, denv, m) + ) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetMethodsAsSymbols: '%s'" msg) + None) + + member __.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag) = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with + | None + | Some ([], _, _, _) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "") + | Some (item :: _, _, _, _) -> + let getTypeVarNames (ilinfo: ILMethInfo) = + let classTypeParams = ilinfo.DeclaringTyconRef.ILTyconRawMetadata.GenericParams |> List.map (fun paramDef -> paramDef.Name) + let methodTypeParams = ilinfo.FormalMethodTypars |> List.map (fun ty -> ty.Name) + classTypeParams @ methodTypeParams |> Array.ofList + + let result = + match item.Item with + | Item.CtorGroup (_, (ILMeth (_,ilinfo,_)) :: _) -> + match ilinfo.MetadataScope with + | ILScopeRef.Assembly assemblyRef -> + let typeVarNames = getTypeVarNames ilinfo + ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes + |> Option.map (fun args -> + let externalSym = ExternalSymbol.Constructor (ilinfo.ILMethodRef.DeclaringTypeRef.FullName, args) + FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) + | _ -> None + + | Item.MethodGroup (name, (ILMeth (_,ilinfo,_)) :: _, _) -> + match ilinfo.MetadataScope with + | ILScopeRef.Assembly assemblyRef -> + let typeVarNames = getTypeVarNames ilinfo + ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes + |> Option.map (fun args -> + let externalSym = ExternalSymbol.Method (ilinfo.ILMethodRef.DeclaringTypeRef.FullName, name, args, ilinfo.ILMethodRef.GenericArity) + FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) + | _ -> None + + | Item.Property (name, ILProp propInfo :: _) -> + let methInfo = + if propInfo.HasGetter then Some propInfo.GetterMethod + elif propInfo.HasSetter then Some propInfo.SetterMethod + else None + + match methInfo with + | Some methInfo -> + match methInfo.MetadataScope with + | ILScopeRef.Assembly assemblyRef -> + let externalSym = ExternalSymbol.Property (methInfo.ILMethodRef.DeclaringTypeRef.FullName, name) + Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) + | _ -> None + | None -> None + + | Item.ILField (ILFieldInfo (typeInfo, fieldDef)) when not typeInfo.TyconRefOfRawMetadata.IsLocalRef -> + match typeInfo.ILScopeRef with + | ILScopeRef.Assembly assemblyRef -> + let externalSym = ExternalSymbol.Field (typeInfo.ILTypeRef.FullName, fieldDef.Name) + Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) + | _ -> None + + | Item.Event (ILEvent (ILEventInfo (typeInfo, eventDef))) when not typeInfo.TyconRefOfRawMetadata.IsLocalRef -> + match typeInfo.ILScopeRef with + | ILScopeRef.Assembly assemblyRef -> + let externalSym = ExternalSymbol.Event (typeInfo.ILTypeRef.FullName, eventDef.Name) + Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) + | _ -> None + + | Item.ImplicitOp(_, {contents = Some(TraitConstraintSln.FSMethSln(_, _vref, _))}) -> + //Item.Value(vref) + None + + | Item.Types (_, TType_app (tr, _) :: _) when tr.IsLocalRef && tr.IsTypeAbbrev -> None + + | Item.Types (_, [ AppTy g (tr, _) ]) when not tr.IsLocalRef -> + match tr.TypeReprInfo, tr.PublicPath with + | TILObjectRepr(TILObjectReprData (ILScopeRef.Assembly assemblyRef, _, _)), Some (PubPath parts) -> + let fullName = parts |> String.concat "." + Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, ExternalSymbol.Type fullName)) + | _ -> None + | _ -> None + match result with + | Some x -> x + | None -> + match rangeOfItem g preferFlag item.Item with + | Some itemRange -> + let projectDir = Filename.directoryName (if projectFileName = "" then mainInputFileName else projectFileName) + let range = fileNameOfItem g (Some projectDir) itemRange item.Item + mkRange range itemRange.Start itemRange.End + |> FSharpFindDeclResult.DeclFound + | None -> + match item.Item with +#if !NO_EXTENSIONTYPING +// provided items may have TypeProviderDefinitionLocationAttribute that binds them to some location + | Item.CtorGroup (name, ProvidedMeth (_)::_ ) + | Item.MethodGroup(name, ProvidedMeth (_)::_, _) + | Item.Property (name, ProvidedProp (_)::_ ) -> FSharpFindDeclFailureReason.ProvidedMember name + | Item.Event ( ProvidedEvent(_) as e ) -> FSharpFindDeclFailureReason.ProvidedMember e.EventName + | Item.ILField ( ProvidedField(_) as f ) -> FSharpFindDeclFailureReason.ProvidedMember f.FieldName + | SymbolHelpers.ItemIsProvidedType g (tcref) -> FSharpFindDeclFailureReason.ProvidedType tcref.DisplayName +#endif + | _ -> FSharpFindDeclFailureReason.Unknown "" + |> FSharpFindDeclResult.DeclNotFound + ) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarationLocation: '%s'" msg) + FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown msg)) + + member __.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) = + ErrorScope.Protect Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with + | None | Some ([], _, _, _) -> None + | Some (item :: _, denv, _, m) -> + let symbol = FSharpSymbol.Create(cenv, item.Item) + Some (symbol, denv, m) + ) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetSymbolUseAtLocation: '%s'" msg) + None) + + member __.PartialAssemblySignatureForFile = + FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ccuSigForFile) + + member __.AccessRights = tcAccessRights + + member __.GetReferencedAssemblies() = + [ for x in tcImports.GetImportedAssemblies() do + yield FSharpAssembly(g, tcImports, x.FSharpViewOfMetadata) ] + + member __.GetFormatSpecifierLocationsAndArity() = + sSymbolUses.GetFormatSpecifierLocationsAndArity() + + member __.GetSemanticClassification(range: range option) : (range * SemanticClassificationType) [] = + ErrorScope.Protect Range.range0 + (fun () -> + let (|LegitTypeOccurence|_|) = function + | ItemOccurence.UseInType + | ItemOccurence.UseInAttribute + | ItemOccurence.Use _ + | ItemOccurence.Binding _ + | ItemOccurence.Pattern _ -> Some() + | _ -> None + + let (|OptionalArgumentAttribute|_|) ttype = + match ttype with + | TType.TType_app(tref, _) when tref.Stamp = g.attrib_OptionalArgumentAttribute.TyconRef.Stamp -> Some() + | _ -> None + + let (|KeywordIntrinsicValue|_|) (vref: ValRef) = + if valRefEq g g.raise_vref vref || + valRefEq g g.reraise_vref vref || + valRefEq g g.typeof_vref vref || + valRefEq g g.typedefof_vref vref || + valRefEq g g.sizeof_vref vref + // TODO uncomment this after `nameof` operator is implemented + // || valRefEq g g.nameof_vref vref + then Some() + else None + + let (|EnumCaseFieldInfo|_|) (rfinfo : RecdFieldInfo) = + match rfinfo.TyconRef.TypeReprInfo with + | TFSharpObjectRepr x -> + match x.fsobjmodel_kind with + | TTyconEnum -> Some () + | _ -> None + | _ -> None + + let resolutions = + match range with + | Some range -> + sResolutions.CapturedNameResolutions + |> Seq.filter (fun cnr -> rangeContainsPos range cnr.Range.Start || rangeContainsPos range cnr.Range.End) + | None -> + sResolutions.CapturedNameResolutions :> seq<_> + + let isDisposableTy (ty: TType) = + protectAssemblyExplorationNoReraise false false (fun () -> Infos.ExistsHeadTypeInEntireHierarchy g amap range0 ty g.tcref_System_IDisposable) + + let isStructTyconRef (tyconRef: TyconRef) = + let ty = generalizedTyconRef tyconRef + let underlyingTy = stripTyEqnsAndMeasureEqns g ty + isStructTy g underlyingTy + + let isValRefMutable (vref: ValRef) = + // Mutable values, ref cells, and non-inref byrefs are mutable. + vref.IsMutable + || Tastops.isRefCellTy g vref.Type + || (Tastops.isByrefTy g vref.Type && not (Tastops.isInByrefTy g vref.Type)) + + let isRecdFieldMutable (rfinfo: RecdFieldInfo) = + (rfinfo.RecdField.IsMutable && rfinfo.LiteralValue.IsNone) + || Tastops.isRefCellTy g rfinfo.RecdField.FormalType + + resolutions + |> Seq.choose (fun cnr -> + match cnr with + // 'seq' in 'seq { ... }' gets colored as keywords + | CNR(_, (Item.Value vref), ItemOccurence.Use, _, _, _, m) when valRefEq g g.seq_vref vref -> + Some (m, SemanticClassificationType.ComputationExpression) + | CNR(_, (Item.Value vref), _, _, _, _, m) when isValRefMutable vref -> + Some (m, SemanticClassificationType.MutableVar) + | CNR(_, Item.Value KeywordIntrinsicValue, ItemOccurence.Use, _, _, _, m) -> + Some (m, SemanticClassificationType.IntrinsicFunction) + | CNR(_, (Item.Value vref), _, _, _, _, m) when isFunction g vref.Type -> + if valRefEq g g.range_op_vref vref || valRefEq g g.range_step_op_vref vref then + None + elif vref.IsPropertyGetterMethod || vref.IsPropertySetterMethod then + Some (m, SemanticClassificationType.Property) + elif IsOperatorName vref.DisplayName then + Some (m, SemanticClassificationType.Operator) + else + Some (m, SemanticClassificationType.Function) + | CNR(_, Item.RecdField rfinfo, _, _, _, _, m) when isRecdFieldMutable rfinfo -> + Some (m, SemanticClassificationType.MutableVar) + | CNR(_, Item.RecdField rfinfo, _, _, _, _, m) when isFunction g rfinfo.FieldType -> + Some (m, SemanticClassificationType.Function) + | CNR(_, Item.RecdField EnumCaseFieldInfo, _, _, _, _, m) -> + Some (m, SemanticClassificationType.Enumeration) + | CNR(_, Item.MethodGroup _, _, _, _, _, m) -> + Some (m, SemanticClassificationType.Function) + // custom builders, custom operations get colored as keywords + | CNR(_, (Item.CustomBuilder _ | Item.CustomOperation _), ItemOccurence.Use, _, _, _, m) -> + Some (m, SemanticClassificationType.ComputationExpression) + // types get colored as types when they occur in syntactic types or custom attributes + // typevariables get colored as types when they occur in syntactic types custom builders, custom operations get colored as keywords + | CNR(_, Item.Types (_, [OptionalArgumentAttribute]), LegitTypeOccurence, _, _, _, _) -> None + | CNR(_, Item.CtorGroup(_, [MethInfo.FSMeth(_, OptionalArgumentAttribute, _, _)]), LegitTypeOccurence, _, _, _, _) -> None + | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists (isInterfaceTy g) -> + Some (m, SemanticClassificationType.Interface) + | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists (isStructTy g) -> + Some (m, SemanticClassificationType.ValueType) + | CNR(_, Item.Types(_, TType_app(tyconRef, TType_measure _ :: _) :: _), LegitTypeOccurence, _, _, _, m) when isStructTyconRef tyconRef -> + Some (m, SemanticClassificationType.ValueType) + | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists isDisposableTy -> + Some (m, SemanticClassificationType.Disposable) + | CNR(_, Item.Types _, LegitTypeOccurence, _, _, _, m) -> + Some (m, SemanticClassificationType.ReferenceType) + | CNR(_, (Item.TypeVar _ ), LegitTypeOccurence, _, _, _, m) -> + Some (m, SemanticClassificationType.TypeArgument) + | CNR(_, Item.UnqualifiedType tyconRefs, LegitTypeOccurence, _, _, _, m) -> + if tyconRefs |> List.exists (fun tyconRef -> tyconRef.Deref.IsStructOrEnumTycon) then + Some (m, SemanticClassificationType.ValueType) + else Some (m, SemanticClassificationType.ReferenceType) + | CNR(_, Item.CtorGroup(_, minfos), LegitTypeOccurence, _, _, _, m) -> + if minfos |> List.exists (fun minfo -> isStructTy g minfo.ApparentEnclosingType) then + Some (m, SemanticClassificationType.ValueType) + else Some (m, SemanticClassificationType.ReferenceType) + | CNR(_, Item.ExnCase _, LegitTypeOccurence, _, _, _, m) -> + Some (m, SemanticClassificationType.ReferenceType) + | CNR(_, Item.ModuleOrNamespaces refs, LegitTypeOccurence, _, _, _, m) when refs |> List.exists (fun x -> x.IsModule) -> + Some (m, SemanticClassificationType.Module) + | CNR(_, (Item.ActivePatternCase _ | Item.UnionCase _ | Item.ActivePatternResult _), _, _, _, _, m) -> + Some (m, SemanticClassificationType.UnionCase) + | _ -> None) + |> Seq.toArray + |> Array.append (sSymbolUses.GetFormatSpecifierLocationsAndArity() |> Array.map (fun m -> fst m, SemanticClassificationType.Printf)) + ) + (fun msg -> + Trace.TraceInformation(sprintf "FCS: recovering from error in GetSemanticClassification: '%s'" msg) + Array.empty) + + /// The resolutions in the file + member __.ScopeResolutions = sResolutions + + /// The uses of symbols in the analyzed file + member __.ScopeSymbolUses = sSymbolUses + + member __.TcGlobals = g + + member __.TcImports = tcImports + + /// The inferred signature of the file + member __.CcuSigForFile = ccuSigForFile + + /// The assembly being analyzed + member __.ThisCcu = thisCcu + + member __.ImplementationFile = implFileOpt + + /// All open declarations in the file, including auto open modules + member __.OpenDeclarations = openDeclarations + + member __.SymbolEnv = cenv + + override __.ToString() = "TypeCheckInfo(" + mainInputFileName + ")" + +type FSharpParsingOptions = + { SourceFiles: string [] + ConditionalCompilationDefines: string list + ErrorSeverityOptions: FSharpErrorSeverityOptions + IsInteractive: bool + LightSyntax: bool option + CompilingFsLib: bool + IsExe: bool } + + member x.LastFileName = + Debug.Assert(not (Array.isEmpty x.SourceFiles), "Parsing options don't contain any file") + Array.last x.SourceFiles + + static member Default = + { SourceFiles = Array.empty + ConditionalCompilationDefines = [] + ErrorSeverityOptions = FSharpErrorSeverityOptions.Default + IsInteractive = false + LightSyntax = None + CompilingFsLib = false + IsExe = false } + + static member FromTcConfig(tcConfig: TcConfig, sourceFiles, isInteractive: bool) = + { SourceFiles = sourceFiles + ConditionalCompilationDefines = tcConfig.conditionalCompilationDefines + ErrorSeverityOptions = tcConfig.errorSeverityOptions + IsInteractive = isInteractive + LightSyntax = tcConfig.light + CompilingFsLib = tcConfig.compilingFslib + IsExe = tcConfig.target.IsExe } + + static member FromTcConfigBuidler(tcConfigB: TcConfigBuilder, sourceFiles, isInteractive: bool) = + { + SourceFiles = sourceFiles + ConditionalCompilationDefines = tcConfigB.conditionalCompilationDefines + ErrorSeverityOptions = tcConfigB.errorSeverityOptions + IsInteractive = isInteractive + LightSyntax = tcConfigB.light + CompilingFsLib = tcConfigB.compilingFslib + IsExe = tcConfigB.target.IsExe + } + +module internal ParseAndCheckFile = + + /// Error handler for parsing & type checking while processing a single file + type ErrorHandler(reportErrors, mainInputFileName, errorSeverityOptions: FSharpErrorSeverityOptions, sourceText: ISourceText, suggestNamesForErrors: bool) = + let mutable options = errorSeverityOptions + let errorsAndWarningsCollector = new ResizeArray<_>() + let mutable errorCount = 0 + + // We'll need number of lines for adjusting error messages at EOF + let fileInfo = sourceText.GetLastCharacterPosition() + + // This function gets called whenever an error happens during parsing or checking + let diagnosticSink sev (exn: PhasedDiagnostic) = + // Sanity check here. The phase of an error should be in a phase known to the language service. + let exn = + if not(exn.IsPhaseInCompile()) then + // Reaching this point means that the error would be sticky if we let it prop up to the language service. + // Assert and recover by replacing phase with one known to the language service. + Trace.TraceInformation(sprintf "The subcategory '%s' seen in an error should not be seen by the language service" (exn.Subcategory())) + { exn with Phase = BuildPhase.TypeCheck } + else exn + if reportErrors then + let report exn = + for ei in ErrorHelpers.ReportError (options, false, mainInputFileName, fileInfo, (exn, sev), suggestNamesForErrors) do + errorsAndWarningsCollector.Add ei + if sev = FSharpErrorSeverity.Error then + errorCount <- errorCount + 1 + + match exn with +#if !NO_EXTENSIONTYPING + | { Exception = (:? TypeProviderError as tpe) } -> tpe.Iter(fun e -> report { exn with Exception = e }) +#endif + | e -> report e + + let errorLogger = + { new ErrorLogger("ErrorHandler") with + member x.DiagnosticSink (exn, isError) = diagnosticSink (if isError then FSharpErrorSeverity.Error else FSharpErrorSeverity.Warning) exn + member x.ErrorCount = errorCount } + + // Public members + member __.ErrorLogger = errorLogger + + member __.CollectedDiagnostics = errorsAndWarningsCollector.ToArray() + + member __.ErrorCount = errorCount + + member __.ErrorSeverityOptions with set opts = options <- opts + + member __.AnyErrors = errorCount > 0 + + let getLightSyntaxStatus fileName options = + let lower = String.lowercase fileName + let lightOnByDefault = List.exists (Filename.checkSuffix lower) FSharpLightSyntaxFileSuffixes + let lightSyntaxStatus = if lightOnByDefault then (options.LightSyntax <> Some false) else (options.LightSyntax = Some true) + LightSyntaxStatus(lightSyntaxStatus, true) + + let createLexerFunction fileName options lexbuf (errHandler: ErrorHandler) = + let lightSyntaxStatus = getLightSyntaxStatus fileName options + + // If we're editing a script then we define INTERACTIVE otherwise COMPILED. + // Since this parsing for intellisense we always define EDITING. + let defines = (SourceFileImpl.AdditionalDefinesForUseInEditor options.IsInteractive) @ options.ConditionalCompilationDefines + + // Note: we don't really attempt to intern strings across a large scope. + let lexResourceManager = new Lexhelp.LexResourceManager() + + // When analyzing files using ParseOneFile, i.e. for the use of editing clients, we do not apply line directives. + // TODO(pathmap): expose PathMap on the service API, and thread it through here + let lexargs = mkLexargs(fileName, defines, lightSyntaxStatus, lexResourceManager, ref [], errHandler.ErrorLogger, PathMap.empty) + let lexargs = { lexargs with applyLineDirectives = false } + + let tokenizer = LexFilter.LexFilter(lightSyntaxStatus, options.CompilingFsLib, Lexer.token lexargs true, lexbuf) + tokenizer.Lexer + + let createLexbuf sourceText = + UnicodeLexing.SourceTextAsLexbuf(sourceText) + + let matchBraces(sourceText: ISourceText, fileName, options: FSharpParsingOptions, userOpName: string, suggestNamesForErrors: bool) = + let delayedLogger = CapturingErrorLogger("matchBraces") + use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _ -> delayedLogger) + use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse + + Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "matchBraces", fileName) + + // Make sure there is an ErrorLogger installed whenever we do stuff that might record errors, even if we ultimately ignore the errors + let delayedLogger = CapturingErrorLogger("matchBraces") + use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _ -> delayedLogger) + use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse + + let matchingBraces = new ResizeArray<_>() + Lexhelp.usingLexbufForParsing(createLexbuf sourceText, fileName) (fun lexbuf -> + let errHandler = ErrorHandler(false, fileName, options.ErrorSeverityOptions, sourceText, suggestNamesForErrors) + let lexfun = createLexerFunction fileName options lexbuf errHandler + let parenTokensBalance t1 t2 = + match t1, t2 with + | (LPAREN, RPAREN) + | (LPAREN, RPAREN_IS_HERE) + | (LBRACE, RBRACE) + | (LBRACE, RBRACE_IS_HERE) + | (SIG, END) + | (STRUCT, END) + | (LBRACK_BAR, BAR_RBRACK) + | (LBRACK, RBRACK) + | (LBRACK_LESS, GREATER_RBRACK) + | (BEGIN, END) -> true + | (LQUOTE q1, RQUOTE q2) -> q1 = q2 + | _ -> false + let rec matchBraces stack = + match lexfun lexbuf, stack with + | tok2, ((tok1, m1) :: stack') when parenTokensBalance tok1 tok2 -> + matchingBraces.Add(m1, lexbuf.LexemeRange) + matchBraces stack' + | ((LPAREN | LBRACE | LBRACK | LBRACK_BAR | LQUOTE _ | LBRACK_LESS) as tok), _ -> + matchBraces ((tok, lexbuf.LexemeRange) :: stack) + | (EOF _ | LEX_FAILURE _), _ -> () + | _ -> matchBraces stack + matchBraces []) + matchingBraces.ToArray() + + let parseFile(sourceText: ISourceText, fileName, options: FSharpParsingOptions, userOpName: string, suggestNamesForErrors: bool) = + Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "parseFile", fileName) + let errHandler = new ErrorHandler(true, fileName, options.ErrorSeverityOptions, sourceText, suggestNamesForErrors) + use unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _oldLogger -> errHandler.ErrorLogger) + use unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse + + let parseResult = + Lexhelp.usingLexbufForParsing(createLexbuf sourceText, fileName) (fun lexbuf -> + let lexfun = createLexerFunction fileName options lexbuf errHandler + let isLastCompiland = + fileName.Equals(options.LastFileName, StringComparison.CurrentCultureIgnoreCase) || + CompileOps.IsScript(fileName) + let isExe = options.IsExe + try Some (ParseInput(lexfun, errHandler.ErrorLogger, lexbuf, None, fileName, (isLastCompiland, isExe))) + with e -> + errHandler.ErrorLogger.StopProcessingRecovery e Range.range0 // don't re-raise any exceptions, we must return None. + None) + errHandler.CollectedDiagnostics, parseResult, errHandler.AnyErrors + + + let ApplyLoadClosure(tcConfig, parsedMainInput, mainInputFileName, loadClosure: LoadClosure option, tcImports: TcImports, backgroundDiagnostics) = + + // If additional references were brought in by the preprocessor then we need to process them + match loadClosure with + | Some loadClosure -> + // Play unresolved references for this file. + tcImports.ReportUnresolvedAssemblyReferences(loadClosure.UnresolvedReferences) + + // If there was a loadClosure, replay the errors and warnings from resolution, excluding parsing + loadClosure.LoadClosureRootFileDiagnostics |> List.iter diagnosticSink + + let fileOfBackgroundError err = (match GetRangeOfDiagnostic (fst err) with Some m-> m.FileName | None -> null) + let sameFile file hashLoadInFile = + (0 = String.Compare(hashLoadInFile, file, StringComparison.OrdinalIgnoreCase)) + + // walk the list of #loads and keep the ones for this file. + let hashLoadsInFile = + loadClosure.SourceFiles + |> List.filter(fun (_,ms) -> ms<>[]) // #loaded file, ranges of #load + + let hashLoadBackgroundDiagnostics, otherBackgroundDiagnostics = + backgroundDiagnostics + |> Array.partition (fun backgroundError -> + hashLoadsInFile + |> List.exists (fst >> sameFile (fileOfBackgroundError backgroundError))) + + // Create single errors for the #load-ed files. + // Group errors and warnings by file name. + let hashLoadBackgroundDiagnosticsGroupedByFileName = + hashLoadBackgroundDiagnostics + |> Array.map(fun err -> fileOfBackgroundError err,err) + |> Array.groupBy fst // fileWithErrors, error list + + // Join the sets and report errors. + // It is by-design that these messages are only present in the language service. A true build would report the errors at their + // spots in the individual source files. + for (fileOfHashLoad, rangesOfHashLoad) in hashLoadsInFile do + for (file, errorGroupedByFileName) in hashLoadBackgroundDiagnosticsGroupedByFileName do + if sameFile file fileOfHashLoad then + for rangeOfHashLoad in rangesOfHashLoad do // Handle the case of two #loads of the same file + let diagnostics = errorGroupedByFileName |> Array.map(fun (_,(pe,f)) -> pe.Exception,f) // Strip the build phase here. It will be replaced, in total, with TypeCheck + let errors = [ for (err,sev) in diagnostics do if sev = FSharpErrorSeverity.Error then yield err ] + let warnings = [ for (err,sev) in diagnostics do if sev = FSharpErrorSeverity.Warning then yield err ] + + let message = HashLoadedSourceHasIssues(warnings,errors,rangeOfHashLoad) + if errors=[] then warning(message) + else errorR(message) + + // Replay other background errors. + for (phasedError,sev) in otherBackgroundDiagnostics do + if sev = FSharpErrorSeverity.Warning then + warning phasedError.Exception + else errorR phasedError.Exception + + | None -> + // For non-scripts, check for disallow #r and #load. + ApplyMetaCommandsFromInputToTcConfig (tcConfig, parsedMainInput,Path.GetDirectoryName mainInputFileName) |> ignore + + // Type check a single file against an initial context, gleaning both errors and intellisense information. + let CheckOneFile + (parseResults: FSharpParseFileResults, + sourceText: ISourceText, + mainInputFileName: string, + projectFileName: string, + tcConfig: TcConfig, + tcGlobals: TcGlobals, + tcImports: TcImports, + tcState: TcState, + moduleNamesDict: ModuleNamesDict, + loadClosure: LoadClosure option, + // These are the errors and warnings seen by the background compiler for the entire antecedent + backgroundDiagnostics: (PhasedDiagnostic * FSharpErrorSeverity)[], + reactorOps: IReactorOperations, + // Used by 'FSharpDeclarationListInfo' to check the IncrementalBuilder is still alive. + textSnapshotInfo : obj option, + userOpName: string, + suggestNamesForErrors: bool) = async { + + use _logBlock = Logger.LogBlock LogCompilerFunctionId.Service_CheckOneFile + + match parseResults.ParseTree with + // When processing the following cases, we don't need to type-check + | None -> return [||], Result.Error() + + // Run the type checker... + | Some parsedMainInput -> + + // Initialize the error handler + let errHandler = new ErrorHandler(true, mainInputFileName, tcConfig.errorSeverityOptions, sourceText, suggestNamesForErrors) + + use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _oldLogger -> errHandler.ErrorLogger) + use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.TypeCheck + + // Apply nowarns to tcConfig (may generate errors, so ensure errorLogger is installed) + let tcConfig = ApplyNoWarnsToTcConfig (tcConfig, parsedMainInput,Path.GetDirectoryName mainInputFileName) + + // update the error handler with the modified tcConfig + errHandler.ErrorSeverityOptions <- tcConfig.errorSeverityOptions + + // Play background errors and warnings for this file. + for (err,sev) in backgroundDiagnostics do + diagnosticSink (err, (sev = FSharpErrorSeverity.Error)) + + // If additional references were brought in by the preprocessor then we need to process them + ApplyLoadClosure(tcConfig, parsedMainInput, mainInputFileName, loadClosure, tcImports, backgroundDiagnostics) + + // A problem arises with nice name generation, which really should only + // be done in the backend, but is also done in the typechecker for better or worse. + // If we don't do this the NNG accumulates data and we get a memory leak. + tcState.NiceNameGenerator.Reset() + + // Typecheck the real input. + let sink = TcResultsSinkImpl(tcGlobals, sourceText = sourceText) + + let! ct = Async.CancellationToken + + let! resOpt = + async { + try + let checkForErrors() = (parseResults.ParseHadErrors || errHandler.ErrorCount > 0) + + let parsedMainInput, _moduleNamesDict = DeduplicateParsedInputModuleName moduleNamesDict parsedMainInput + + // Typecheck is potentially a long running operation. We chop it up here with an Eventually continuation and, at each slice, give a chance + // for the client to claim the result as obsolete and have the typecheck abort. + + let! result = + TypeCheckOneInputAndFinishEventually(checkForErrors, tcConfig, tcImports, tcGlobals, None, TcResultsSink.WithSink sink, tcState, parsedMainInput) + |> Eventually.repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled maxTimeShareMilliseconds ct (fun ctok f -> f ctok) + |> Eventually.forceAsync + (fun work -> + reactorOps.EnqueueAndAwaitOpAsync(userOpName, "CheckOneFile.Fragment", mainInputFileName, + fun ctok -> + // This work is not cancellable + let res = + // Reinstall the compilation globals each time we start or restart + use unwind = new CompilationGlobalsScope (errHandler.ErrorLogger, BuildPhase.TypeCheck) + work ctok + cancellable.Return(res) + )) + + return result + with e -> + errorR e + return Some((tcState.TcEnvFromSignatures, EmptyTopAttrs, [], [NewEmptyModuleOrNamespaceType Namespace]), tcState) + } + + let errors = errHandler.CollectedDiagnostics + + let res = + match resOpt with + | Some ((tcEnvAtEnd, _, implFiles, ccuSigsForFiles), tcState) -> + TypeCheckInfo(tcConfig, tcGlobals, + List.head ccuSigsForFiles, + tcState.Ccu, + tcImports, + tcEnvAtEnd.AccessRights, + projectFileName, + mainInputFileName, + sink.GetResolutions(), + sink.GetSymbolUses(), + tcEnvAtEnd.NameEnv, + loadClosure, + reactorOps, + textSnapshotInfo, + List.tryHead implFiles, + sink.GetOpenDeclarations()) + |> Result.Ok + | None -> + Result.Error() + return errors, res + } + + +[] +type FSharpProjectContext(thisCcu: CcuThunk, assemblies: FSharpAssembly list, ad: AccessorDomain) = + + /// Get the assemblies referenced + member __.GetReferencedAssemblies() = assemblies + + member __.AccessibilityRights = FSharpAccessibilityRights(thisCcu, ad) + + +[] +/// A live object of this type keeps the background corresponding background builder (and type providers) alive (through reference-counting). +// +// There is an important property of all the objects returned by the methods of this type: they do not require +// the corresponding background builder to be alive. That is, they are simply plain-old-data through pre-formatting of all result text. +type FSharpCheckFileResults + (filename: string, + errors: FSharpErrorInfo[], + scopeOptX: TypeCheckInfo option, + dependencyFiles: string[], + builderX: IncrementalBuilder option, + reactorOpsX:IReactorOperations, + keepAssemblyContents: bool) = + + // This may be None initially + let mutable details = match scopeOptX with None -> None | Some scopeX -> Some (scopeX, builderX, reactorOpsX) + + // Run an operation that needs to access a builder and be run in the reactor thread + let reactorOp userOpName opName dflt f = + async { + match details with + | None -> + return dflt + | Some (scope, _, reactor) -> + // Increment the usage count to ensure the builder doesn't get released while running operations asynchronously. + let! res = reactor.EnqueueAndAwaitOpAsync(userOpName, opName, filename, fun ctok -> f ctok scope |> cancellable.Return) + return res + } + + // Run an operation that can be called from any thread + let threadSafeOp dflt f = + match details with + | None -> dflt() + | Some (scope, _builderOpt, _ops) -> f scope + + member __.Errors = errors + + member __.HasFullTypeCheckInfo = details.IsSome + + member info.TryGetCurrentTcImports () = + match builderX with + | Some builder -> builder.TryGetCurrentTcImports () + | _ -> None + + /// Intellisense autocompletions + member __.GetDeclarationListInfo(parseResultsOpt, line, lineStr, partialName, ?getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + let getAllEntities = defaultArg getAllEntities (fun() -> []) + let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) + reactorOp userOpName "GetDeclarations" FSharpDeclarationListInfo.Empty (fun ctok scope -> + scope.GetDeclarations(ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) + + member __.GetDeclarationListSymbols(parseResultsOpt, line, lineStr, partialName, ?getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) + let getAllEntities = defaultArg getAllEntities (fun() -> []) + reactorOp userOpName "GetDeclarationListSymbols" List.empty (fun ctok scope -> + scope.GetDeclarationListSymbols(ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) + + /// Resolve the names at the given location to give a data tip + member __.GetStructuredToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + let dflt = FSharpToolTipText [] + match tokenTagToTokenId tokenTag with + | TOKEN_IDENT -> + reactorOp userOpName "GetStructuredToolTipText" dflt (fun ctok scope -> + scope.GetStructuredToolTipText(ctok, line, lineStr, colAtEndOfNames, names)) + | TOKEN_STRING | TOKEN_STRING_TEXT -> + reactorOp userOpName "GetReferenceResolutionToolTipText" dflt (fun ctok scope -> + scope.GetReferenceResolutionStructuredToolTipText(ctok, line, colAtEndOfNames) ) + | _ -> + async.Return dflt + + member info.GetToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, userOpName) = + info.GetStructuredToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, ?userOpName=userOpName) + |> Tooltips.Map Tooltips.ToFSharpToolTipText + + member __.GetF1Keyword (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "GetF1Keyword" None (fun ctok scope -> + scope.GetF1Keyword (ctok, line, lineStr, colAtEndOfNames, names)) + + // Resolve the names at the given location to a set of methods + member __.GetMethods(line, colAtEndOfNames, lineStr, names, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + let dflt = FSharpMethodGroup("",[| |]) + reactorOp userOpName "GetMethods" dflt (fun ctok scope -> + scope.GetMethods (ctok, line, lineStr, colAtEndOfNames, names)) + + member __.GetDeclarationLocation (line, colAtEndOfNames, lineStr, names, ?preferFlag, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + let dflt = FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "") + reactorOp userOpName "GetDeclarationLocation" dflt (fun ctok scope -> + scope.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag)) + + member __.GetSymbolUseAtLocation (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "GetSymbolUseAtLocation" None (fun ctok scope -> + scope.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) + |> Option.map (fun (sym,denv,m) -> FSharpSymbolUse(scope.TcGlobals,denv,sym,ItemOccurence.Use,m))) + + member __.GetMethodsAsSymbols (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "GetMethodsAsSymbols" None (fun ctok scope -> + scope.GetMethodsAsSymbols (ctok, line, lineStr, colAtEndOfNames, names) + |> Option.map (fun (symbols,denv,m) -> + symbols |> List.map (fun sym -> FSharpSymbolUse(scope.TcGlobals,denv,sym,ItemOccurence.Use,m)))) + + member __.GetSymbolAtLocation (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "GetSymbolAtLocation" None (fun ctok scope -> + scope.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) + |> Option.map (fun (sym,_,_) -> sym)) + + member info.GetFormatSpecifierLocations() = + info.GetFormatSpecifierLocationsAndArity() |> Array.map fst + + member __.GetFormatSpecifierLocationsAndArity() = + threadSafeOp + (fun () -> [| |]) + (fun scope -> + // This operation is not asynchronous - GetFormatSpecifierLocationsAndArity can be run on the calling thread + scope.GetFormatSpecifierLocationsAndArity()) + + member __.GetSemanticClassification(range: range option) = + threadSafeOp + (fun () -> [| |]) + (fun scope -> + // This operation is not asynchronous - GetSemanticClassification can be run on the calling thread + scope.GetSemanticClassification(range)) + + member __.PartialAssemblySignature = + threadSafeOp + (fun () -> failwith "not available") + (fun scope -> + // This operation is not asynchronous - PartialAssemblySignature can be run on the calling thread + scope.PartialAssemblySignatureForFile) + + member __.ProjectContext = + threadSafeOp + (fun () -> failwith "not available") + (fun scope -> + // This operation is not asynchronous - GetReferencedAssemblies can be run on the calling thread + FSharpProjectContext(scope.ThisCcu, scope.GetReferencedAssemblies(), scope.AccessRights)) + + member __.DependencyFiles = dependencyFiles + + member __.GetAllUsesOfAllSymbolsInFile() = + threadSafeOp + (fun () -> [| |]) + (fun scope -> + let cenv = scope.SymbolEnv + [| for symbolUseChunk in scope.ScopeSymbolUses.AllUsesOfSymbols do + for symbolUse in symbolUseChunk do + if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then + let symbol = FSharpSymbol.Create(cenv, symbolUse.Item) + yield FSharpSymbolUse(scope.TcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |]) + |> async.Return + + member __.GetUsesOfSymbolInFile(symbol:FSharpSymbol) = + threadSafeOp + (fun () -> [| |]) + (fun scope -> + [| for symbolUse in scope.ScopeSymbolUses.GetUsesOfSymbol(symbol.Item) |> Seq.distinctBy (fun symbolUse -> symbolUse.ItemOccurence, symbolUse.Range) do + if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then + yield FSharpSymbolUse(scope.TcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |]) + |> async.Return + + member __.GetVisibleNamespacesAndModulesAtPoint(pos: pos) = + threadSafeOp + (fun () -> [| |]) + (fun scope -> scope.GetVisibleNamespacesAndModulesAtPosition(pos) |> List.toArray) + |> async.Return + + member __.IsRelativeNameResolvable(pos: pos, plid: string list, item: Item, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "IsRelativeNameResolvable" true (fun ctok scope -> + RequireCompilationThread ctok + scope.IsRelativeNameResolvable(pos, plid, item)) + + member __.IsRelativeNameResolvableFromSymbol(pos: pos, plid: string list, symbol: FSharpSymbol, ?userOpName: string) = + let userOpName = defaultArg userOpName "Unknown" + reactorOp userOpName "IsRelativeNameResolvableFromSymbol" true (fun ctok scope -> + RequireCompilationThread ctok + scope.IsRelativeNameResolvableFromSymbol(pos, plid, symbol)) + + member __.GetDisplayContextForPos(pos: pos) : Async = + let userOpName = "CodeLens" + reactorOp userOpName "GetDisplayContextAtPos" None (fun ctok scope -> + DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok + let (nenv, _), _ = scope.GetBestDisplayEnvForPos pos + Some(FSharpDisplayContext(fun _ -> nenv.DisplayEnv))) + + member __.ImplementationFile = + if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" + scopeOptX + |> Option.map (fun scope -> + let cenv = SymbolEnv(scope.TcGlobals, scope.ThisCcu, Some scope.CcuSigForFile, scope.TcImports) + scope.ImplementationFile |> Option.map (fun implFile -> FSharpImplementationFileContents(cenv, implFile))) + |> Option.defaultValue None + + member __.OpenDeclarations = + scopeOptX + |> Option.map (fun scope -> + let cenv = scope.SymbolEnv + scope.OpenDeclarations |> Array.map (fun x -> FSharpOpenDeclaration(x.LongId, x.Range, (x.Modules |> List.map (fun x -> FSharpEntity(cenv, x))), x.AppliedScope, x.IsOwnNamespace))) + |> Option.defaultValue [| |] + + override __.ToString() = "FSharpCheckFileResults(" + filename + ")" + + static member MakeEmpty(filename: string, creationErrors: FSharpErrorInfo[], reactorOps, keepAssemblyContents) = + FSharpCheckFileResults (filename, creationErrors, None, [| |], None, reactorOps, keepAssemblyContents) + + static member JoinErrors(isIncompleteTypeCheckEnvironment, + creationErrors: FSharpErrorInfo[], + parseErrors: FSharpErrorInfo[], + tcErrors: FSharpErrorInfo[]) = + [| yield! creationErrors + yield! parseErrors + if isIncompleteTypeCheckEnvironment then + yield! Seq.truncate maxTypeCheckErrorsOutOfProjectContext tcErrors + else + yield! tcErrors |] + + static member Make + (mainInputFileName: string, + projectFileName, + tcConfig, tcGlobals, + isIncompleteTypeCheckEnvironment: bool, + builder: IncrementalBuilder, + dependencyFiles, + creationErrors: FSharpErrorInfo[], + parseErrors: FSharpErrorInfo[], + tcErrors: FSharpErrorInfo[], + reactorOps, + keepAssemblyContents, + ccuSigForFile, + thisCcu, tcImports, tcAccessRights, + sResolutions, sSymbolUses, + sFallback, loadClosure, + implFileOpt, + openDeclarations) = + + let tcFileInfo = + TypeCheckInfo(tcConfig, tcGlobals, ccuSigForFile, thisCcu, tcImports, tcAccessRights, + projectFileName, mainInputFileName, sResolutions, sSymbolUses, + sFallback, loadClosure, reactorOps, + None, implFileOpt, openDeclarations) + + let errors = FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors) + FSharpCheckFileResults (mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, reactorOps, keepAssemblyContents) + + static member CheckOneFile + (parseResults: FSharpParseFileResults, + sourceText: ISourceText, + mainInputFileName: string, + projectFileName: string, + tcConfig: TcConfig, + tcGlobals: TcGlobals, + tcImports: TcImports, + tcState: TcState, + moduleNamesDict: ModuleNamesDict, + loadClosure: LoadClosure option, + backgroundDiagnostics: (PhasedDiagnostic * FSharpErrorSeverity)[], + reactorOps: IReactorOperations, + textSnapshotInfo : obj option, + userOpName: string, + isIncompleteTypeCheckEnvironment: bool, + builder: IncrementalBuilder, + dependencyFiles: string[], + creationErrors: FSharpErrorInfo[], + parseErrors: FSharpErrorInfo[], + keepAssemblyContents: bool, + suggestNamesForErrors: bool) = + async { + let! tcErrors, tcFileInfo = + ParseAndCheckFile.CheckOneFile + (parseResults, sourceText, mainInputFileName, projectFileName, tcConfig, tcGlobals, tcImports, + tcState, moduleNamesDict, loadClosure, backgroundDiagnostics, reactorOps, + textSnapshotInfo, userOpName, suggestNamesForErrors) + match tcFileInfo with + | Result.Error () -> + return FSharpCheckFileAnswer.Aborted + | Result.Ok tcFileInfo -> + let errors = FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors) + let results = FSharpCheckFileResults (mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, reactorOps, keepAssemblyContents) + return FSharpCheckFileAnswer.Succeeded(results) + } + +and [] FSharpCheckFileAnswer = + | Aborted + | Succeeded of FSharpCheckFileResults + + +[] +// 'details' is an option because the creation of the tcGlobals etc. for the project may have failed. +type FSharpCheckProjectResults + (projectFileName:string, + tcConfigOption: TcConfig option, + keepAssemblyContents: bool, + errors: FSharpErrorInfo[], + details:(TcGlobals * TcImports * CcuThunk * ModuleOrNamespaceType * TcSymbolUses list * TopAttribs option * CompileOps.IRawFSharpAssemblyData option * ILAssemblyRef * AccessorDomain * TypedImplFile list option * string[]) option) = + + let getDetails() = + match details with + | None -> invalidOp ("The project has no results due to critical errors in the project options. Check the HasCriticalErrors before accessing the detailed results. Errors: " + String.concat "\n" [ for e in errors -> e.Message ]) + | Some d -> d + + let getTcConfig() = + match tcConfigOption with + | None -> invalidOp ("The project has no results due to critical errors in the project options. Check the HasCriticalErrors before accessing the detailed results. Errors: " + String.concat "\n" [ for e in errors -> e.Message ]) + | Some d -> d + + member __.Errors = errors + + member __.HasCriticalErrors = details.IsNone + + member __.AssemblySignature = + let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + FSharpAssemblySignature(tcGlobals, thisCcu, ccuSig, tcImports, topAttribs, ccuSig) + + member __.TypedImplementionFiles = + if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" + let (tcGlobals, tcImports, thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() + let mimpls = + match tcAssemblyExpr with + | None -> [] + | Some mimpls -> mimpls + tcGlobals, thisCcu, tcImports, mimpls + + member info.AssemblyContents = + if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" + let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() + let mimpls = + match tcAssemblyExpr with + | None -> [] + | Some mimpls -> mimpls + FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls) + + member __.GetOptimizedAssemblyContents() = + if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" + let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() + let mimpls = + match tcAssemblyExpr with + | None -> [] + | Some mimpls -> mimpls + let outfile = "" // only used if tcConfig.writeTermsToFiles is true + let importMap = tcImports.GetImportMap() + let optEnv0 = GetInitialOptimizationEnv (tcImports, tcGlobals) + let tcConfig = getTcConfig() + let optimizedImpls, _optimizationData, _ = ApplyAllOptimizations (tcConfig, tcGlobals, (LightweightTcValForUsingInBuildMethodCall tcGlobals), outfile, importMap, false, optEnv0, thisCcu, mimpls) + let mimpls = + match optimizedImpls with + | TypedAssemblyAfterOptimization files -> + files |> List.map fst + + FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls) + + // Not, this does not have to be a SyncOp, it can be called from any thread + member __.GetUsesOfSymbol(symbol:FSharpSymbol) = + let (tcGlobals, _tcImports, _thisCcu, _ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + + tcSymbolUses + |> Seq.collect (fun r -> r.GetUsesOfSymbol symbol.Item) + |> Seq.distinctBy (fun symbolUse -> symbolUse.ItemOccurence, symbolUse.Range) + |> Seq.filter (fun symbolUse -> symbolUse.ItemOccurence <> ItemOccurence.RelatedText) + |> Seq.map (fun symbolUse -> FSharpSymbolUse(tcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range)) + |> Seq.toArray + |> async.Return + + // Not, this does not have to be a SyncOp, it can be called from any thread + member __.GetAllUsesOfAllSymbols() = + let (tcGlobals, tcImports, thisCcu, ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + let cenv = SymbolEnv(tcGlobals, thisCcu, Some ccuSig, tcImports) + + [| for r in tcSymbolUses do + for symbolUseChunk in r.AllUsesOfSymbols do + for symbolUse in symbolUseChunk do + if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then + let symbol = FSharpSymbol.Create(cenv, symbolUse.Item) + yield FSharpSymbolUse(tcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |] + |> async.Return + + member __.ProjectContext = + let (tcGlobals, tcImports, thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + let assemblies = + [ for x in tcImports.GetImportedAssemblies() do + yield FSharpAssembly(tcGlobals, tcImports, x.FSharpViewOfMetadata) ] + FSharpProjectContext(thisCcu, assemblies, ad) + + member __.RawFSharpAssemblyData = + let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + tcAssemblyData + + member __.DependencyFiles = + let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, dependencyFiles) = getDetails() + dependencyFiles + + member __.AssemblyFullName = + let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() + ilAssemRef.QualifiedName + + override __.ToString() = "FSharpCheckProjectResults(" + projectFileName + ")" + +type FsiInteractiveChecker(legacyReferenceResolver, + reactorOps: IReactorOperations, + tcConfig: TcConfig, + tcGlobals, + tcImports, + tcState) = + + let keepAssemblyContents = false + + member __.ParseAndCheckInteraction (ctok, sourceText: ISourceText, ?userOpName: string) = + async { + let userOpName = defaultArg userOpName "Unknown" + let filename = Path.Combine(tcConfig.implicitIncludeDir, "stdin.fsx") + let suggestNamesForErrors = true // Will always be true, this is just for readability + // Note: projectSourceFiles is only used to compute isLastCompiland, and is ignored if Build.IsScript(mainInputFileName) is true (which it is in this case). + let parsingOptions = FSharpParsingOptions.FromTcConfig(tcConfig, [| filename |], true) + let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile (sourceText, filename, parsingOptions, userOpName, suggestNamesForErrors) + let dependencyFiles = [| |] // interactions have no dependencies + let parseResults = FSharpParseFileResults(parseErrors, parseTreeOpt, parseHadErrors = anyErrors, dependencyFiles = dependencyFiles) + + let backgroundDiagnostics = [| |] + let reduceMemoryUsage = ReduceMemoryFlag.Yes + let assumeDotNetFramework = true + + let applyCompilerOptions tcConfigB = + let fsiCompilerOptions = CompileOptions.GetCoreFsiCompilerOptions tcConfigB + CompileOptions.ParseCompilerOptions (ignore, fsiCompilerOptions, [ ]) + + let loadClosure = + LoadClosure.ComputeClosureOfScriptText(ctok, legacyReferenceResolver, defaultFSharpBinariesDir, + filename, sourceText, CodeContext.Editing, + tcConfig.useSimpleResolution, tcConfig.useFsiAuxLib, + tcConfig.useSdkRefs, new Lexhelp.LexResourceManager(), + applyCompilerOptions, assumeDotNetFramework, + tryGetMetadataSnapshot=(fun _ -> None), reduceMemoryUsage=reduceMemoryUsage) + + let! tcErrors, tcFileInfo = + ParseAndCheckFile.CheckOneFile + (parseResults, sourceText, filename, "project", + tcConfig, tcGlobals, tcImports, tcState, + Map.empty, Some loadClosure, backgroundDiagnostics, + reactorOps, None, userOpName, suggestNamesForErrors) + + return + match tcFileInfo with + | Result.Ok tcFileInfo -> + let errors = [| yield! parseErrors; yield! tcErrors |] + let typeCheckResults = FSharpCheckFileResults (filename, errors, Some tcFileInfo, dependencyFiles, None, reactorOps, false) + let projectResults = + FSharpCheckProjectResults (filename, Some tcConfig, + keepAssemblyContents, errors, + Some(tcGlobals, tcImports, tcFileInfo.ThisCcu, tcFileInfo.CcuSigForFile, + [tcFileInfo.ScopeSymbolUses], None, None, mkSimpleAssemblyRef "stdin", + tcState.TcEnvFromImpls.AccessRights, None, dependencyFiles)) + + parseResults, typeCheckResults, projectResults + + | Result.Error () -> + failwith "unexpected aborted" + } + diff --git a/src/fsharp/service/FSharpCheckerResults.fsi b/src/fsharp/service/FSharpCheckerResults.fsi new file mode 100644 index 00000000000..4f7062d9ce2 --- /dev/null +++ b/src/fsharp/service/FSharpCheckerResults.fsi @@ -0,0 +1,421 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.SourceCodeServices + +open FSharp.Compiler +open FSharp.Compiler.AbstractIL.IL +open FSharp.Compiler.AbstractIL.Internal.Library +open FSharp.Compiler.AccessibilityLogic +open FSharp.Compiler.Ast +open FSharp.Compiler.CompileOps +open FSharp.Compiler.ErrorLogger +open FSharp.Compiler.NameResolution +open FSharp.Compiler.Range +open FSharp.Compiler.Tast +open FSharp.Compiler.Tastops +open FSharp.Compiler.TcGlobals +open FSharp.Compiler.Text +open FSharp.Compiler.TypeChecker + +/// Represents the reason why the GetDeclarationLocation operation failed. +[] +type public FSharpFindDeclFailureReason = + + /// Generic reason: no particular information about error apart from a message + | Unknown of message: string + + /// Source code file is not available + | NoSourceCode + + /// Trying to find declaration of ProvidedType without TypeProviderDefinitionLocationAttribute + | ProvidedType of string + + /// Trying to find declaration of ProvidedMember without TypeProviderDefinitionLocationAttribute + | ProvidedMember of string + +/// Represents the result of the GetDeclarationLocation operation. +[] +type public FSharpFindDeclResult = + + /// Indicates a declaration location was not found, with an additional reason + | DeclNotFound of FSharpFindDeclFailureReason + + /// Indicates a declaration location was found + | DeclFound of range + + /// Indicates an external declaration was found + | ExternalDecl of assembly : string * externalSym : ExternalSymbol + +/// Represents the checking context implied by the ProjectOptions +[] +type public FSharpProjectContext = + + /// Get the resolution and full contents of the assemblies referenced by the project options + member GetReferencedAssemblies : unit -> FSharpAssembly list + + /// Get the accessibility rights for this project context w.r.t. InternalsVisibleTo attributes granting access to other assemblies + member AccessibilityRights : FSharpAccessibilityRights + +[] +type public SemanticClassificationType = + | ReferenceType + | ValueType + | UnionCase + | Function + | Property + | MutableVar + | Module + | Printf + | ComputationExpression + | IntrinsicFunction + | Enumeration + | Interface + | TypeArgument + | Operator + | Disposable + +/// Options used to determine active --define conditionals and other options relevant to parsing files in a project +type public FSharpParsingOptions = + { + SourceFiles: string[] + ConditionalCompilationDefines: string list + ErrorSeverityOptions: FSharpErrorSeverityOptions + IsInteractive: bool + LightSyntax: bool option + CompilingFsLib: bool + IsExe: bool + } + static member Default: FSharpParsingOptions + + static member internal FromTcConfig: tcConfig: TcConfig * sourceFiles: string[] * isInteractive: bool -> FSharpParsingOptions + + static member internal FromTcConfigBuidler: tcConfigB: TcConfigBuilder * sourceFiles: string[] * isInteractive: bool -> FSharpParsingOptions + +/// A handle to the results of CheckFileInProject. +[] +type public FSharpCheckFileResults = + /// The errors returned by parsing a source file. + member Errors : FSharpErrorInfo[] + + /// Get a view of the contents of the assembly up to and including the file just checked + member PartialAssemblySignature : FSharpAssemblySignature + + /// Get the resolution of the ProjectOptions + member ProjectContext : FSharpProjectContext + + /// Indicates whether type checking successfully occurred with some results returned. If false, indicates that + /// an unrecoverable error in earlier checking/parsing/resolution steps. + member HasFullTypeCheckInfo: bool + + /// Tries to get the current successful TcImports. This is only used in testing. Do not use it for other stuff. + member internal TryGetCurrentTcImports: unit -> TcImports option + + /// Indicates the set of files which must be watched to accurately track changes that affect these results, + /// Clients interested in reacting to updates to these files should watch these files and take actions as described + /// in the documentation for compiler service. + member DependencyFiles : string[] + + /// Get the items for a declaration list + /// + /// + /// If this is present, it is used to filter declarations based on location in the + /// parse tree, specifically at 'open' declarations, 'inherit' of class or interface + /// 'record field' locations and r.h.s. of 'range' operator a..b + /// + /// The line number where the completion is happening + /// + /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. + /// + /// + /// The text of the line where the completion is happening. This is only used to make a couple + /// of adhoc corrections to completion accuracy (e.g. checking for "..") + /// + /// + /// Function that returns all entities from current and referenced assemblies. + /// + /// + /// If text has been used from a captured name resolution from the typecheck, then + /// callback to the client to check if the text has changed. If it has, then give up + /// and assume that we're going to repeat the operation later on. + /// + /// An optional string used for tracing compiler operations associated with this request. + member GetDeclarationListInfo : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * ?getAllEntities: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async + + /// Get the items for a declaration list in FSharpSymbol format + /// + /// + /// If this is present, it is used to filter declarations based on location in the + /// parse tree, specifically at 'open' declarations, 'inherit' of class or interface + /// 'record field' locations and r.h.s. of 'range' operator a..b + /// + /// The line number where the completion is happening + /// + /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. + /// + /// + /// The text of the line where the completion is happening. This is only used to make a couple + /// of adhoc corrections to completion accuracy (e.g. checking for "..") + /// + /// + /// Function that returns all entities from current and referenced assemblies. + /// + /// + /// If text has been used from a captured name resolution from the typecheck, then + /// callback to the client to check if the text has changed. If it has, then give up + /// and assume that we're going to repeat the operation later on. + /// + /// An optional string used for tracing compiler operations associated with this request. + member GetDeclarationListSymbols : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * ?getAllEntities: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async + + /// Compute a formatted tooltip for the given location + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// Used to discriminate between 'identifiers', 'strings' and others. For strings, an attempt is made to give a tooltip for a #r "..." location. Use a value from FSharpTokenInfo.Tag, or FSharpTokenTag.Identifier, unless you have other information available. + /// An optional string used for tracing compiler operations associated with this request. + member GetStructuredToolTipText : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int * ?userOpName: string -> Async + + /// Compute a formatted tooltip for the given location + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// Used to discriminate between 'identifiers', 'strings' and others. For strings, an attempt is made to give a tooltip for a #r "..." location. Use a value from FSharpTokenInfo.Tag, or FSharpTokenTag.Identifier, unless you have other information available. + /// An optional string used for tracing compiler operations associated with this request. + member GetToolTipText : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int * ?userOpName: string -> Async + + /// Compute the Visual Studio F1-help key identifier for the given location, based on name resolution results + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// An optional string used for tracing compiler operations associated with this request. + member GetF1Keyword : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async + + + /// Compute a set of method overloads to show in a dialog relevant to the given code location. + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// An optional string used for tracing compiler operations associated with this request. + member GetMethods : line:int * colAtEndOfNames:int * lineText:string * names:string list option * ?userOpName: string -> Async + + /// Compute a set of method overloads to show in a dialog relevant to the given code location. The resulting method overloads are returned as symbols. + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// An optional string used for tracing compiler operations associated with this request. + member GetMethodsAsSymbols : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async + + /// Resolve the names at the given location to the declaration location of the corresponding construct. + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// If not given, then get the location of the symbol. If false, then prefer the location of the corresponding symbol in the implementation of the file (rather than the signature if present). If true, prefer the location of the corresponding symbol in the signature of the file (rather than the implementation). + /// An optional string used for tracing compiler operations associated with this request. + member GetDeclarationLocation : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?preferFlag:bool * ?userOpName: string -> Async + + /// Resolve the names at the given location to a use of symbol. + /// + /// The line number where the information is being requested. + /// The column number at the end of the identifiers where the information is being requested. + /// The text of the line where the information is being requested. + /// The identifiers at the location where the information is being requested. + /// An optional string used for tracing compiler operations associated with this request. + member GetSymbolUseAtLocation : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async + + /// Get any extra colorization info that is available after the typecheck + member GetSemanticClassification : range option -> (range * SemanticClassificationType)[] + + /// Get the locations of format specifiers + [] + member GetFormatSpecifierLocations : unit -> range[] + + /// Get the locations of and number of arguments associated with format specifiers + member GetFormatSpecifierLocationsAndArity : unit -> (range*int)[] + + /// Get all textual usages of all symbols throughout the file + member GetAllUsesOfAllSymbolsInFile : unit -> Async + + /// Get the textual usages that resolved to the given symbol throughout the file + member GetUsesOfSymbolInFile : symbol:FSharpSymbol -> Async + + member internal GetVisibleNamespacesAndModulesAtPoint : pos -> Async + + /// Find the most precise display environment for the given line and column. + member GetDisplayContextForPos : pos : pos -> Async + + /// Determines if a long ident is resolvable at a specific point. + /// An optional string used for tracing compiler operations associated with this request. + member internal IsRelativeNameResolvable: cursorPos : pos * plid : string list * item: Item * ?userOpName: string -> Async + + /// Determines if a long ident is resolvable at a specific point. + /// An optional string used for tracing compiler operations associated with this request. + member IsRelativeNameResolvableFromSymbol: cursorPos : pos * plid : string list * symbol: FSharpSymbol * ?userOpName: string -> Async + + /// Represents complete typechecked implementation file, including its typechecked signatures if any. + member ImplementationFile: FSharpImplementationFileContents option + + /// Open declarations in the file, including auto open modules. + member OpenDeclarations: FSharpOpenDeclaration[] + + /// Internal constructor + static member internal MakeEmpty : + filename: string * + creationErrors: FSharpErrorInfo[] * + reactorOps: IReactorOperations * + keepAssemblyContents: bool + -> FSharpCheckFileResults + + /// Internal constructor + static member internal Make: + mainInputFileName: string * + projectFileName: string * + tcConfig: TcConfig * + tcGlobals: TcGlobals * + isIncompleteTypeCheckEnvironment: bool * + builder: IncrementalBuilder * + dependencyFiles: string[] * + creationErrors: FSharpErrorInfo[] * + parseErrors: FSharpErrorInfo[] * + tcErrors: FSharpErrorInfo[] * + reactorOps : IReactorOperations * + keepAssemblyContents: bool * + ccuSigForFile: ModuleOrNamespaceType * + thisCcu: CcuThunk * + tcImports: TcImports * + tcAccessRights: AccessorDomain * + sResolutions: TcResolutions * + sSymbolUses: TcSymbolUses * + sFallback: NameResolutionEnv * + loadClosure : LoadClosure option * + implFileOpt: TypedImplFile option * + openDeclarations: OpenDeclaration[] + -> FSharpCheckFileResults + + /// Internal constructor - check a file and collect errors + static member internal CheckOneFile: + parseResults: FSharpParseFileResults * + sourceText: ISourceText * + mainInputFileName: string * + projectFileName: string * + tcConfig: TcConfig * + tcGlobals: TcGlobals * + tcImports: TcImports * + tcState: TcState * + moduleNamesDict: ModuleNamesDict * + loadClosure: LoadClosure option * + backgroundDiagnostics: (PhasedDiagnostic * FSharpErrorSeverity)[] * + reactorOps: IReactorOperations * + textSnapshotInfo : obj option * + userOpName: string * + isIncompleteTypeCheckEnvironment: bool * + builder: IncrementalBuilder * + dependencyFiles: string[] * + creationErrors:FSharpErrorInfo[] * + parseErrors:FSharpErrorInfo[] * + keepAssemblyContents: bool * + suggestNamesForErrors: bool + -> Async + +/// The result of calling TypeCheckResult including the possibility of abort and background compiler not caught up. +and [] public FSharpCheckFileAnswer = + /// Aborted because cancellation caused an abandonment of the operation + | Aborted + + /// Success + | Succeeded of FSharpCheckFileResults + +/// A handle to the results of CheckFileInProject. +[] +type public FSharpCheckProjectResults = + + /// The errors returned by processing the project + member Errors: FSharpErrorInfo[] + + /// Get a view of the overall signature of the assembly. Only valid to use if HasCriticalErrors is false. + member AssemblySignature: FSharpAssemblySignature + + /// Get a view of the overall contents of the assembly. Only valid to use if HasCriticalErrors is false. + member AssemblyContents: FSharpAssemblyContents + + /// Get an optimized view of the overall contents of the assembly. Only valid to use if HasCriticalErrors is false. + member GetOptimizedAssemblyContents: unit -> FSharpAssemblyContents + + /// Get the resolution of the ProjectOptions + member ProjectContext: FSharpProjectContext + + /// Get the textual usages that resolved to the given symbol throughout the project + member GetUsesOfSymbol: symbol:FSharpSymbol -> Async + + /// Get all textual usages of all symbols throughout the project + member GetAllUsesOfAllSymbols: unit -> Async + + /// Indicates if critical errors existed in the project options + member HasCriticalErrors: bool + + /// Indicates the set of files which must be watched to accurately track changes that affect these results, + /// Clients interested in reacting to updates to these files should watch these files and take actions as described + /// in the documentation for compiler service. + member DependencyFiles: string[] + + member internal RawFSharpAssemblyData : IRawFSharpAssemblyData option + + // Internal constructor. + internal new : + projectFileName:string * + tcConfigOption: TcConfig option * + keepAssemblyContents: bool * + errors: FSharpErrorInfo[] * + details:(TcGlobals * TcImports * CcuThunk * ModuleOrNamespaceType * TcSymbolUses list * TopAttribs option * CompileOps.IRawFSharpAssemblyData option * ILAssemblyRef * AccessorDomain * TypedImplFile list option * string[]) option + -> FSharpCheckProjectResults + +module internal ParseAndCheckFile = + + val parseFile: + sourceText: ISourceText * + fileName: string * + options: FSharpParsingOptions * + userOpName: string * + suggestNamesForErrors: bool + -> FSharpErrorInfo[] * ParsedInput option * bool + + val matchBraces: + sourceText: ISourceText * + fileName: string * + options: FSharpParsingOptions * + userOpName: string * + suggestNamesForErrors: bool + -> (range * range)[] + +// An object to typecheck source in a given typechecking environment. +// Used internally to provide intellisense over F# Interactive. +type internal FsiInteractiveChecker = + internal new: + ReferenceResolver.Resolver * + ops: IReactorOperations * + tcConfig: TcConfig * + tcGlobals: TcGlobals * + tcImports: TcImports * + tcState: TcState + -> FsiInteractiveChecker + + member internal ParseAndCheckInteraction : + ctok: CompilationThreadToken * + sourceText:ISourceText * + ?userOpName: string + -> Async + +module internal FSharpCheckerResultsSettings = + val defaultFSharpBinariesDir: string + + val maxTimeShareMilliseconds : int64 \ No newline at end of file diff --git a/src/fsharp/service/ServiceAssemblyContent.fs b/src/fsharp/service/ServiceAssemblyContent.fs index 67a7cc7e8d0..8ceb381225b 100644 --- a/src/fsharp/service/ServiceAssemblyContent.fs +++ b/src/fsharp/service/ServiceAssemblyContent.fs @@ -529,7 +529,7 @@ module ParsedInput = let rec walkImplFileInput (ParsedImplFileInput (modules = moduleOrNamespaceList)) = List.iter walkSynModuleOrNamespace moduleOrNamespaceList - and walkSynModuleOrNamespace (SynModuleOrNamespace(_, _, _, decls, _, attrs, _, _)) = + and walkSynModuleOrNamespace (SynModuleOrNamespace(_, _, _, decls, _, Attributes attrs, _, _)) = List.iter walkAttribute attrs List.iter walkSynModuleDecl decls @@ -537,7 +537,7 @@ module ParsedInput = addLongIdentWithDots attr.TypeName walkExpr attr.ArgExpr - and walkTyparDecl (SynTyparDecl.TyparDecl (attrs, typar)) = + and walkTyparDecl (SynTyparDecl.TyparDecl (Attributes attrs, typar)) = List.iter walkAttribute attrs walkTypar typar @@ -564,7 +564,7 @@ module ParsedInput = | SynPat.Typed (pat, t, _) -> walkPat pat walkType t - | SynPat.Attrib (pat, attrs, _) -> + | SynPat.Attrib (pat, Attributes attrs, _) -> walkPat pat List.iter walkAttribute attrs | SynPat.Or (pat1, pat2, _) -> List.iter walkPat [pat1; pat2] @@ -582,7 +582,7 @@ module ParsedInput = and walkTypar (Typar (_, _, _)) = () - and walkBinding (SynBinding.Binding (_, _, _, _, attrs, _, _, pat, returnInfo, e, _, _)) = + and walkBinding (SynBinding.Binding (_, _, _, _, Attributes attrs, _, _, pat, returnInfo, e, _, _)) = List.iter walkAttribute attrs walkPat pat walkExpr e @@ -726,7 +726,7 @@ module ParsedInput = | SynMeasure.Anon _ -> () and walkSimplePat = function - | SynSimplePat.Attrib (pat, attrs, _) -> + | SynSimplePat.Attrib (pat, Attributes attrs, _) -> walkSimplePat pat List.iter walkAttribute attrs | SynSimplePat.Typed(pat, t, _) -> @@ -734,15 +734,15 @@ module ParsedInput = walkType t | _ -> () - and walkField (SynField.Field(attrs, _, _, t, _, _, _, _)) = + and walkField (SynField.Field(Attributes attrs, _, _, t, _, _, _, _)) = List.iter walkAttribute attrs walkType t - and walkValSig (SynValSig.ValSpfn(attrs, _, _, t, SynValInfo(argInfos, argInfo), _, _, _, _, _, _)) = + and walkValSig (SynValSig.ValSpfn(Attributes attrs, _, _, t, SynValInfo(argInfos, argInfo), _, _, _, _, _, _)) = List.iter walkAttribute attrs walkType t argInfo :: (argInfos |> List.concat) - |> List.map (fun (SynArgInfo(attrs, _, _)) -> attrs) + |> List.map (fun (SynArgInfo(Attributes attrs, _, _)) -> attrs) |> List.concat |> List.iter walkAttribute @@ -765,9 +765,9 @@ module ParsedInput = and walkMember = function | SynMemberDefn.AbstractSlot (valSig, _, _) -> walkValSig valSig | SynMemberDefn.Member (binding, _) -> walkBinding binding - | SynMemberDefn.ImplicitCtor (_, attrs, pats, _, _) -> + | SynMemberDefn.ImplicitCtor (_, Attributes attrs, SynSimplePats.SimplePats(simplePats, _), _, _) -> List.iter walkAttribute attrs - List.iter walkSimplePat pats + List.iter walkSimplePat simplePats | SynMemberDefn.ImplicitInherit (t, e, _, _) -> walkType t; walkExpr e | SynMemberDefn.LetBindings (bindings, _, _, _) -> List.iter walkBinding bindings | SynMemberDefn.Interface (t, members, _) -> @@ -776,19 +776,19 @@ module ParsedInput = | SynMemberDefn.Inherit (t, _, _) -> walkType t | SynMemberDefn.ValField (field, _) -> walkField field | SynMemberDefn.NestedType (tdef, _, _) -> walkTypeDefn tdef - | SynMemberDefn.AutoProperty (attrs, _, _, t, _, _, _, _, e, _, _) -> + | SynMemberDefn.AutoProperty (Attributes attrs, _, _, t, _, _, _, _, e, _, _) -> List.iter walkAttribute attrs Option.iter walkType t walkExpr e | _ -> () - and walkEnumCase (EnumCase(attrs, _, _, _, _)) = List.iter walkAttribute attrs + and walkEnumCase (EnumCase(Attributes attrs, _, _, _, _)) = List.iter walkAttribute attrs and walkUnionCaseType = function | SynUnionCaseType.UnionCaseFields fields -> List.iter walkField fields | SynUnionCaseType.UnionCaseFullType (t, _) -> walkType t - and walkUnionCase (SynUnionCase.UnionCase (attrs, _, t, _, _, _)) = + and walkUnionCase (SynUnionCase.UnionCase (Attributes attrs, _, t, _, _, _)) = List.iter walkAttribute attrs walkUnionCaseType t @@ -799,7 +799,7 @@ module ParsedInput = | SynTypeDefnSimpleRepr.TypeAbbrev (_, t, _) -> walkType t | _ -> () - and walkComponentInfo isTypeExtensionOrAlias (ComponentInfo(attrs, typars, constraints, longIdent, _, _, _, _)) = + and walkComponentInfo isTypeExtensionOrAlias (ComponentInfo(Attributes attrs, typars, constraints, longIdent, _, _, _, _)) = List.iter walkAttribute attrs List.iter walkTyparDecl typars List.iter walkTypeConstraint constraints @@ -836,7 +836,7 @@ module ParsedInput = | SynModuleDecl.Let (_, bindings, _) -> List.iter walkBinding bindings | SynModuleDecl.DoExpr (_, expr, _) -> walkExpr expr | SynModuleDecl.Types (types, _) -> List.iter walkTypeDefn types - | SynModuleDecl.Attributes (attrs, _) -> List.iter walkAttribute attrs + | SynModuleDecl.Attributes (Attributes attrs, _) -> List.iter walkAttribute attrs | _ -> () match input with diff --git a/src/fsharp/service/ServiceInterfaceStubGenerator.fs b/src/fsharp/service/ServiceInterfaceStubGenerator.fs index 1dad892e8dc..d6e46783e74 100644 --- a/src/fsharp/service/ServiceInterfaceStubGenerator.fs +++ b/src/fsharp/service/ServiceInterfaceStubGenerator.fs @@ -4,7 +4,6 @@ namespace FSharp.Compiler.SourceCodeServices open System open System.Diagnostics -open System.Collections.Generic open FSharp.Compiler open FSharp.Compiler.Ast open FSharp.Compiler.Range @@ -102,7 +101,7 @@ module internal CodeGenerationUtils = /// Capture information about an interface in ASTs [] -type internal InterfaceData = +type InterfaceData = | Interface of SynType * SynMemberDefns option | ObjExpr of SynType * SynBinding list member x.Range = @@ -168,7 +167,7 @@ type internal InterfaceData = | _ -> [||] -module internal InterfaceStubGenerator = +module InterfaceStubGenerator = [] type internal Context = { diff --git a/src/fsharp/service/ServiceInterfaceStubGenerator.fsi b/src/fsharp/service/ServiceInterfaceStubGenerator.fsi index 49a0da63d33..4212449408f 100644 --- a/src/fsharp/service/ServiceInterfaceStubGenerator.fsi +++ b/src/fsharp/service/ServiceInterfaceStubGenerator.fsi @@ -2,25 +2,20 @@ namespace FSharp.Compiler.SourceCodeServices -open System -open System.Diagnostics -open System.Collections.Generic -open FSharp.Compiler open FSharp.Compiler.Ast open FSharp.Compiler.Range open FSharp.Compiler.SourceCodeServices -open FSharp.Compiler.AbstractIL.Internal.Library #if !FX_NO_INDENTED_TEXT_WRITER /// Capture information about an interface in ASTs [] -type internal InterfaceData = +type InterfaceData = | Interface of SynType * SynMemberDefns option | ObjExpr of SynType * SynBinding list member Range : range member TypeParameters : string[] -module internal InterfaceStubGenerator = +module InterfaceStubGenerator = /// Get members in the decreasing order of inheritance chain val getInterfaceMembers : FSharpEntity -> seq> diff --git a/src/fsharp/service/ServiceParseTreeWalk.fs b/src/fsharp/service/ServiceParseTreeWalk.fs index 55975c6cb53..abd3472a680 100755 --- a/src/fsharp/service/ServiceParseTreeWalk.fs +++ b/src/fsharp/service/ServiceParseTreeWalk.fs @@ -606,8 +606,10 @@ module public AstTraversal = match m with | SynMemberDefn.Open(_longIdent, _range) -> None | SynMemberDefn.Member(synBinding, _range) -> traverseSynBinding path synBinding - | SynMemberDefn.ImplicitCtor(_synAccessOption, _synAttributes, synSimplePatList, _identOption, _range) -> - visitor.VisitSimplePats(synSimplePatList) + | SynMemberDefn.ImplicitCtor(_synAccessOption, _synAttributes, simplePats, _identOption, _range) -> + match simplePats with + | SynSimplePats.SimplePats(simplePats, _) -> visitor.VisitSimplePats(simplePats) + | _ -> None | SynMemberDefn.ImplicitInherit(synType, synExpr, _identOption, range) -> [ dive () synType.Range (fun () -> diff --git a/src/fsharp/service/ServiceStructure.fs b/src/fsharp/service/ServiceStructure.fs index 1f35e9e8ce0..1bf31e27cf6 100644 --- a/src/fsharp/service/ServiceStructure.fs +++ b/src/fsharp/service/ServiceStructure.fs @@ -405,7 +405,7 @@ module Structure = rcheck Scope.MatchClause Collapse.Same e.Range collapse parseExpr e - and parseAttributes (attrs: SynAttributes) = + and parseAttributes (Attributes attrs) = let attrListRange() = if not (List.isEmpty attrs) then let range = Range.startToEnd (attrs.[0].Range) (attrs.[attrs.Length-1].ArgExpr.Range) diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 4e5b1e596fe..f98fae2f720 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -723,7 +723,7 @@ module UntypedParseImpl = let rec walkImplFileInput (ParsedImplFileInput (modules = moduleOrNamespaceList)) = List.tryPick (walkSynModuleOrNamespace true) moduleOrNamespaceList - and walkSynModuleOrNamespace isTopLevel (SynModuleOrNamespace(_, _, _, decls, _, attrs, _, r)) = + and walkSynModuleOrNamespace isTopLevel (SynModuleOrNamespace(_, _, _, decls, _, Attributes attrs, _, r)) = List.tryPick walkAttribute attrs |> Option.orElse (ifPosInRange r (fun _ -> List.tryPick (walkSynModuleDecl isTopLevel) decls)) @@ -733,7 +733,7 @@ module UntypedParseImpl = and walkTypar (Typar (ident, _, _)) = ifPosInRange ident.idRange (fun _ -> Some EntityKind.Type) - and walkTyparDecl (SynTyparDecl.TyparDecl (attrs, typar)) = + and walkTyparDecl (SynTyparDecl.TyparDecl (Attributes attrs, typar)) = List.tryPick walkAttribute attrs |> Option.orElse (walkTypar typar) @@ -757,7 +757,7 @@ module UntypedParseImpl = if isPosInRange nameRange then None else walkPat pat | SynPat.Typed(pat, t, _) -> walkPat pat |> Option.orElse (walkType t) - | SynPat.Attrib(pat, attrs, _) -> walkPat pat |> Option.orElse (List.tryPick walkAttribute attrs) + | SynPat.Attrib(pat, Attributes attrs, _) -> walkPat pat |> Option.orElse (List.tryPick walkAttribute attrs) | SynPat.Or(pat1, pat2, _) -> List.tryPick walkPat [pat1; pat2] | SynPat.LongIdent(_, _, typars, ConstructorPats pats, _, r) -> ifPosInRange r (fun _ -> kind) @@ -776,7 +776,7 @@ module UntypedParseImpl = and walkPat = walkPatWithKind None - and walkBinding (SynBinding.Binding(_, _, _, _, attrs, _, _, pat, returnInfo, e, _, _)) = + and walkBinding (SynBinding.Binding(_, _, _, _, Attributes attrs, _, _, pat, returnInfo, e, _, _)) = List.tryPick walkAttribute attrs |> Option.orElse (walkPat pat) |> Option.orElse (walkExpr e) @@ -892,15 +892,15 @@ module UntypedParseImpl = and walkExpr = walkExprWithKind None and walkSimplePat = function - | SynSimplePat.Attrib (pat, attrs, _) -> + | SynSimplePat.Attrib (pat, Attributes attrs, _) -> walkSimplePat pat |> Option.orElse (List.tryPick walkAttribute attrs) | SynSimplePat.Typed(pat, t, _) -> walkSimplePat pat |> Option.orElse (walkType t) | _ -> None - and walkField (SynField.Field(attrs, _, _, t, _, _, _, _)) = + and walkField (SynField.Field(Attributes attrs, _, _, t, _, _, _, _)) = List.tryPick walkAttribute attrs |> Option.orElse (walkType t) - and walkValSig (SynValSig.ValSpfn(attrs, _, _, t, _, _, _, _, _, _, _)) = + and walkValSig (SynValSig.ValSpfn(Attributes attrs, _, _, t, _, _, _, _, _, _, _)) = List.tryPick walkAttribute attrs |> Option.orElse (walkType t) and walkMemberSig = function @@ -916,8 +916,8 @@ module UntypedParseImpl = and walkMember = function | SynMemberDefn.AbstractSlot (valSig, _, _) -> walkValSig valSig | SynMemberDefn.Member(binding, _) -> walkBinding binding - | SynMemberDefn.ImplicitCtor(_, attrs, pats, _, _) -> - List.tryPick walkAttribute attrs |> Option.orElse (List.tryPick walkSimplePat pats) + | SynMemberDefn.ImplicitCtor(_, Attributes attrs, SynSimplePats.SimplePats(simplePats, _), _, _) -> + List.tryPick walkAttribute attrs |> Option.orElse (List.tryPick walkSimplePat simplePats) | SynMemberDefn.ImplicitInherit(t, e, _, _) -> walkType t |> Option.orElse (walkExpr e) | SynMemberDefn.LetBindings(bindings, _, _, _) -> List.tryPick walkBinding bindings | SynMemberDefn.Interface(t, members, _) -> @@ -925,19 +925,19 @@ module UntypedParseImpl = | SynMemberDefn.Inherit(t, _, _) -> walkType t | SynMemberDefn.ValField(field, _) -> walkField field | SynMemberDefn.NestedType(tdef, _, _) -> walkTypeDefn tdef - | SynMemberDefn.AutoProperty(attrs, _, _, t, _, _, _, _, e, _, _) -> + | SynMemberDefn.AutoProperty(Attributes attrs, _, _, t, _, _, _, _, e, _, _) -> List.tryPick walkAttribute attrs |> Option.orElse (Option.bind walkType t) |> Option.orElse (walkExpr e) | _ -> None - and walkEnumCase (EnumCase(attrs, _, _, _, _)) = List.tryPick walkAttribute attrs + and walkEnumCase (EnumCase(Attributes attrs, _, _, _, _)) = List.tryPick walkAttribute attrs and walkUnionCaseType = function | SynUnionCaseType.UnionCaseFields fields -> List.tryPick walkField fields | SynUnionCaseType.UnionCaseFullType(t, _) -> walkType t - and walkUnionCase (UnionCase(attrs, _, t, _, _, _)) = + and walkUnionCase (UnionCase(Attributes attrs, _, t, _, _, _)) = List.tryPick walkAttribute attrs |> Option.orElse (walkUnionCaseType t) and walkTypeDefnSimple = function @@ -947,7 +947,7 @@ module UntypedParseImpl = | SynTypeDefnSimpleRepr.TypeAbbrev(_, t, _) -> walkType t | _ -> None - and walkComponentInfo isModule (ComponentInfo(attrs, typars, constraints, _, _, _, _, r)) = + and walkComponentInfo isModule (ComponentInfo(Attributes attrs, typars, constraints, _, _, _, _, r)) = if isModule then None else ifPosInRange r (fun _ -> Some EntityKind.Type) |> Option.orElse ( List.tryPick walkAttribute attrs @@ -1051,7 +1051,7 @@ module UntypedParseImpl = | false, false, true -> Struct | _ -> Invalid - let GetCompletionContextForInheritSynMember ((ComponentInfo(synAttributes, _, _, _, _, _, _, _)), typeDefnKind : SynTypeDefnKind, completionPath) = + let GetCompletionContextForInheritSynMember ((ComponentInfo(Attributes synAttributes, _, _, _, _, _, _, _)), typeDefnKind : SynTypeDefnKind, completionPath) = let success k = Some (CompletionContext.Inherit (k, completionPath)) diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index a3afd792b86..cc7d11b9f9f 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -1,59 +1,36 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -// Open up the compiler as an incremental service for parsing, -// type checking and intellisense-like environment-reporting. - namespace FSharp.Compiler.SourceCodeServices open System -open System.Collections.Generic open System.Collections.Concurrent open System.Diagnostics open System.IO open System.Reflection -open System.Text -open Microsoft.FSharp.Core.Printf -open FSharp.Compiler +open FSharp.Compiler open FSharp.Compiler.AbstractIL open FSharp.Compiler.AbstractIL.IL open FSharp.Compiler.AbstractIL.ILBinaryReader -open FSharp.Compiler.AbstractIL.Diagnostics -open FSharp.Compiler.AbstractIL.Internal -open FSharp.Compiler.AbstractIL.Internal.Library +open FSharp.Compiler.AbstractIL.Internal.Library -open FSharp.Compiler.AccessibilityLogic open FSharp.Compiler.Ast open FSharp.Compiler.CompileOps open FSharp.Compiler.CompileOptions open FSharp.Compiler.Driver open FSharp.Compiler.ErrorLogger open FSharp.Compiler.Lib -open FSharp.Compiler.PrettyNaming -open FSharp.Compiler.Parser open FSharp.Compiler.Range -open FSharp.Compiler.Lexhelp -open FSharp.Compiler.Layout -open FSharp.Compiler.Tast -open FSharp.Compiler.Tastops -open FSharp.Compiler.TcGlobals +open FSharp.Compiler.TcGlobals open FSharp.Compiler.Text -open FSharp.Compiler.Infos -open FSharp.Compiler.InfoReader -open FSharp.Compiler.NameResolution -open FSharp.Compiler.TypeChecker -open FSharp.Compiler.SourceCodeServices.SymbolHelpers open Internal.Utilities open Internal.Utilities.Collections -open FSharp.Compiler.Layout.TaggedTextOps type internal Layout = StructuredFormat.Layout [] module EnvMisc = - let getToolTipTextSize = GetEnvInteger "FCS_GetToolTipTextCacheSize" 5 - let maxTypeCheckErrorsOutOfProjectContext = GetEnvInteger "FCS_MaxErrorsOutOfProjectContext" 3 let braceMatchCacheSize = GetEnvInteger "FCS_BraceMatchCacheSize" 5 let parseFileCacheSize = GetEnvInteger "FCS_ParseFileCacheSize" 2 let checkFileInProjectCacheSize = GetEnvInteger "FCS_CheckFileInProjectCacheSize" 10 @@ -63,1709 +40,6 @@ module EnvMisc = let maxMBDefault = GetEnvInteger "FCS_MaxMB" 1000000 // a million MB = 1TB = disabled //let maxMBDefault = GetEnvInteger "FCS_maxMB" (if sizeof = 4 then 1700 else 3400) - /// Maximum time share for a piece of background work before it should (cooperatively) yield - /// to enable other requests to be serviced. Yielding means returning a continuation function - /// (via an Eventually<_> value of case NotYetDone) that can be called as the next piece of work. - let maxTimeShareMilliseconds = - match System.Environment.GetEnvironmentVariable("FCS_MaxTimeShare") with - | null | "" -> 100L - | s -> int64 s - - -//---------------------------------------------------------------------------- -// Scopes. -//-------------------------------------------------------------------------- - -[] -type FSharpFindDeclFailureReason = - // generic reason: no particular information about error - | Unknown of message: string - // source code file is not available - | NoSourceCode - // trying to find declaration of ProvidedType without TypeProviderDefinitionLocationAttribute - | ProvidedType of string - // trying to find declaration of ProvidedMember without TypeProviderDefinitionLocationAttribute - | ProvidedMember of string - -[] -type FSharpFindDeclResult = - /// declaration not found + reason - | DeclNotFound of FSharpFindDeclFailureReason - /// found declaration - | DeclFound of range - /// Indicates an external declaration was found - | ExternalDecl of assembly : string * externalSym : ExternalSymbol - -/// This type is used to describe what was found during the name resolution. -/// (Depending on the kind of the items, we may stop processing or continue to find better items) -[] -[] -type internal NameResResult = - | Members of (ItemWithInst list * DisplayEnv * range) - | Cancel of DisplayEnv * range - | Empty - | TypecheckStaleAndTextChanged - - -[] -type ResolveOverloads = -| Yes -| No - -[] -type GetPreciseCompletionListFromExprTypingsResult = - | NoneBecauseTypecheckIsStaleAndTextChanged - | NoneBecauseThereWereTypeErrors - | None - | Some of (ItemWithInst list * DisplayEnv * range) * TType - -type Names = string list - -[] -type SemanticClassificationType = - | ReferenceType - | ValueType - | UnionCase - | Function - | Property - | MutableVar - | Module - | Printf - | ComputationExpression - | IntrinsicFunction - | Enumeration - | Interface - | TypeArgument - | Operator - | Disposable - -/// A TypeCheckInfo represents everything we get back from the typecheck of a file. -/// It acts like an in-memory database about the file. -/// It is effectively immutable and not updated: when we re-typecheck we just drop the previous -/// scope object on the floor and make a new one. -[] -type TypeCheckInfo - (// Information corresponding to miscellaneous command-line options (--define, etc). - _sTcConfig: TcConfig, - g: TcGlobals, - // The signature of the assembly being checked, up to and including the current file - ccuSigForFile: ModuleOrNamespaceType, - thisCcu: CcuThunk, - tcImports: TcImports, - tcAccessRights: AccessorDomain, - projectFileName: string, - mainInputFileName: string, - sResolutions: TcResolutions, - sSymbolUses: TcSymbolUses, - // This is a name resolution environment to use if no better match can be found. - sFallback: NameResolutionEnv, - loadClosure : LoadClosure option, - reactorOps : IReactorOperations, - textSnapshotInfo:obj option, - implFileOpt: TypedImplFile option, - openDeclarations: OpenDeclaration[]) = - - let textSnapshotInfo = defaultArg textSnapshotInfo null - let (|CNR|) (cnr:CapturedNameResolution) = - (cnr.Pos, cnr.Item, cnr.ItemOccurence, cnr.DisplayEnv, cnr.NameResolutionEnv, cnr.AccessorDomain, cnr.Range) - - // These strings are potentially large and the editor may choose to hold them for a while. - // Use this cache to fold together data tip text results that are the same. - // Is not keyed on 'Names' collection because this is invariant for the current position in - // this unchanged file. Keyed on lineStr though to prevent a change to the currently line - // being available against a stale scope. - let getToolTipTextCache = AgedLookup>(getToolTipTextSize,areSimilar=(fun (x,y) -> x = y)) - - let amap = tcImports.GetImportMap() - let infoReader = new InfoReader(g,amap) - let ncenv = new NameResolver(g,amap,infoReader,NameResolution.FakeInstantiationGenerator) - let cenv = SymbolEnv(g, thisCcu, Some ccuSigForFile, tcImports, amap, infoReader) - - /// Find the most precise naming environment for the given line and column - let GetBestEnvForPos cursorPos = - - let mutable bestSoFar = None - - // Find the most deeply nested enclosing scope that contains given position - sResolutions.CapturedEnvs |> ResizeArray.iter (fun (possm,env,ad) -> - if rangeContainsPos possm cursorPos then - match bestSoFar with - | Some (bestm,_,_) -> - if rangeContainsRange bestm possm then - bestSoFar <- Some (possm,env,ad) - | None -> - bestSoFar <- Some (possm,env,ad)) - - let mostDeeplyNestedEnclosingScope = bestSoFar - - // Look for better subtrees on the r.h.s. of the subtree to the left of where we are - // Should really go all the way down the r.h.s. of the subtree to the left of where we are - // This is all needed when the index is floating free in the area just after the environment we really want to capture - // We guarantee to only refine to a more nested environment. It may not be strictly - // the right environment, but will always be at least as rich - - let bestAlmostIncludedSoFar = ref None - - sResolutions.CapturedEnvs |> ResizeArray.iter (fun (possm,env,ad) -> - // take only ranges that strictly do not include cursorPos (all ranges that touch cursorPos were processed during 'Strict Inclusion' part) - if rangeBeforePos possm cursorPos && not (posEq possm.End cursorPos) then - let contained = - match mostDeeplyNestedEnclosingScope with - | Some (bestm,_,_) -> rangeContainsRange bestm possm - | None -> true - - if contained then - match !bestAlmostIncludedSoFar with - | Some (rightm:range,_,_) -> - if posGt possm.End rightm.End || - (posEq possm.End rightm.End && posGt possm.Start rightm.Start) then - bestAlmostIncludedSoFar := Some (possm,env,ad) - | _ -> bestAlmostIncludedSoFar := Some (possm,env,ad)) - - let resEnv = - match !bestAlmostIncludedSoFar, mostDeeplyNestedEnclosingScope with - | Some (_,env,ad), None -> env, ad - | Some (_,almostIncludedEnv,ad), Some (_,mostDeeplyNestedEnv,_) - when almostIncludedEnv.eFieldLabels.Count >= mostDeeplyNestedEnv.eFieldLabels.Count -> - almostIncludedEnv,ad - | _ -> - match mostDeeplyNestedEnclosingScope with - | Some (_,env,ad) -> - env,ad - | None -> - sFallback,AccessibleFromSomeFSharpCode - let pm = mkRange mainInputFileName cursorPos cursorPos - - resEnv,pm - - /// The items that come back from ResolveCompletionsInType are a bit - /// noisy. Filter a few things out. - /// - /// e.g. prefer types to constructors for FSharpToolTipText - let FilterItemsForCtors filterCtors (items: ItemWithInst list) = - let items = items |> List.filter (fun item -> match item.Item with (Item.CtorGroup _) when filterCtors = ResolveTypeNamesToTypeRefs -> false | _ -> true) - items - - // Filter items to show only valid & return Some if there are any - let ReturnItemsOfType (items: ItemWithInst list) g denv (m:range) filterCtors hasTextChangedSinceLastTypecheck = - let items = - items - |> RemoveDuplicateItems g - |> RemoveExplicitlySuppressed g - |> FilterItemsForCtors filterCtors - - if not (isNil items) then - if hasTextChangedSinceLastTypecheck(textSnapshotInfo, m) then - NameResResult.TypecheckStaleAndTextChanged // typecheck is stale, wait for second-chance IntelliSense to bring up right result - else - NameResResult.Members (items, denv, m) - else NameResResult.Empty - - let GetCapturedNameResolutions endOfNamesPos resolveOverloads = - - let quals = - match resolveOverloads with - | ResolveOverloads.Yes -> sResolutions.CapturedNameResolutions - | ResolveOverloads.No -> sResolutions.CapturedMethodGroupResolutions - - let quals = quals |> ResizeArray.filter (fun cnr -> posEq cnr.Pos endOfNamesPos) - - quals - - /// Looks at the exact name resolutions that occurred during type checking - /// If 'membersByResidue' is specified, we look for members of the item obtained - /// from the name resolution and filter them by the specified residue (?) - let GetPreciseItemsFromNameResolution(line, colAtEndOfNames, membersByResidue, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck) = - let endOfNamesPos = mkPos line colAtEndOfNames - - // Logic below expects the list to be in reverse order of resolution - let cnrs = GetCapturedNameResolutions endOfNamesPos resolveOverloads |> ResizeArray.toList |> List.rev - - match cnrs, membersByResidue with - - // If we're looking for members using a residue, we'd expect only - // a single item (pick the first one) and we need the residue (which may be "") - | CNR(_,Item.Types(_,(ty :: _)), _, denv, nenv, ad, m) :: _, Some _ -> - let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad true ty - let items = List.map ItemWithNoInst items - ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck - - // Value reference from the name resolution. Primarily to disallow "let x.$ = 1" - // In most of the cases, value references can be obtained from expression typings or from environment, - // so we wouldn't have to handle values here. However, if we have something like: - // let varA = "string" - // let varA = if b then 0 else varA. - // then the expression typings get confused (thinking 'varA:int'), so we use name resolution even for usual values. - - | CNR(_, Item.Value(vref), occurence, denv, nenv, ad, m) :: _, Some _ -> - if (occurence = ItemOccurence.Binding || occurence = ItemOccurence.Pattern) then - // Return empty list to stop further lookup - for value declarations - NameResResult.Cancel(denv, m) - else - // If we have any valid items for the value, then return completions for its type now. - // Adjust the type in case this is the 'this' pointer stored in a reference cell. - let ty = StripSelfRefCell(g, vref.BaseOrThisInfo, vref.TauType) - // patch accessibility domain to remove protected members if accessing NormalVal - let ad = - match vref.BaseOrThisInfo, ad with - | ValBaseOrThisInfo.NormalVal, AccessibleFrom(paths, Some tcref) -> - let tcref = generalizedTyconRef tcref - // check that type of value is the same or subtype of tcref - // yes - allow access to protected members - // no - strip ability to access protected members - if FSharp.Compiler.TypeRelations.TypeFeasiblySubsumesType 0 g amap m tcref FSharp.Compiler.TypeRelations.CanCoerce ty then - ad - else - AccessibleFrom(paths, None) - | _ -> ad - - let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad false ty - let items = List.map ItemWithNoInst items - ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck - - // No residue, so the items are the full resolution of the name - | CNR(_, _, _, denv, _, _, m) :: _, None -> - let items = - cnrs - |> List.map (fun cnr -> cnr.ItemWithInst) - // "into" is special magic syntax, not an identifier or a library call. It is part of capturedNameResolutions as an - // implementation detail of syntax coloring, but we should not report name resolution results for it, to prevent spurious QuickInfo. - |> List.filter (fun item -> match item.Item with Item.CustomOperation(CustomOperations.Into,_,_) -> false | _ -> true) - ReturnItemsOfType items g denv m filterCtors hasTextChangedSinceLastTypecheck - | _, _ -> NameResResult.Empty - - let TryGetTypeFromNameResolution(line, colAtEndOfNames, membersByResidue, resolveOverloads) = - let endOfNamesPos = mkPos line colAtEndOfNames - let items = GetCapturedNameResolutions endOfNamesPos resolveOverloads |> ResizeArray.toList |> List.rev - - match items, membersByResidue with - | CNR(_,Item.Types(_,(ty :: _)),_,_,_,_,_) :: _, Some _ -> Some ty - | CNR(_, Item.Value(vref), occurence,_,_,_,_) :: _, Some _ -> - if (occurence = ItemOccurence.Binding || occurence = ItemOccurence.Pattern) then None - else Some (StripSelfRefCell(g, vref.BaseOrThisInfo, vref.TauType)) - | _, _ -> None - - let CollectParameters (methods: MethInfo list) amap m: Item list = - methods - |> List.collect (fun meth -> - match meth.GetParamDatas(amap, m, meth.FormalMethodInst) with - | x :: _ -> x |> List.choose(fun (ParamData(_isParamArray, _isInArg, _isOutArg, _optArgInfo, _callerInfo, name, _, ty)) -> - match name with - | Some n -> Some (Item.ArgName(n, ty, Some (ArgumentContainer.Method meth))) - | None -> None - ) - | _ -> [] - ) - - let GetNamedParametersAndSettableFields endOfExprPos hasTextChangedSinceLastTypecheck = - let cnrs = GetCapturedNameResolutions endOfExprPos ResolveOverloads.No |> ResizeArray.toList |> List.rev - let result = - match cnrs with - | CNR(_, Item.CtorGroup(_, ((ctor :: _) as ctors)), _, denv, nenv, ad, m) :: _ -> - let props = ResolveCompletionsInType ncenv nenv ResolveCompletionTargets.SettablePropertiesAndFields m ad false ctor.ApparentEnclosingType - let parameters = CollectParameters ctors amap m - let items = props @ parameters - Some (denv, m, items) - | CNR(_, Item.MethodGroup(_, methods, _), _, denv, nenv, ad, m) :: _ -> - let props = - methods - |> List.collect (fun meth -> - let retTy = meth.GetFSharpReturnTy(amap, m, meth.FormalMethodInst) - ResolveCompletionsInType ncenv nenv ResolveCompletionTargets.SettablePropertiesAndFields m ad false retTy - ) - let parameters = CollectParameters methods amap m - let items = props @ parameters - Some (denv, m, items) - | _ -> - None - match result with - | None -> - NameResResult.Empty - | Some (denv, m, items) -> - let items = List.map ItemWithNoInst items - ReturnItemsOfType items g denv m TypeNameResolutionFlag.ResolveTypeNamesToTypeRefs hasTextChangedSinceLastTypecheck - - /// finds captured typing for the given position - let GetExprTypingForPosition(endOfExprPos) = - let quals = - sResolutions.CapturedExpressionTypings - |> Seq.filter (fun (pos,ty,denv,_,_,_) -> - // We only want expression types that end at the particular position in the file we are looking at. - let isLocationWeCareAbout = posEq pos endOfExprPos - // Get rid of function types. True, given a 2-arg curried function "f x y", it is legal to do "(f x).GetType()", - // but you almost never want to do this in practice, and we choose not to offer up any intellisense for - // F# function types. - let isFunction = isFunTy denv.g ty - isLocationWeCareAbout && not isFunction) - |> Seq.toArray - - let thereWereSomeQuals = not (Array.isEmpty quals) - // filter out errors - - let quals = quals - |> Array.filter (fun (_,ty,denv,_,_,_) -> not (isTyparTy denv.g ty && (destTyparTy denv.g ty).IsFromError)) - thereWereSomeQuals, quals - - /// obtains captured typing for the given position - /// if type of captured typing is record - returns list of record fields - let GetRecdFieldsForExpr(r : range) = - let _, quals = GetExprTypingForPosition(r.End) - let bestQual = - match quals with - | [||] -> None - | quals -> - quals |> Array.tryFind (fun (_,_,_,_,_,rq) -> - ignore(r) // for breakpoint - posEq r.Start rq.Start) - match bestQual with - | Some (_,ty,denv,_nenv,ad,m) when isRecdTy denv.g ty -> - let items = NameResolution.ResolveRecordOrClassFieldsOfType ncenv m ad ty false - Some (items, denv, m) - | _ -> None - - /// Looks at the exact expression types at the position to the left of the - /// residue then the source when it was typechecked. - let GetPreciseCompletionListFromExprTypings(parseResults:FSharpParseFileResults, endOfExprPos, filterCtors, hasTextChangedSinceLastTypecheck: (obj * range -> bool)) = - - let thereWereSomeQuals, quals = GetExprTypingForPosition(endOfExprPos) - - match quals with - | [| |] -> - if thereWereSomeQuals then - GetPreciseCompletionListFromExprTypingsResult.NoneBecauseThereWereTypeErrors - else - GetPreciseCompletionListFromExprTypingsResult.None - | _ -> - let bestQual, textChanged = - match parseResults.ParseTree with - | Some(input) -> - match UntypedParseImpl.GetRangeOfExprLeftOfDot(endOfExprPos,Some(input)) with // TODO we say "colAtEndOfNames" everywhere, but that's not really a good name ("foo . $" hit Ctrl-Space at $) - | Some( exprRange) -> - if hasTextChangedSinceLastTypecheck(textSnapshotInfo, exprRange) then - None, true // typecheck is stale, wait for second-chance IntelliSense to bring up right result - else - // See bug 130733. We have an up-to-date sync parse, and know the exact range of the prior expression. - // The quals all already have the same ending position, so find one with a matching starting position, if it exists. - // If not, then the stale typecheck info does not have a capturedExpressionTyping for this exact expression, and the - // user can wait for typechecking to catch up and second-chance intellisense to give the right result. - let qual = - quals |> Array.tryFind (fun (_,_,_,_,_,r) -> - ignore(r) // for breakpoint - posEq exprRange.Start r.Start) - qual, false - | None -> - // TODO In theory I think we should never get to this code path; it would be nice to add an assert. - // In practice, we do get here in some weird cases like "2.0 .. 3.0" and hitting Ctrl-Space in between the two dots of the range operator. - // I wasn't able to track down what was happening in those weird cases, not worth worrying about, it doesn't manifest as a product bug or anything. - None, false - | _ -> None, false - - match bestQual with - | Some bestQual -> - let (_,ty,denv,nenv,ad,m) = bestQual - let items = ResolveCompletionsInType ncenv nenv (ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m)) m ad false ty - let items = items |> List.map ItemWithNoInst - let items = items |> RemoveDuplicateItems g - let items = items |> RemoveExplicitlySuppressed g - let items = items |> FilterItemsForCtors filterCtors - GetPreciseCompletionListFromExprTypingsResult.Some((items,denv,m), ty) - | None -> - if textChanged then GetPreciseCompletionListFromExprTypingsResult.NoneBecauseTypecheckIsStaleAndTextChanged - else GetPreciseCompletionListFromExprTypingsResult.None - - /// Find items in the best naming environment. - let GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, showObsolete) = - let items = NameResolution.ResolvePartialLongIdent ncenv nenv (ConstraintSolver.IsApplicableMethApprox g amap m) m ad plid showObsolete - let items = items |> List.map ItemWithNoInst - let items = items |> RemoveDuplicateItems g - let items = items |> RemoveExplicitlySuppressed g - let items = items |> FilterItemsForCtors filterCtors - (items, nenv.DisplayEnv, m) - - /// Find items in the best naming environment. - let GetEnvironmentLookupResolutionsAtPosition(cursorPos, plid, filterCtors, showObsolete) = - let (nenv,ad),m = GetBestEnvForPos cursorPos - GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, showObsolete) - - /// Find record fields in the best naming environment. - let GetClassOrRecordFieldsEnvironmentLookupResolutions(cursorPos, plid) = - let (nenv, ad),m = GetBestEnvForPos cursorPos - let items = NameResolution.ResolvePartialLongIdentToClassOrRecdFields ncenv nenv m ad plid false - let items = items |> List.map ItemWithNoInst - let items = items |> RemoveDuplicateItems g - let items = items |> RemoveExplicitlySuppressed g - items, nenv.DisplayEnv, m - - /// Resolve a location and/or text to items. - // Three techniques are used - // - look for an exact known name resolution from type checking - // - use the known type of an expression, e.g. (expr).Name, to generate an item list - // - lookup an entire name in the name resolution environment, e.g. A.B.Name, to generate an item list - // - // The overall aim is to resolve as accurately as possible based on what we know from type inference - - let GetBaseClassCandidates = function - | Item.ModuleOrNamespaces _ -> true - | Item.Types(_, ty :: _) when (isClassTy g ty) && not (isSealedTy g ty) -> true - | _ -> false - - let GetInterfaceCandidates = function - | Item.ModuleOrNamespaces _ -> true - | Item.Types(_, ty :: _) when (isInterfaceTy g ty) -> true - | _ -> false - - - // Return only items with the specified name - let FilterDeclItemsByResidue (getItem: 'a -> Item) residue (items: 'a list) = - let attributedResidue = residue + "Attribute" - let nameMatchesResidue name = (residue = name) || (attributedResidue = name) - - items |> List.filter (fun x -> - let item = getItem x - let n1 = item.DisplayName - match item with - | Item.Types _ -> nameMatchesResidue n1 - | Item.CtorGroup (_, meths) -> - nameMatchesResidue n1 || - meths |> List.exists (fun meth -> - let tcref = meth.ApparentEnclosingTyconRef - tcref.IsProvided || nameMatchesResidue tcref.DisplayName) - | _ -> residue = n1) - - /// Post-filter items to make sure they have precisely the right name - /// This also checks that there are some remaining results - /// exactMatchResidueOpt = Some _ -- means that we are looking for exact matches - let FilterRelevantItemsBy (getItem: 'a -> Item) (exactMatchResidueOpt : _ option) check (items: 'a list, denv, m) = - - // can throw if type is in located in non-resolved CCU: i.e. bigint if reference to System.Numerics is absent - let safeCheck item = try check item with _ -> false - - // Are we looking for items with precisely the given name? - if not (isNil items) && exactMatchResidueOpt.IsSome then - let items = items |> FilterDeclItemsByResidue getItem exactMatchResidueOpt.Value |> List.filter safeCheck - if not (isNil items) then Some(items, denv, m) else None - else - // When (items = []) we must returns Some([],..) and not None - // because this value is used if we want to stop further processing (e.g. let x.$ = ...) - let items = items |> List.filter safeCheck - Some(items, denv, m) - - /// Post-filter items to make sure they have precisely the right name - /// This also checks that there are some remaining results - let (|FilterRelevantItems|_|) getItem exactMatchResidueOpt orig = - FilterRelevantItemsBy getItem exactMatchResidueOpt (fun _ -> true) orig - - /// Find the first non-whitespace position in a line prior to the given character - let FindFirstNonWhitespacePosition (lineStr: string) i = - if i >= lineStr.Length then None - else - let mutable p = i - while p >= 0 && System.Char.IsWhiteSpace(lineStr.[p]) do - p <- p - 1 - if p >= 0 then Some p else None - - let CompletionItem (ty: ValueOption) (assemblySymbol: ValueOption) (item: ItemWithInst) = - let kind = - match item.Item with - | Item.MethodGroup (_, minfo :: _, _) -> CompletionItemKind.Method minfo.IsExtensionMember - | Item.RecdField _ - | Item.Property _ -> CompletionItemKind.Property - | Item.Event _ -> CompletionItemKind.Event - | Item.ILField _ - | Item.Value _ -> CompletionItemKind.Field - | Item.CustomOperation _ -> CompletionItemKind.CustomOperation - | _ -> CompletionItemKind.Other - - { ItemWithInst = item - MinorPriority = 0 - Kind = kind - IsOwnMember = false - Type = match ty with ValueSome x -> Some x | _ -> None - Unresolved = match assemblySymbol with ValueSome x -> Some x.UnresolvedSymbol | _ -> None } - - let DefaultCompletionItem item = CompletionItem ValueNone ValueNone item - - let getItem (x: ItemWithInst) = x.Item - let GetDeclaredItems (parseResultsOpt: FSharpParseFileResults option, lineStr: string, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, - filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, allSymbols: unit -> AssemblySymbol list) = - - // Are the last two chars (except whitespaces) = ".." - let isLikeRangeOp = - match FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1) with - | Some x when x >= 1 && lineStr.[x] = '.' && lineStr.[x - 1] = '.' -> true - | _ -> false - - // if last two chars are .. and we are not in range operator context - no completion - if isLikeRangeOp && not isInRangeOperator then None else - - // Try to use the exact results of name resolution during type checking to generate the results - // This is based on position (i.e. colAtEndOfNamesAndResidue). This is not used if a residueOpt is given. - let nameResItems = - match residueOpt with - | None -> GetPreciseItemsFromNameResolution(line, colAtEndOfNamesAndResidue, None, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) - | Some residue -> - // deals with cases when we have spaces between dot and\or identifier, like A . $ - // if this is our case - then we need to locate end position of the name skipping whitespaces - // this allows us to handle cases like: let x . $ = 1 - match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with - | Some p when lineStr.[p] = '.' -> - match FindFirstNonWhitespacePosition lineStr (p - 1) with - | Some colAtEndOfNames -> - let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based - GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) - | None -> NameResResult.Empty - | _ -> NameResResult.Empty - - // Normalize to form A.B.C.D where D is the residue. It may be empty for "A.B.C." - // residueOpt = Some when we are looking for the exact match - let plid, exactMatchResidueOpt = - match origLongIdentOpt, residueOpt with - | None, _ -> [], None - | Some(origLongIdent), Some _ -> origLongIdent, None - | Some(origLongIdent), None -> - System.Diagnostics.Debug.Assert(not (isNil origLongIdent), "origLongIdent is empty") - // note: as above, this happens when we are called for "precise" resolution - (F1 keyword, data tip etc..) - let plid, residue = List.frontAndBack origLongIdent - plid, Some residue - - let pos = mkPos line loc - let (nenv, ad), m = GetBestEnvForPos pos - - let getType() = - match NameResolution.TryToResolveLongIdentAsType ncenv nenv m plid with - | Some x -> tryDestAppTy g x - | None -> - match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with - | Some p when lineStr.[p] = '.' -> - match FindFirstNonWhitespacePosition lineStr (p - 1) with - | Some colAtEndOfNames -> - let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based - match TryGetTypeFromNameResolution(line, colAtEndOfNames, residueOpt, resolveOverloads) with - | Some x -> tryDestAppTy g x - | _ -> ValueNone - | None -> ValueNone - | _ -> ValueNone - - match nameResItems with - | NameResResult.TypecheckStaleAndTextChanged -> None // second-chance intellisense will try again - | NameResResult.Cancel(denv,m) -> Some([], denv, m) - | NameResResult.Members(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m)) -> - // lookup based on name resolution results successful - Some (items |> List.map (CompletionItem (getType()) ValueNone), denv, m) - | _ -> - match origLongIdentOpt with - | None -> None - | Some _ -> - - // Try to use the type of the expression on the left to help generate a completion list - let qualItems, thereIsADotInvolved = - match parseResultsOpt with - | None -> - // Note, you will get here if the 'reason' is not CompleteWord/MemberSelect/DisplayMemberList, as those are currently the - // only reasons we do a sync parse to have the most precise and likely-to-be-correct-and-up-to-date info. So for example, - // if you do QuickInfo hovering over A in "f(x).A()", you will only get a tip if typechecking has a name-resolution recorded - // for A, not if merely we know the capturedExpressionTyping of f(x) and you very recently typed ".A()" - in that case, - // you won't won't get a tip until the typechecking catches back up. - GetPreciseCompletionListFromExprTypingsResult.None, false - | Some parseResults -> - - match UntypedParseImpl.TryFindExpressionASTLeftOfDotLeftOfCursor(mkPos line colAtEndOfNamesAndResidue,parseResults.ParseTree) with - | Some(pos,_) -> - GetPreciseCompletionListFromExprTypings(parseResults, pos, filterCtors, hasTextChangedSinceLastTypecheck), true - | None -> - // Can get here in a case like: if "f xxx yyy" is legal, and we do "f xxx y" - // We have no interest in expression typings, those are only useful for dot-completion. We want to fallback - // to "Use an environment lookup as the last resort" below - GetPreciseCompletionListFromExprTypingsResult.None, false - - match qualItems,thereIsADotInvolved with - | GetPreciseCompletionListFromExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty), _ - // Initially we only use the expression typings when looking up, e.g. (expr).Nam or (expr).Name1.Nam - // These come through as an empty plid and residue "". Otherwise we try an environment lookup - // and then return to the qualItems. This is because the expression typings are a little inaccurate, primarily because - // it appears we're getting some typings recorded for non-atomic expressions like "f x" - when isNil plid -> - // lookup based on expression typings successful - Some (items |> List.map (CompletionItem (tryDestAppTy g ty) ValueNone), denv, m) - | GetPreciseCompletionListFromExprTypingsResult.NoneBecauseThereWereTypeErrors, _ -> - // There was an error, e.g. we have "." and there is an error determining the type of - // In this case, we don't want any of the fallback logic, rather, we want to produce zero results. - None - | GetPreciseCompletionListFromExprTypingsResult.NoneBecauseTypecheckIsStaleAndTextChanged, _ -> - // we want to report no result and let second-chance intellisense kick in - None - | _, true when isNil plid -> - // If the user just pressed '.' after an _expression_ (not a plid), it is never right to show environment-lookup top-level completions. - // The user might by typing quickly, and the LS didn't have an expression type right before the dot yet. - // Second-chance intellisense will bring up the correct list in a moment. - None - | _ -> - // Use an environment lookup as the last resort - let envItems, denv, m = GetEnvironmentLookupResolutions(nenv, ad, m, plid, filterCtors, residueOpt.IsSome) - - let envResult = - match nameResItems, (envItems, denv, m), qualItems with - - // First, use unfiltered name resolution items, if they're not empty - | NameResResult.Members(items, denv, m), _, _ when not (isNil items) -> - // lookup based on name resolution results successful - ValueSome(items |> List.map (CompletionItem (getType()) ValueNone), denv, m) - - // If we have nonempty items from environment that were resolved from a type, then use them... - // (that's better than the next case - here we'd return 'int' as a type) - | _, FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), _ when not (isNil items) -> - // lookup based on name and environment successful - ValueSome(items |> List.map (CompletionItem (getType()) ValueNone), denv, m) - - // Try again with the qualItems - | _, _, GetPreciseCompletionListFromExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty) -> - ValueSome(items |> List.map (CompletionItem (tryDestAppTy g ty) ValueNone), denv, m) - - | _ -> ValueNone - - let globalResult = - match origLongIdentOpt with - | None | Some [] -> - let globalItems = - allSymbols() - |> List.filter (fun x -> not x.Symbol.IsExplicitlySuppressed) - |> List.filter (fun x -> - match x.Symbol with - | :? FSharpMemberOrFunctionOrValue as m when m.IsConstructor && filterCtors = ResolveTypeNamesToTypeRefs -> false - | _ -> true) - - let getItem (x: AssemblySymbol) = x.Symbol.Item - - match globalItems, denv, m with - | FilterRelevantItems getItem exactMatchResidueOpt (globalItemsFiltered, denv, m) when not (isNil globalItemsFiltered) -> - globalItemsFiltered - |> List.map(fun globalItem -> CompletionItem (getType()) (ValueSome globalItem) (ItemWithNoInst globalItem.Symbol.Item)) - |> fun r -> ValueSome(r, denv, m) - | _ -> ValueNone - | _ -> ValueNone // do not return unresolved items after dot - - match envResult, globalResult with - | ValueSome (items, denv, m), ValueSome (gItems,_,_) -> Some (items @ gItems, denv, m) - | ValueSome x, ValueNone -> Some x - | ValueNone, ValueSome y -> Some y - | ValueNone, ValueNone -> None - - - let toCompletionItems (items: ItemWithInst list, denv: DisplayEnv, m: range ) = - items |> List.map DefaultCompletionItem, denv, m - - /// Get the auto-complete items at a particular location. - let GetDeclItemsForNamesAtPosition(ctok: CompilationThreadToken, parseResultsOpt: FSharpParseFileResults option, origLongIdentOpt: string list option, - residueOpt:string option, lastDotPos: int option, line:int, lineStr:string, colAtEndOfNamesAndResidue, filterCtors, resolveOverloads, - getAllSymbols: unit -> AssemblySymbol list, hasTextChangedSinceLastTypecheck: (obj * range -> bool)) - : (CompletionItem list * DisplayEnv * CompletionContext option * range) option = - RequireCompilationThread ctok // the operations in this method need the reactor thread - - let loc = - match colAtEndOfNamesAndResidue with - | pastEndOfLine when pastEndOfLine >= lineStr.Length -> lineStr.Length - | atDot when lineStr.[atDot] = '.' -> atDot + 1 - | atStart when atStart = 0 -> 0 - | otherwise -> otherwise - 1 - - // Look for a "special" completion context - let completionContext = - parseResultsOpt - |> Option.bind (fun x -> x.ParseTree) - |> Option.bind (fun parseTree -> UntypedParseImpl.TryGetCompletionContext(mkPos line colAtEndOfNamesAndResidue, parseTree, lineStr)) - - let res = - match completionContext with - // Invalid completion locations - | Some CompletionContext.Invalid -> None - - // Completion at 'inherit C(...)" - | Some (CompletionContext.Inherit(InheritanceContext.Class, (plid, _))) -> - GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) - |> FilterRelevantItemsBy getItem None (getItem >> GetBaseClassCandidates) - |> Option.map toCompletionItems - - // Completion at 'interface ..." - | Some (CompletionContext.Inherit(InheritanceContext.Interface, (plid, _))) -> - GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) - |> FilterRelevantItemsBy getItem None (getItem >> GetInterfaceCandidates) - |> Option.map toCompletionItems - - // Completion at 'implement ..." - | Some (CompletionContext.Inherit(InheritanceContext.Unknown, (plid, _))) -> - GetEnvironmentLookupResolutionsAtPosition(mkPos line loc, plid, filterCtors, false) - |> FilterRelevantItemsBy getItem None (getItem >> (fun t -> GetBaseClassCandidates t || GetInterfaceCandidates t)) - |> Option.map toCompletionItems - - // Completion at ' { XXX = ... } " - | Some(CompletionContext.RecordField(RecordContext.New(plid, _))) -> - // { x. } can be either record construction or computation expression. Try to get all visible record fields first - match GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, plid) |> toCompletionItems with - | [],_,_ -> - // no record fields found, return completion list as if we were outside any computation expression - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, false, fun() -> []) - | result -> Some(result) - - // Completion at ' { XXX = ... with ... } " - | Some(CompletionContext.RecordField(RecordContext.CopyOnUpdate(r, (plid, _)))) -> - match GetRecdFieldsForExpr(r) with - | None -> - Some (GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, plid)) - |> Option.map toCompletionItems - | Some (items, denv, m) -> - Some (List.map ItemWithNoInst items, denv, m) - |> Option.map toCompletionItems - - // Completion at ' { XXX = ... with ... } " - | Some(CompletionContext.RecordField(RecordContext.Constructor(typeName))) -> - Some(GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, [typeName])) - |> Option.map toCompletionItems - - // Completion at ' SomeMethod( ... ) ' with named arguments - | Some(CompletionContext.ParameterList (endPos, fields)) -> - let results = GetNamedParametersAndSettableFields endPos hasTextChangedSinceLastTypecheck - - let declaredItems = - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, - hasTextChangedSinceLastTypecheck, false, getAllSymbols) - - match results with - | NameResResult.Members(items, denv, m) -> - let filtered = - items - |> RemoveDuplicateItems g - |> RemoveExplicitlySuppressed g - |> List.filter (fun item -> not (fields.Contains item.Item.DisplayName)) - |> List.map (fun item -> - { ItemWithInst = item - Kind = CompletionItemKind.Argument - MinorPriority = 0 - IsOwnMember = false - Type = None - Unresolved = None }) - match declaredItems with - | None -> Some (toCompletionItems (items, denv, m)) - | Some (declItems, declaredDisplayEnv, declaredRange) -> Some (filtered @ declItems, declaredDisplayEnv, declaredRange) - | _ -> declaredItems - - | Some(CompletionContext.AttributeApplication) -> - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) - |> Option.map (fun (items, denv, m) -> - items - |> List.filter (fun cItem -> - match cItem.Item with - | Item.ModuleOrNamespaces _ -> true - | _ when IsAttribute infoReader cItem.Item -> true - | _ -> false), denv, m) - - | Some(CompletionContext.OpenDeclaration) -> - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) - |> Option.map (fun (items, denv, m) -> - items |> List.filter (fun x -> match x.Item with Item.ModuleOrNamespaces _ -> true | _ -> false), denv, m) - - // Completion at '(x: ...)" - | Some (CompletionContext.PatternType) -> - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) - |> Option.map (fun (items, denv, m) -> - items - |> List.filter (fun cItem -> - match cItem.Item with - | Item.ModuleOrNamespaces _ - | Item.Types _ - | Item.UnqualifiedType _ - | Item.ExnCase _ -> true - | _ -> false), denv, m) - - // Other completions - | cc -> - match residueOpt |> Option.bind Seq.tryHead with - | Some ''' -> - // The last token in - // let x = 'E - // is Ident with text "'E", however it's either unfinished char literal or generic parameter. - // We should not provide any completion in the former case, and we don't provide it for the latter one for now - // because providing generic parameters list is context aware, which we don't have here (yet). - None - | _ -> - let isInRangeOperator = (match cc with Some (CompletionContext.RangeOperator) -> true | _ -> false) - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, getAllSymbols) - - res |> Option.map (fun (items, denv, m) -> items, denv, completionContext, m) - - /// Return 'false' if this is not a completion item valid in an interface file. - let IsValidSignatureFileItem item = - match item with - | Item.Types _ | Item.ModuleOrNamespaces _ -> true - | _ -> false - - /// Find the most precise display context for the given line and column. - member __.GetBestDisplayEnvForPos cursorPos = GetBestEnvForPos cursorPos - - member __.GetVisibleNamespacesAndModulesAtPosition(cursorPos: pos) : ModuleOrNamespaceRef list = - let (nenv, ad), m = GetBestEnvForPos cursorPos - NameResolution.GetVisibleNamespacesAndModulesAtPoint ncenv nenv m ad - - /// Determines if a long ident is resolvable at a specific point. - member __.IsRelativeNameResolvable(cursorPos: pos, plid: string list, item: Item) : bool = - ErrorScope.Protect - Range.range0 - (fun () -> - /// Find items in the best naming environment. - let (nenv, ad), m = GetBestEnvForPos cursorPos - NameResolution.IsItemResolvable ncenv nenv m ad plid item) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in IsRelativeNameResolvable: '%s'" msg) - false) - - /// Determines if a long ident is resolvable at a specific point. - member scope.IsRelativeNameResolvableFromSymbol(cursorPos: pos, plid: string list, symbol: FSharpSymbol) : bool = - scope.IsRelativeNameResolvable(cursorPos, plid, symbol.Item) - - /// Get the auto-complete items at a location - member __.GetDeclarations (ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck) = - let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllEntities, hasTextChangedSinceLastTypecheck) with - | None -> FSharpDeclarationListInfo.Empty - | Some (items, denv, ctx, m) -> - let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items - let getAccessibility item = FSharpSymbol.GetAccessibility (FSharpSymbol.Create(cenv, item)) - let currentNamespaceOrModule = - parseResultsOpt - |> Option.bind (fun x -> x.ParseTree) - |> Option.map (fun parsedInput -> UntypedParseImpl.GetFullNameOfSmallestModuleOrNamespaceAtPoint(parsedInput, mkPos line 0)) - let isAttributeApplication = ctx = Some CompletionContext.AttributeApplication - FSharpDeclarationListInfo.Create(infoReader,m,denv,getAccessibility,items,reactorOps,currentNamespaceOrModule,isAttributeApplication)) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarations: '%s'" msg) - FSharpDeclarationListInfo.Error msg) - - /// Get the symbols for auto-complete items at a location - member __.GetDeclarationListSymbols (ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck) = - let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllEntities, hasTextChangedSinceLastTypecheck) with - | None -> List.Empty - | Some (items, denv, _, m) -> - let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items - - //do filtering like Declarationset - let items = items |> RemoveExplicitlySuppressedCompletionItems g - - // Sort by name. For things with the same name, - // - show types with fewer generic parameters first - // - show types before over other related items - they usually have very useful XmlDocs - let items = - items |> List.sortBy (fun d -> - let n = - match d.Item with - | Item.Types (_,(TType_app(tcref,_) :: _)) -> 1 + tcref.TyparsNoRange.Length - // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name - | Item.FakeInterfaceCtor (TType_app(tcref,_)) - | Item.DelegateCtor (TType_app(tcref,_)) -> 1000 + tcref.TyparsNoRange.Length - // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name - | Item.CtorGroup (_, (cinfo :: _)) -> 1000 + 10 * cinfo.DeclaringTyconRef.TyparsNoRange.Length - | _ -> 0 - (d.Item.DisplayName,n)) - - // Remove all duplicates. We've put the types first, so this removes the DelegateCtor and DefaultStructCtor's. - let items = items |> RemoveDuplicateCompletionItems g - - // Group by compiled name for types, display name for functions - // (We don't want types with the same display name to be grouped as overloads) - let items = - items |> List.groupBy (fun d -> - match d.Item with - | Item.Types (_,(TType_app(tcref,_) :: _)) - | Item.ExnCase tcref -> tcref.LogicalName - | Item.UnqualifiedType(tcref :: _) - | Item.FakeInterfaceCtor (TType_app(tcref,_)) - | Item.DelegateCtor (TType_app(tcref,_)) -> tcref.CompiledName - | Item.CtorGroup (_, (cinfo :: _)) -> - cinfo.ApparentEnclosingTyconRef.CompiledName - | _ -> d.Item.DisplayName) - - // Filter out operators (and list) - let items = - // Check whether this item looks like an operator. - let isOpItem(nm, item: CompletionItem list) = - match item |> List.map (fun x -> x.Item) with - | [Item.Value _] - | [Item.MethodGroup(_,[_],_)] -> IsOperatorName nm - | [Item.UnionCase _] -> IsOperatorName nm - | _ -> false - - let isFSharpList nm = (nm = "[]") // list shows up as a Type and a UnionCase, only such entity with a symbolic name, but want to filter out of intellisense - - items |> List.filter (fun (nm,items) -> not (isOpItem(nm,items)) && not(isFSharpList nm)) - - let items = - // Filter out duplicate names - items |> List.map (fun (_nm,itemsWithSameName) -> - match itemsWithSameName with - | [] -> failwith "Unexpected empty bag" - | items -> - items - |> List.map (fun item -> let symbol = FSharpSymbol.Create(cenv, item.Item) - FSharpSymbolUse(g, denv, symbol, ItemOccurence.Use, m))) - - //end filtering - items) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarationListSymbols: '%s'" msg) - []) - - /// Get the "reference resolution" tooltip for at a location - member __.GetReferenceResolutionStructuredToolTipText(ctok, line,col) = - - RequireCompilationThread ctok // the operations in this method need the reactor thread but the reasons why are not yet grounded - - let pos = mkPos line col - let isPosMatch(pos, ar:AssemblyReference) : bool = - let isRangeMatch = (Range.rangeContainsPos ar.Range pos) - let isNotSpecialRange = not (Range.equals ar.Range rangeStartup) && not (Range.equals ar.Range range0) && not (Range.equals ar.Range rangeCmdArgs) - let isMatch = isRangeMatch && isNotSpecialRange - isMatch - - let dataTipOfReferences() = - let matches = - match loadClosure with - | None -> [] - | Some(loadClosure) -> - loadClosure.References - |> List.map snd - |> List.concat - |> List.filter(fun ar->isPosMatch(pos, ar.originalReference)) - - match matches with - | resolved :: _ // Take the first seen - | [resolved] -> - let tip = wordL (TaggedTextOps.tagStringLiteral((resolved.prepareToolTip ()).TrimEnd([|'\n'|]))) - FSharpStructuredToolTipText.FSharpToolTipText [FSharpStructuredToolTipElement.Single(tip, FSharpXmlDoc.None)] - - | [] -> FSharpStructuredToolTipText.FSharpToolTipText [] - - ErrorScope.Protect Range.range0 - dataTipOfReferences - (fun err -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetReferenceResolutionStructuredToolTipText: '%s'" err) - FSharpToolTipText [FSharpStructuredToolTipElement.CompositionError err]) - - // GetToolTipText: return the "pop up" (or "Quick Info") text given a certain context. - member __.GetStructuredToolTipText(ctok, line, lineStr, colAtEndOfNames, names) = - let Compute() = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None,Some(names),None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []),fun _ -> false) with - | None -> FSharpToolTipText [] - | Some(items, denv, _, m) -> - FSharpToolTipText(items |> List.map (fun x -> FormatStructuredDescriptionOfItem false infoReader m denv x.ItemWithInst))) - (fun err -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetStructuredToolTipText: '%s'" err) - FSharpToolTipText [FSharpStructuredToolTipElement.CompositionError err]) - - // See devdiv bug 646520 for rationale behind truncating and caching these quick infos (they can be big!) - let key = line,colAtEndOfNames,lineStr - match getToolTipTextCache.TryGet (ctok, key) with - | Some res -> res - | None -> - let res = Compute() - getToolTipTextCache.Put(ctok, key,res) - res - - member __.GetF1Keyword (ctok, line, lineStr, colAtEndOfNames, names) : string option = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None, Some names, None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []), fun _ -> false) with // F1 Keywords do not distinguish between overloads - | None -> None - | Some (items: CompletionItem list, _,_, _) -> - match items with - | [] -> None - | [item] -> - GetF1Keyword g item.Item - | _ -> - // handle new Type() - let allTypes, constr, ty = - List.fold - (fun (allTypes,constr,ty) (item: CompletionItem) -> - match item.Item, constr, ty with - | (Item.Types _) as t, _, None -> allTypes, constr, Some t - | (Item.Types _), _, _ -> allTypes, constr, ty - | (Item.CtorGroup _), None, _ -> allTypes, Some item.Item, ty - | _ -> false, None, None) - (true,None,None) items - match allTypes, constr, ty with - | true, Some (Item.CtorGroup(_, _) as item), _ - -> GetF1Keyword g item - | true, _, Some ty - -> GetF1Keyword g ty - | _ -> None - ) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetF1Keyword: '%s'" msg) - None) - - member __.GetMethods (ctok, line, lineStr, colAtEndOfNames, namesOpt) = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None,namesOpt,None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.No,(fun() -> []),fun _ -> false) with - | None -> FSharpMethodGroup("",[| |]) - | Some (items, denv, _, m) -> - // GetDeclItemsForNamesAtPosition returns Items.Types and Item.CtorGroup for `new T(|)`, - // the Item.Types is not needed here as it duplicates (at best) parameterless ctor. - let ctors = items |> List.filter (fun x -> match x.Item with Item.CtorGroup _ -> true | _ -> false) - let items = - match ctors with - | [] -> items - | ctors -> ctors - FSharpMethodGroup.Create(infoReader, m, denv, items |> List.map (fun x -> x.ItemWithInst))) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetMethods: '%s'" msg) - FSharpMethodGroup(msg,[| |])) - - member __.GetMethodsAsSymbols (ctok, line, lineStr, colAtEndOfNames, names) = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None,line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []),fun _ -> false) with - | None | Some ([],_,_,_) -> None - | Some (items, denv, _, m) -> - let allItems = items |> List.collect (fun item -> SymbolHelpers.FlattenItems g m item.Item) - let symbols = allItems |> List.map (fun item -> FSharpSymbol.Create(cenv, item)) - Some (symbols, denv, m) - ) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetMethodsAsSymbols: '%s'" msg) - None) - - member __.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag) = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with - | None - | Some ([], _, _, _) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "") - | Some (item :: _, _, _, _) -> - let getTypeVarNames (ilinfo: ILMethInfo) = - let classTypeParams = ilinfo.DeclaringTyconRef.ILTyconRawMetadata.GenericParams |> List.map (fun paramDef -> paramDef.Name) - let methodTypeParams = ilinfo.FormalMethodTypars |> List.map (fun ty -> ty.Name) - classTypeParams @ methodTypeParams |> Array.ofList - - let result = - match item.Item with - | Item.CtorGroup (_, (ILMeth (_,ilinfo,_)) :: _) -> - match ilinfo.MetadataScope with - | ILScopeRef.Assembly assemblyRef -> - let typeVarNames = getTypeVarNames ilinfo - ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes - |> Option.map (fun args -> - let externalSym = ExternalSymbol.Constructor (ilinfo.ILMethodRef.DeclaringTypeRef.FullName, args) - FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) - | _ -> None - - | Item.MethodGroup (name, (ILMeth (_,ilinfo,_)) :: _, _) -> - match ilinfo.MetadataScope with - | ILScopeRef.Assembly assemblyRef -> - let typeVarNames = getTypeVarNames ilinfo - ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes - |> Option.map (fun args -> - let externalSym = ExternalSymbol.Method (ilinfo.ILMethodRef.DeclaringTypeRef.FullName, name, args, ilinfo.ILMethodRef.GenericArity) - FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) - | _ -> None - - | Item.Property (name, ILProp propInfo :: _) -> - let methInfo = - if propInfo.HasGetter then Some propInfo.GetterMethod - elif propInfo.HasSetter then Some propInfo.SetterMethod - else None - - match methInfo with - | Some methInfo -> - match methInfo.MetadataScope with - | ILScopeRef.Assembly assemblyRef -> - let externalSym = ExternalSymbol.Property (methInfo.ILMethodRef.DeclaringTypeRef.FullName, name) - Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) - | _ -> None - | None -> None - - | Item.ILField (ILFieldInfo (typeInfo, fieldDef)) when not typeInfo.TyconRefOfRawMetadata.IsLocalRef -> - match typeInfo.ILScopeRef with - | ILScopeRef.Assembly assemblyRef -> - let externalSym = ExternalSymbol.Field (typeInfo.ILTypeRef.FullName, fieldDef.Name) - Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) - | _ -> None - - | Item.Event (ILEvent (ILEventInfo (typeInfo, eventDef))) when not typeInfo.TyconRefOfRawMetadata.IsLocalRef -> - match typeInfo.ILScopeRef with - | ILScopeRef.Assembly assemblyRef -> - let externalSym = ExternalSymbol.Event (typeInfo.ILTypeRef.FullName, eventDef.Name) - Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, externalSym)) - | _ -> None - - | Item.ImplicitOp(_, {contents = Some(TraitConstraintSln.FSMethSln(_, _vref, _))}) -> - //Item.Value(vref) - None - - | Item.Types (_, TType_app (tr, _) :: _) when tr.IsLocalRef && tr.IsTypeAbbrev -> None - - | Item.Types (_, [ AppTy g (tr, _) ]) when not tr.IsLocalRef -> - match tr.TypeReprInfo, tr.PublicPath with - | TILObjectRepr(TILObjectReprData (ILScopeRef.Assembly assemblyRef, _, _)), Some (PubPath parts) -> - let fullName = parts |> String.concat "." - Some (FSharpFindDeclResult.ExternalDecl (assemblyRef.Name, ExternalSymbol.Type fullName)) - | _ -> None - | _ -> None - match result with - | Some x -> x - | None -> - match rangeOfItem g preferFlag item.Item with - | Some itemRange -> - let projectDir = Filename.directoryName (if projectFileName = "" then mainInputFileName else projectFileName) - let range = fileNameOfItem g (Some projectDir) itemRange item.Item - mkRange range itemRange.Start itemRange.End - |> FSharpFindDeclResult.DeclFound - | None -> - match item.Item with -#if !NO_EXTENSIONTYPING -// provided items may have TypeProviderDefinitionLocationAttribute that binds them to some location - | Item.CtorGroup (name, ProvidedMeth (_) :: _ ) - | Item.MethodGroup(name, ProvidedMeth (_) :: _, _) - | Item.Property (name, ProvidedProp (_) :: _ ) -> FSharpFindDeclFailureReason.ProvidedMember name - | Item.Event ( ProvidedEvent(_) as e ) -> FSharpFindDeclFailureReason.ProvidedMember e.EventName - | Item.ILField ( ProvidedField(_) as f ) -> FSharpFindDeclFailureReason.ProvidedMember f.FieldName - | SymbolHelpers.ItemIsProvidedType g (tcref) -> FSharpFindDeclFailureReason.ProvidedType tcref.DisplayName -#endif - | _ -> FSharpFindDeclFailureReason.Unknown "" - |> FSharpFindDeclResult.DeclNotFound - ) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarationLocation: '%s'" msg) - FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown msg)) - - member __.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) = - ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with - | None | Some ([], _, _, _) -> None - | Some (item :: _, denv, _, m) -> - let symbol = FSharpSymbol.Create(cenv, item.Item) - Some (symbol, denv, m) - ) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetSymbolUseAtLocation: '%s'" msg) - None) - - member __.PartialAssemblySignatureForFile = - FSharpAssemblySignature(g, thisCcu, ccuSigForFile, tcImports, None, ccuSigForFile) - - member __.AccessRights = tcAccessRights - - member __.GetReferencedAssemblies() = - [ for x in tcImports.GetImportedAssemblies() do - yield FSharpAssembly(g, tcImports, x.FSharpViewOfMetadata) ] - - member __.GetFormatSpecifierLocationsAndArity() = - sSymbolUses.GetFormatSpecifierLocationsAndArity() - - member __.GetSemanticClassification(range: range option) : (range * SemanticClassificationType) [] = - ErrorScope.Protect Range.range0 - (fun () -> - let (|LegitTypeOccurence|_|) = function - | ItemOccurence.UseInType - | ItemOccurence.UseInAttribute - | ItemOccurence.Use _ - | ItemOccurence.Binding _ - | ItemOccurence.Pattern _ -> Some() - | _ -> None - - let (|OptionalArgumentAttribute|_|) ttype = - match ttype with - | TType.TType_app(tref, _) when tref.Stamp = g.attrib_OptionalArgumentAttribute.TyconRef.Stamp -> Some() - | _ -> None - - let (|KeywordIntrinsicValue|_|) (vref: ValRef) = - if valRefEq g g.raise_vref vref || - valRefEq g g.reraise_vref vref || - valRefEq g g.typeof_vref vref || - valRefEq g g.typedefof_vref vref || - valRefEq g g.sizeof_vref vref - // TODO uncomment this after `nameof` operator is implemented - // || valRefEq g g.nameof_vref vref - then Some() - else None - - let (|EnumCaseFieldInfo|_|) (rfinfo : RecdFieldInfo) = - match rfinfo.TyconRef.TypeReprInfo with - | TFSharpObjectRepr x -> - match x.fsobjmodel_kind with - | TTyconEnum -> Some () - | _ -> None - | _ -> None - - let resolutions = - match range with - | Some range -> - sResolutions.CapturedNameResolutions - |> Seq.filter (fun cnr -> rangeContainsPos range cnr.Range.Start || rangeContainsPos range cnr.Range.End) - | None -> - sResolutions.CapturedNameResolutions :> seq<_> - - let isDisposableTy (ty: TType) = - protectAssemblyExplorationNoReraise false false (fun () -> Infos.ExistsHeadTypeInEntireHierarchy g amap range0 ty g.tcref_System_IDisposable) - - let isStructTyconRef (tyconRef: TyconRef) = - let ty = generalizedTyconRef tyconRef - let underlyingTy = stripTyEqnsAndMeasureEqns g ty - isStructTy g underlyingTy - - let isValRefMutable (vref: ValRef) = - // Mutable values, ref cells, and non-inref byrefs are mutable. - vref.IsMutable - || Tastops.isRefCellTy g vref.Type - || (Tastops.isByrefTy g vref.Type && not (Tastops.isInByrefTy g vref.Type)) - - let isRecdFieldMutable (rfinfo: RecdFieldInfo) = - (rfinfo.RecdField.IsMutable && rfinfo.LiteralValue.IsNone) - || Tastops.isRefCellTy g rfinfo.RecdField.FormalType - - resolutions - |> Seq.choose (fun cnr -> - match cnr with - // 'seq' in 'seq { ... }' gets colored as keywords - | CNR(_, (Item.Value vref), ItemOccurence.Use, _, _, _, m) when valRefEq g g.seq_vref vref -> - Some (m, SemanticClassificationType.ComputationExpression) - | CNR(_, (Item.Value vref), _, _, _, _, m) when isValRefMutable vref -> - Some (m, SemanticClassificationType.MutableVar) - | CNR(_, Item.Value KeywordIntrinsicValue, ItemOccurence.Use, _, _, _, m) -> - Some (m, SemanticClassificationType.IntrinsicFunction) - | CNR(_, (Item.Value vref), _, _, _, _, m) when isFunction g vref.Type -> - if valRefEq g g.range_op_vref vref || valRefEq g g.range_step_op_vref vref then - None - elif vref.IsPropertyGetterMethod || vref.IsPropertySetterMethod then - Some (m, SemanticClassificationType.Property) - elif IsOperatorName vref.DisplayName then - Some (m, SemanticClassificationType.Operator) - else - Some (m, SemanticClassificationType.Function) - | CNR(_, Item.RecdField rfinfo, _, _, _, _, m) when isRecdFieldMutable rfinfo -> - Some (m, SemanticClassificationType.MutableVar) - | CNR(_, Item.RecdField rfinfo, _, _, _, _, m) when isFunction g rfinfo.FieldType -> - Some (m, SemanticClassificationType.Function) - | CNR(_, Item.RecdField EnumCaseFieldInfo, _, _, _, _, m) -> - Some (m, SemanticClassificationType.Enumeration) - | CNR(_, Item.MethodGroup _, _, _, _, _, m) -> - Some (m, SemanticClassificationType.Function) - // custom builders, custom operations get colored as keywords - | CNR(_, (Item.CustomBuilder _ | Item.CustomOperation _), ItemOccurence.Use, _, _, _, m) -> - Some (m, SemanticClassificationType.ComputationExpression) - // types get colored as types when they occur in syntactic types or custom attributes - // typevariables get colored as types when they occur in syntactic types custom builders, custom operations get colored as keywords - | CNR(_, Item.Types (_, [OptionalArgumentAttribute]), LegitTypeOccurence, _, _, _, _) -> None - | CNR(_, Item.CtorGroup(_, [MethInfo.FSMeth(_, OptionalArgumentAttribute, _, _)]), LegitTypeOccurence, _, _, _, _) -> None - | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists (isInterfaceTy g) -> - Some (m, SemanticClassificationType.Interface) - | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists (isStructTy g) -> - Some (m, SemanticClassificationType.ValueType) - | CNR(_, Item.Types(_, TType_app(tyconRef, TType_measure _ :: _) :: _), LegitTypeOccurence, _, _, _, m) when isStructTyconRef tyconRef -> - Some (m, SemanticClassificationType.ValueType) - | CNR(_, Item.Types(_, types), LegitTypeOccurence, _, _, _, m) when types |> List.exists isDisposableTy -> - Some (m, SemanticClassificationType.Disposable) - | CNR(_, Item.Types _, LegitTypeOccurence, _, _, _, m) -> - Some (m, SemanticClassificationType.ReferenceType) - | CNR(_, (Item.TypeVar _ ), LegitTypeOccurence, _, _, _, m) -> - Some (m, SemanticClassificationType.TypeArgument) - | CNR(_, Item.UnqualifiedType tyconRefs, LegitTypeOccurence, _, _, _, m) -> - if tyconRefs |> List.exists (fun tyconRef -> tyconRef.Deref.IsStructOrEnumTycon) then - Some (m, SemanticClassificationType.ValueType) - else Some (m, SemanticClassificationType.ReferenceType) - | CNR(_, Item.CtorGroup(_, minfos), LegitTypeOccurence, _, _, _, m) -> - if minfos |> List.exists (fun minfo -> isStructTy g minfo.ApparentEnclosingType) then - Some (m, SemanticClassificationType.ValueType) - else Some (m, SemanticClassificationType.ReferenceType) - | CNR(_, Item.ExnCase _, LegitTypeOccurence, _, _, _, m) -> - Some (m, SemanticClassificationType.ReferenceType) - | CNR(_, Item.ModuleOrNamespaces refs, LegitTypeOccurence, _, _, _, m) when refs |> List.exists (fun x -> x.IsModule) -> - Some (m, SemanticClassificationType.Module) - | CNR(_, (Item.ActivePatternCase _ | Item.UnionCase _ | Item.ActivePatternResult _), _, _, _, _, m) -> - Some (m, SemanticClassificationType.UnionCase) - | _ -> None) - |> Seq.toArray - |> Array.append (sSymbolUses.GetFormatSpecifierLocationsAndArity() |> Array.map (fun m -> fst m, SemanticClassificationType.Printf)) - ) - (fun msg -> - Trace.TraceInformation(sprintf "FCS: recovering from error in GetSemanticClassification: '%s'" msg) - Array.empty) - - /// The resolutions in the file - member __.ScopeResolutions = sResolutions - - /// The uses of symbols in the analyzed file - member __.ScopeSymbolUses = sSymbolUses - - member __.TcGlobals = g - - member __.TcImports = tcImports - - /// The inferred signature of the file - member __.CcuSigForFile = ccuSigForFile - - /// The assembly being analyzed - member __.ThisCcu = thisCcu - - member __.ImplementationFile = implFileOpt - - /// All open declarations in the file, including auto open modules - member __.OpenDeclarations = openDeclarations - - member __.SymbolEnv = cenv - - override __.ToString() = "TypeCheckInfo(" + mainInputFileName + ")" - -type FSharpParsingOptions = - { SourceFiles: string [] - ConditionalCompilationDefines: string list - ErrorSeverityOptions: FSharpErrorSeverityOptions - IsInteractive: bool - LightSyntax: bool option - CompilingFsLib: bool - IsExe: bool } - - member x.LastFileName = - Debug.Assert(not (Array.isEmpty x.SourceFiles), "Parsing options don't contain any file") - Array.last x.SourceFiles - - static member Default = - { SourceFiles = Array.empty - ConditionalCompilationDefines = [] - ErrorSeverityOptions = FSharpErrorSeverityOptions.Default - IsInteractive = false - LightSyntax = None - CompilingFsLib = false - IsExe = false } - - static member FromTcConfig(tcConfig: TcConfig, sourceFiles, isInteractive: bool) = - { SourceFiles = sourceFiles - ConditionalCompilationDefines = tcConfig.conditionalCompilationDefines - ErrorSeverityOptions = tcConfig.errorSeverityOptions - IsInteractive = isInteractive - LightSyntax = tcConfig.light - CompilingFsLib = tcConfig.compilingFslib - IsExe = tcConfig.target.IsExe } - - static member FromTcConfigBuidler(tcConfigB: TcConfigBuilder, sourceFiles, isInteractive: bool) = - { - SourceFiles = sourceFiles - ConditionalCompilationDefines = tcConfigB.conditionalCompilationDefines - ErrorSeverityOptions = tcConfigB.errorSeverityOptions - IsInteractive = isInteractive - LightSyntax = tcConfigB.light - CompilingFsLib = tcConfigB.compilingFslib - IsExe = tcConfigB.target.IsExe - } - -module internal Parser = - - /// Error handler for parsing & type checking while processing a single file - type ErrorHandler(reportErrors, mainInputFileName, errorSeverityOptions: FSharpErrorSeverityOptions, sourceText: ISourceText, suggestNamesForErrors: bool) = - let mutable options = errorSeverityOptions - let errorsAndWarningsCollector = new ResizeArray<_>() - let mutable errorCount = 0 - - // We'll need number of lines for adjusting error messages at EOF - let fileInfo = sourceText.GetLastCharacterPosition() - - // This function gets called whenever an error happens during parsing or checking - let diagnosticSink sev (exn: PhasedDiagnostic) = - // Sanity check here. The phase of an error should be in a phase known to the language service. - let exn = - if not(exn.IsPhaseInCompile()) then - // Reaching this point means that the error would be sticky if we let it prop up to the language service. - // Assert and recover by replacing phase with one known to the language service. - Trace.TraceInformation(sprintf "The subcategory '%s' seen in an error should not be seen by the language service" (exn.Subcategory())) - { exn with Phase = BuildPhase.TypeCheck } - else exn - if reportErrors then - let report exn = - for ei in ErrorHelpers.ReportError (options, false, mainInputFileName, fileInfo, (exn, sev), suggestNamesForErrors) do - errorsAndWarningsCollector.Add ei - if sev = FSharpErrorSeverity.Error then - errorCount <- errorCount + 1 - - match exn with -#if !NO_EXTENSIONTYPING - | { Exception = (:? TypeProviderError as tpe) } -> tpe.Iter(fun e -> report { exn with Exception = e }) -#endif - | e -> report e - - let errorLogger = - { new ErrorLogger("ErrorHandler") with - member x.DiagnosticSink (exn, isError) = diagnosticSink (if isError then FSharpErrorSeverity.Error else FSharpErrorSeverity.Warning) exn - member x.ErrorCount = errorCount } - - // Public members - member x.ErrorLogger = errorLogger - member x.CollectedDiagnostics = errorsAndWarningsCollector.ToArray() - member x.ErrorCount = errorCount - member x.ErrorSeverityOptions with set opts = options <- opts - member x.AnyErrors = errorCount > 0 - - let getLightSyntaxStatus fileName options = - let lower = String.lowercase fileName - let lightOnByDefault = List.exists (Filename.checkSuffix lower) FSharpLightSyntaxFileSuffixes - let lightSyntaxStatus = if lightOnByDefault then (options.LightSyntax <> Some false) else (options.LightSyntax = Some true) - LightSyntaxStatus(lightSyntaxStatus, true) - - let createLexerFunction fileName options lexbuf (errHandler: ErrorHandler) = - let lightSyntaxStatus = getLightSyntaxStatus fileName options - - // If we're editing a script then we define INTERACTIVE otherwise COMPILED. - // Since this parsing for intellisense we always define EDITING. - let defines = (SourceFileImpl.AdditionalDefinesForUseInEditor options.IsInteractive) @ options.ConditionalCompilationDefines - - // Note: we don't really attempt to intern strings across a large scope. - let lexResourceManager = new Lexhelp.LexResourceManager() - - // When analyzing files using ParseOneFile, i.e. for the use of editing clients, we do not apply line directives. - // TODO(pathmap): expose PathMap on the service API, and thread it through here - let lexargs = mkLexargs(fileName, defines, lightSyntaxStatus, lexResourceManager, ref [], errHandler.ErrorLogger, PathMap.empty) - let lexargs = { lexargs with applyLineDirectives = false } - - let tokenizer = LexFilter.LexFilter(lightSyntaxStatus, options.CompilingFsLib, Lexer.token lexargs true, lexbuf) - tokenizer.Lexer - - let createLexbuf sourceText = - UnicodeLexing.SourceTextAsLexbuf(sourceText) - - let matchBraces(sourceText, fileName, options: FSharpParsingOptions, userOpName: string, suggestNamesForErrors: bool) = - let delayedLogger = CapturingErrorLogger("matchBraces") - use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _ -> delayedLogger) - use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse - - Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "matchBraces", fileName) - - // Make sure there is an ErrorLogger installed whenever we do stuff that might record errors, even if we ultimately ignore the errors - let delayedLogger = CapturingErrorLogger("matchBraces") - use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _ -> delayedLogger) - use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse - - let matchingBraces = new ResizeArray<_>() - Lexhelp.usingLexbufForParsing(createLexbuf sourceText, fileName) (fun lexbuf -> - let errHandler = ErrorHandler(false, fileName, options.ErrorSeverityOptions, sourceText, suggestNamesForErrors) - let lexfun = createLexerFunction fileName options lexbuf errHandler - let parenTokensBalance t1 t2 = - match t1, t2 with - | (LPAREN, RPAREN) - | (LPAREN, RPAREN_IS_HERE) - | (LBRACE, RBRACE) - | (LBRACE, RBRACE_IS_HERE) - | (SIG, END) - | (STRUCT, END) - | (LBRACK_BAR, BAR_RBRACK) - | (LBRACK, RBRACK) - | (LBRACK_LESS, GREATER_RBRACK) - | (BEGIN, END) -> true - | (LQUOTE q1, RQUOTE q2) -> q1 = q2 - | _ -> false - let rec matchBraces stack = - match lexfun lexbuf, stack with - | tok2, ((tok1, m1) :: stack') when parenTokensBalance tok1 tok2 -> - matchingBraces.Add(m1, lexbuf.LexemeRange) - matchBraces stack' - | ((LPAREN | LBRACE | LBRACK | LBRACK_BAR | LQUOTE _ | LBRACK_LESS) as tok), _ -> - matchBraces ((tok, lexbuf.LexemeRange) :: stack) - | (EOF _ | LEX_FAILURE _), _ -> () - | _ -> matchBraces stack - matchBraces []) - matchingBraces.ToArray() - - let parseFile(sourceText: ISourceText, fileName, options: FSharpParsingOptions, userOpName: string, suggestNamesForErrors: bool) = - Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "parseFile", fileName) - let errHandler = new ErrorHandler(true, fileName, options.ErrorSeverityOptions, sourceText, suggestNamesForErrors) - use unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _oldLogger -> errHandler.ErrorLogger) - use unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.Parse - - let parseResult = - Lexhelp.usingLexbufForParsing(createLexbuf sourceText, fileName) (fun lexbuf -> - let lexfun = createLexerFunction fileName options lexbuf errHandler - let isLastCompiland = - fileName.Equals(options.LastFileName, StringComparison.CurrentCultureIgnoreCase) || - CompileOps.IsScript(fileName) - let isExe = options.IsExe - try Some (ParseInput(lexfun, errHandler.ErrorLogger, lexbuf, None, fileName, (isLastCompiland, isExe))) - with e -> - errHandler.ErrorLogger.StopProcessingRecovery e Range.range0 // don't re-raise any exceptions, we must return None. - None) - errHandler.CollectedDiagnostics, parseResult, errHandler.AnyErrors - - /// Indicates if the type check got aborted because it is no longer relevant. - type TypeCheckAborted = Yes | No of TypeCheckInfo - - // Type check a single file against an initial context, gleaning both errors and intellisense information. - let CheckOneFile - (parseResults: FSharpParseFileResults, - sourceText: ISourceText, - mainInputFileName: string, - projectFileName: string, - tcConfig: TcConfig, - tcGlobals: TcGlobals, - tcImports: TcImports, - tcState: TcState, - moduleNamesDict: ModuleNamesDict, - loadClosure: LoadClosure option, - // These are the errors and warnings seen by the background compiler for the entire antecedent - backgroundDiagnostics: (PhasedDiagnostic * FSharpErrorSeverity)[], - reactorOps: IReactorOperations, - textSnapshotInfo : obj option, - userOpName: string, - suggestNamesForErrors: bool) = - - async { - use _logBlock = Logger.LogBlock LogCompilerFunctionId.Service_CheckOneFile - - match parseResults.ParseTree with - // When processing the following cases, we don't need to type-check - | None -> return [||], TypeCheckAborted.Yes - - // Run the type checker... - | Some parsedMainInput -> - // Initialize the error handler - let errHandler = new ErrorHandler(true, mainInputFileName, tcConfig.errorSeverityOptions, sourceText, suggestNamesForErrors) - - use _unwindEL = PushErrorLoggerPhaseUntilUnwind (fun _oldLogger -> errHandler.ErrorLogger) - use _unwindBP = PushThreadBuildPhaseUntilUnwind BuildPhase.TypeCheck - - // Apply nowarns to tcConfig (may generate errors, so ensure errorLogger is installed) - let tcConfig = ApplyNoWarnsToTcConfig (tcConfig, parsedMainInput,Path.GetDirectoryName mainInputFileName) - - // update the error handler with the modified tcConfig - errHandler.ErrorSeverityOptions <- tcConfig.errorSeverityOptions - - // Play background errors and warnings for this file. - for (err,sev) in backgroundDiagnostics do - diagnosticSink (err, (sev = FSharpErrorSeverity.Error)) - - // If additional references were brought in by the preprocessor then we need to process them - match loadClosure with - | Some loadClosure -> - // Play unresolved references for this file. - tcImports.ReportUnresolvedAssemblyReferences(loadClosure.UnresolvedReferences) - - // If there was a loadClosure, replay the errors and warnings from resolution, excluding parsing - loadClosure.LoadClosureRootFileDiagnostics |> List.iter diagnosticSink - - let fileOfBackgroundError err = (match GetRangeOfDiagnostic (fst err) with Some m-> m.FileName | None -> null) - let sameFile file hashLoadInFile = - (0 = String.Compare(hashLoadInFile, file, StringComparison.OrdinalIgnoreCase)) - - // walk the list of #loads and keep the ones for this file. - let hashLoadsInFile = - loadClosure.SourceFiles - |> List.filter(fun (_,ms) -> ms<>[]) // #loaded file, ranges of #load - - let hashLoadBackgroundDiagnostics, otherBackgroundDiagnostics = - backgroundDiagnostics - |> Array.partition (fun backgroundError -> - hashLoadsInFile - |> List.exists (fst >> sameFile (fileOfBackgroundError backgroundError))) - - // Create single errors for the #load-ed files. - // Group errors and warnings by file name. - let hashLoadBackgroundDiagnosticsGroupedByFileName = - hashLoadBackgroundDiagnostics - |> Array.map(fun err -> fileOfBackgroundError err,err) - |> Array.groupBy fst // fileWithErrors, error list - - // Join the sets and report errors. - // It is by-design that these messages are only present in the language service. A true build would report the errors at their - // spots in the individual source files. - for (fileOfHashLoad, rangesOfHashLoad) in hashLoadsInFile do - for (file, errorGroupedByFileName) in hashLoadBackgroundDiagnosticsGroupedByFileName do - if sameFile file fileOfHashLoad then - for rangeOfHashLoad in rangesOfHashLoad do // Handle the case of two #loads of the same file - let diagnostics = errorGroupedByFileName |> Array.map(fun (_,(pe,f)) -> pe.Exception,f) // Strip the build phase here. It will be replaced, in total, with TypeCheck - let errors = [ for (err,sev) in diagnostics do if sev = FSharpErrorSeverity.Error then yield err ] - let warnings = [ for (err,sev) in diagnostics do if sev = FSharpErrorSeverity.Warning then yield err ] - - let message = HashLoadedSourceHasIssues(warnings,errors,rangeOfHashLoad) - if errors=[] then warning(message) - else errorR(message) - - // Replay other background errors. - for (phasedError,sev) in otherBackgroundDiagnostics do - if sev = FSharpErrorSeverity.Warning then - warning phasedError.Exception - else errorR phasedError.Exception - - | None -> - // For non-scripts, check for disallow #r and #load. - ApplyMetaCommandsFromInputToTcConfig (tcConfig, parsedMainInput,Path.GetDirectoryName mainInputFileName) |> ignore - - // A problem arises with nice name generation, which really should only - // be done in the backend, but is also done in the typechecker for better or worse. - // If we don't do this the NNG accumulates data and we get a memory leak. - tcState.NiceNameGenerator.Reset() - - // Typecheck the real input. - let sink = TcResultsSinkImpl(tcGlobals, sourceText = sourceText) - - let! ct = Async.CancellationToken - - let! resOpt = - async { - try - let checkForErrors() = (parseResults.ParseHadErrors || errHandler.ErrorCount > 0) - - let parsedMainInput, _moduleNamesDict = DeduplicateParsedInputModuleName moduleNamesDict parsedMainInput - - // Typecheck is potentially a long running operation. We chop it up here with an Eventually continuation and, at each slice, give a chance - // for the client to claim the result as obsolete and have the typecheck abort. - - let! result = - TypeCheckOneInputAndFinishEventually(checkForErrors, tcConfig, tcImports, tcGlobals, None, TcResultsSink.WithSink sink, tcState, parsedMainInput) - |> Eventually.repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled maxTimeShareMilliseconds ct (fun ctok f -> f ctok) - |> Eventually.forceAsync - (fun work -> - reactorOps.EnqueueAndAwaitOpAsync(userOpName, "CheckOneFile.Fragment", mainInputFileName, - fun ctok -> - // This work is not cancellable - let res = - // Reinstall the compilation globals each time we start or restart - use unwind = new CompilationGlobalsScope (errHandler.ErrorLogger, BuildPhase.TypeCheck) - work ctok - cancellable.Return(res) - )) - - return result |> Option.map (fun ((tcEnvAtEnd, _, implFiles, ccuSigsForFiles), tcState) -> tcEnvAtEnd, implFiles, ccuSigsForFiles, tcState) - with e -> - errorR e - return Some(tcState.TcEnvFromSignatures, [], [NewEmptyModuleOrNamespaceType Namespace], tcState) - } - - let errors = errHandler.CollectedDiagnostics - - match resOpt with - | Some (tcEnvAtEnd, implFiles, ccuSigsForFiles, tcState) -> - let scope = - TypeCheckInfo(tcConfig, tcGlobals, - List.head ccuSigsForFiles, - tcState.Ccu, - tcImports, - tcEnvAtEnd.AccessRights, - projectFileName, - mainInputFileName, - sink.GetResolutions(), - sink.GetSymbolUses(), - tcEnvAtEnd.NameEnv, - loadClosure, - reactorOps, - textSnapshotInfo, - List.tryHead implFiles, - sink.GetOpenDeclarations()) - return errors, TypeCheckAborted.No scope - | None -> - return errors, TypeCheckAborted.Yes - } - type UnresolvedReferencesSet = UnresolvedReferencesSet of UnresolvedAssemblyReference list // NOTE: may be better just to move to optional arguments here @@ -1814,328 +88,10 @@ type FSharpProjectOptions = member po.ProjectDirectory = System.IO.Path.GetDirectoryName(po.ProjectFileName) override this.ToString() = "FSharpProjectOptions(" + this.ProjectFileName + ")" - -[] -type FSharpProjectContext(thisCcu: CcuThunk, assemblies: FSharpAssembly list, ad: AccessorDomain) = - - /// Get the assemblies referenced - member __.GetReferencedAssemblies() = assemblies - - member __.AccessibilityRights = FSharpAccessibilityRights(thisCcu, ad) - - -[] -// 'details' is an option because the creation of the tcGlobals etc. for the project may have failed. -type FSharpCheckProjectResults(projectFileName:string, tcConfigOption, keepAssemblyContents, errors: FSharpErrorInfo[], - details:(TcGlobals * TcImports * CcuThunk * ModuleOrNamespaceType * TcSymbolUses list * TopAttribs option * CompileOps.IRawFSharpAssemblyData option * ILAssemblyRef * AccessorDomain * TypedImplFile list option * string[]) option) = - - let getDetails() = - match details with - | None -> invalidOp ("The project has no results due to critical errors in the project options. Check the HasCriticalErrors before accessing the detailed results. Errors: " + String.concat "\n" [ for e in errors -> e.Message ]) - | Some d -> d - - let getTcConfig() = - match tcConfigOption with - | None -> invalidOp ("The project has no results due to critical errors in the project options. Check the HasCriticalErrors before accessing the detailed results. Errors: " + String.concat "\n" [ for e in errors -> e.Message ]) - | Some d -> d - - member info.Errors = errors - - member info.HasCriticalErrors = details.IsNone - - member info.AssemblySignature = - let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - FSharpAssemblySignature(tcGlobals, thisCcu, ccuSig, tcImports, topAttribs, ccuSig) - - member info.TypedImplementionFiles = - if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" - let (tcGlobals, tcImports, thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() - let mimpls = - match tcAssemblyExpr with - | None -> [] - | Some mimpls -> mimpls - tcGlobals, thisCcu, tcImports, mimpls - - member info.AssemblyContents = - if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" - let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() - let mimpls = - match tcAssemblyExpr with - | None -> [] - | Some mimpls -> mimpls - FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls) - - member info.GetOptimizedAssemblyContents() = - if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" - let (tcGlobals, tcImports, thisCcu, ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, tcAssemblyExpr, _dependencyFiles) = getDetails() - let mimpls = - match tcAssemblyExpr with - | None -> [] - | Some mimpls -> mimpls - let outfile = "" // only used if tcConfig.writeTermsToFiles is true - let importMap = tcImports.GetImportMap() - let optEnv0 = GetInitialOptimizationEnv (tcImports, tcGlobals) - let tcConfig = getTcConfig() - let optimizedImpls, _optimizationData, _ = ApplyAllOptimizations (tcConfig, tcGlobals, (LightweightTcValForUsingInBuildMethodCall tcGlobals), outfile, importMap, false, optEnv0, thisCcu, mimpls) - let mimpls = - match optimizedImpls with - | TypedAssemblyAfterOptimization files -> - files |> List.map fst - - FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls) - - // Not, this does not have to be a SyncOp, it can be called from any thread - member info.GetUsesOfSymbol(symbol:FSharpSymbol) = - let (tcGlobals, _tcImports, _thisCcu, _ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - - tcSymbolUses - |> Seq.collect (fun r -> r.GetUsesOfSymbol symbol.Item) - |> Seq.distinctBy (fun symbolUse -> symbolUse.ItemOccurence, symbolUse.Range) - |> Seq.filter (fun symbolUse -> symbolUse.ItemOccurence <> ItemOccurence.RelatedText) - |> Seq.map (fun symbolUse -> FSharpSymbolUse(tcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range)) - |> Seq.toArray - |> async.Return - - // Not, this does not have to be a SyncOp, it can be called from any thread - member __.GetAllUsesOfAllSymbols() = - let (tcGlobals, tcImports, thisCcu, ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - let cenv = SymbolEnv(tcGlobals, thisCcu, Some ccuSig, tcImports) - - [| for r in tcSymbolUses do - for symbolUseChunk in r.AllUsesOfSymbols do - for symbolUse in symbolUseChunk do - if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then - let symbol = FSharpSymbol.Create(cenv, symbolUse.Item) - yield FSharpSymbolUse(tcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |] - |> async.Return - - member __.ProjectContext = - let (tcGlobals, tcImports, thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - let assemblies = - [ for x in tcImports.GetImportedAssemblies() do - yield FSharpAssembly(tcGlobals, tcImports, x.FSharpViewOfMetadata) ] - FSharpProjectContext(thisCcu, assemblies, ad) - - member __.RawFSharpAssemblyData = - let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - tcAssemblyData - - member __.DependencyFiles = - let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr, dependencyFiles) = getDetails() - dependencyFiles - - member __.AssemblyFullName = - let (_tcGlobals, _tcImports, _thisCcu, _ccuSig, _tcSymbolUses, _topAttribs, _tcAssemblyData, ilAssemRef, _ad, _tcAssemblyExpr, _dependencyFiles) = getDetails() - ilAssemRef.QualifiedName - - override info.ToString() = "FSharpCheckProjectResults(" + projectFileName + ")" - -[] -type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOptX: TypeCheckInfo option, dependencyFiles: string[], builderX: IncrementalBuilder option, reactorOpsX:IReactorOperations, keepAssemblyContents: bool) = - - // This may be None initially. - let mutable details = match scopeOptX with None -> None | Some scopeX -> Some (scopeX, builderX, reactorOpsX) - - // Run an operation that needs to access a builder and be run in the reactor thread - let reactorOp userOpName opName dflt f = - async { - match details with - | None -> - return dflt - | Some (scope, _, reactor) -> - let! res = reactor.EnqueueAndAwaitOpAsync(userOpName, opName, filename, fun ctok -> f ctok scope |> cancellable.Return) - return res - } - - // Run an operation that can be called from any thread - let threadSafeOp dflt f = - match details with - | None -> dflt() - | Some (scope, _builderOpt, _ops) -> f scope - - member info.Errors = errors - - member info.HasFullTypeCheckInfo = details.IsSome - - member info.TryGetCurrentTcImports () = - match builderX with - | Some builder -> builder.TryGetCurrentTcImports () - | _ -> None - - /// Intellisense autocompletions - member info.GetDeclarationListInfo(parseResultsOpt, line, lineStr, partialName, ?getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - let getAllEntities = defaultArg getAllEntities (fun() -> []) - let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) - reactorOp userOpName "GetDeclarations" FSharpDeclarationListInfo.Empty (fun ctok scope -> - scope.GetDeclarations(ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) - - member info.GetDeclarationListSymbols(parseResultsOpt, line, lineStr, partialName, ?getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) - let getAllEntities = defaultArg getAllEntities (fun() -> []) - reactorOp userOpName "GetDeclarationListSymbols" List.empty (fun ctok scope -> scope.GetDeclarationListSymbols(ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) - - /// Resolve the names at the given location to give a data tip - member info.GetStructuredToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - let dflt = FSharpToolTipText [] - match tokenTagToTokenId tokenTag with - | TOKEN_IDENT -> - reactorOp userOpName "GetStructuredToolTipText" dflt (fun ctok scope -> scope.GetStructuredToolTipText(ctok, line, lineStr, colAtEndOfNames, names)) - | TOKEN_STRING | TOKEN_STRING_TEXT -> - reactorOp userOpName "GetReferenceResolutionToolTipText" dflt (fun ctok scope -> scope.GetReferenceResolutionStructuredToolTipText(ctok, line, colAtEndOfNames) ) - | _ -> - async.Return dflt - - - member info.GetToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, userOpName) = - info.GetStructuredToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, ?userOpName=userOpName) - |> Tooltips.Map Tooltips.ToFSharpToolTipText - - member info.GetF1Keyword (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "GetF1Keyword" None (fun ctok scope -> - scope.GetF1Keyword (ctok, line, lineStr, colAtEndOfNames, names)) - - // Resolve the names at the given location to a set of methods - member info.GetMethods(line, colAtEndOfNames, lineStr, names, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - let dflt = FSharpMethodGroup("",[| |]) - reactorOp userOpName "GetMethods" dflt (fun ctok scope -> - scope.GetMethods (ctok, line, lineStr, colAtEndOfNames, names)) - - member info.GetDeclarationLocation (line, colAtEndOfNames, lineStr, names, ?preferFlag, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - let dflt = FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "") - reactorOp userOpName "GetDeclarationLocation" dflt (fun ctok scope -> - scope.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag)) - - member info.GetSymbolUseAtLocation (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "GetSymbolUseAtLocation" None (fun ctok scope -> - scope.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) - |> Option.map (fun (sym,denv,m) -> FSharpSymbolUse(scope.TcGlobals,denv,sym,ItemOccurence.Use,m))) - - member info.GetMethodsAsSymbols (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "GetMethodsAsSymbols" None (fun ctok scope -> - scope.GetMethodsAsSymbols (ctok, line, lineStr, colAtEndOfNames, names) - |> Option.map (fun (symbols,denv,m) -> - symbols |> List.map (fun sym -> FSharpSymbolUse(scope.TcGlobals,denv,sym,ItemOccurence.Use,m)))) - - member info.GetSymbolAtLocation (line, colAtEndOfNames, lineStr, names, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "GetSymbolAtLocation" None (fun ctok scope -> - scope.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) - |> Option.map (fun (sym,_,_) -> sym)) - - member info.GetFormatSpecifierLocations() = - info.GetFormatSpecifierLocationsAndArity() |> Array.map fst - - member info.GetFormatSpecifierLocationsAndArity() = - threadSafeOp - (fun () -> [| |]) - (fun scope -> - // This operation is not asynchronous - GetFormatSpecifierLocationsAndArity can be run on the calling thread - scope.GetFormatSpecifierLocationsAndArity()) - - member __.GetSemanticClassification(range: range option) = - threadSafeOp - (fun () -> [| |]) - (fun scope -> - // This operation is not asynchronous - GetSemanticClassification can be run on the calling thread - scope.GetSemanticClassification(range)) - - member __.PartialAssemblySignature = - threadSafeOp - (fun () -> failwith "not available") - (fun scope -> - // This operation is not asynchronous - PartialAssemblySignature can be run on the calling thread - scope.PartialAssemblySignatureForFile) - - member __.ProjectContext = - threadSafeOp - (fun () -> failwith "not available") - (fun scope -> - // This operation is not asynchronous - GetReferencedAssemblies can be run on the calling thread - FSharpProjectContext(scope.ThisCcu, scope.GetReferencedAssemblies(), scope.AccessRights)) - - member __.DependencyFiles = dependencyFiles - - member info.GetAllUsesOfAllSymbolsInFile() = - threadSafeOp - (fun () -> [| |]) - (fun scope -> - let cenv = scope.SymbolEnv - [| for symbolUseChunk in scope.ScopeSymbolUses.AllUsesOfSymbols do - for symbolUse in symbolUseChunk do - if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then - let symbol = FSharpSymbol.Create(cenv, symbolUse.Item) - yield FSharpSymbolUse(scope.TcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |]) - |> async.Return - - member info.GetUsesOfSymbolInFile(symbol:FSharpSymbol) = - threadSafeOp - (fun () -> [| |]) - (fun scope -> - [| for symbolUse in scope.ScopeSymbolUses.GetUsesOfSymbol(symbol.Item) |> Seq.distinctBy (fun symbolUse -> symbolUse.ItemOccurence, symbolUse.Range) do - if symbolUse.ItemOccurence <> ItemOccurence.RelatedText then - yield FSharpSymbolUse(scope.TcGlobals, symbolUse.DisplayEnv, symbol, symbolUse.ItemOccurence, symbolUse.Range) |]) - |> async.Return - - member info.GetVisibleNamespacesAndModulesAtPoint(pos: pos) = - threadSafeOp - (fun () -> [| |]) - (fun scope -> scope.GetVisibleNamespacesAndModulesAtPosition(pos) |> List.toArray) - |> async.Return - - member info.IsRelativeNameResolvable(pos: pos, plid: string list, item: Item, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "IsRelativeNameResolvable" true (fun ctok scope -> - RequireCompilationThread ctok - scope.IsRelativeNameResolvable(pos, plid, item)) - - member info.IsRelativeNameResolvableFromSymbol(pos: pos, plid: string list, symbol: FSharpSymbol, ?userOpName: string) = - let userOpName = defaultArg userOpName "Unknown" - reactorOp userOpName "IsRelativeNameResolvableFromSymbol" true (fun ctok scope -> - RequireCompilationThread ctok - scope.IsRelativeNameResolvableFromSymbol(pos, plid, symbol)) - - member info.GetDisplayEnvForPos(pos: pos) : Async = - let userOpName = "CodeLens" - reactorOp userOpName "GetDisplayContextAtPos" None (fun ctok scope -> - DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok - let (nenv, _), _ = scope.GetBestDisplayEnvForPos pos - Some nenv.DisplayEnv) - - member info.ImplementationFile = - if not keepAssemblyContents then invalidOp "The 'keepAssemblyContents' flag must be set to true on the FSharpChecker in order to access the checked contents of assemblies" - scopeOptX - |> Option.map (fun scope -> - let cenv = SymbolEnv(scope.TcGlobals, scope.ThisCcu, Some scope.CcuSigForFile, scope.TcImports) - scope.ImplementationFile |> Option.map (fun implFile -> FSharpImplementationFileContents(cenv, implFile))) - |> Option.defaultValue None - - member info.OpenDeclarations = - scopeOptX - |> Option.map (fun scope -> - let cenv = scope.SymbolEnv - scope.OpenDeclarations |> Array.map (fun x -> FSharpOpenDeclaration(x.LongId, x.Range, (x.Modules |> List.map (fun x -> FSharpEntity(cenv, x))), x.AppliedScope, x.IsOwnNamespace))) - |> Option.defaultValue [| |] - - override info.ToString() = "FSharpCheckFileResults(" + filename + ")" - //---------------------------------------------------------------------------- // BackgroundCompiler // -[] -type FSharpCheckFileAnswer = - | Aborted - | Succeeded of FSharpCheckFileResults - - /// Callback that indicates whether a requested result has become obsolete. [] type IsResultObsolete = @@ -2145,9 +101,6 @@ type IsResultObsolete = [] module Helpers = - // Look for DLLs in the location of the service DLL first. - let defaultFSharpBinariesDir = FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(Some(typeof.Assembly.Location)).Value - /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking let AreSameForChecking2((fileName1: string, options1: FSharpProjectOptions), (fileName2, options2)) = (fileName1 = fileName2) @@ -2350,9 +303,9 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC let loadClosure = scriptClosureCacheLock.AcquireLock (fun ltok -> scriptClosureCache.TryGet (ltok, options)) let! builderOpt, diagnostics = IncrementalBuilder.TryCreateBackgroundBuilderForProjectOptions - (ctok, legacyReferenceResolver, defaultFSharpBinariesDir, frameworkTcImportsCache, loadClosure, Array.toList options.SourceFiles, + (ctok, legacyReferenceResolver, FSharpCheckerResultsSettings.defaultFSharpBinariesDir, frameworkTcImportsCache, loadClosure, Array.toList options.SourceFiles, Array.toList options.OtherOptions, projectReferences, options.ProjectDirectory, - options.UseScriptResolutionRules, keepAssemblyContents, keepAllBackgroundResolutions, maxTimeShareMilliseconds, + options.UseScriptResolutionRules, keepAssemblyContents, keepAllBackgroundResolutions, FSharpCheckerResultsSettings.maxTimeShareMilliseconds, tryGetMetadataSnapshot, suggestNamesForErrors) match builderOpt with @@ -2439,28 +392,10 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC (fun (f1, o1, v1) (f2, o2, v2) -> f1 = f2 && v1 = v2 && FSharpProjectOptions.AreSameForChecking(o1, o2))) static let mutable foregroundParseCount = 0 + static let mutable foregroundTypeCheckCount = 0 - let MakeCheckFileResultsEmpty(filename, creationErrors) = - FSharpCheckFileResults (filename, creationErrors, None, [| |], None, reactorOps, keepAssemblyContents) - - let MakeCheckFileResults(filename, options:FSharpProjectOptions, builder, scope, dependencyFiles, creationErrors, parseErrors, tcErrors) = - let errors = - [| yield! creationErrors - yield! parseErrors - if options.IsIncompleteTypeCheckEnvironment then - yield! Seq.truncate maxTypeCheckErrorsOutOfProjectContext tcErrors - else - yield! tcErrors |] - - FSharpCheckFileResults (filename, errors, Some scope, dependencyFiles, Some builder, reactorOps, keepAssemblyContents) - - let MakeCheckFileAnswer(filename, tcFileResult, options:FSharpProjectOptions, builder, dependencyFiles, creationErrors, parseErrors, tcErrors) = - match tcFileResult with - | Parser.TypeCheckAborted.Yes -> FSharpCheckFileAnswer.Aborted - | Parser.TypeCheckAborted.No scope -> FSharpCheckFileAnswer.Succeeded(MakeCheckFileResults(filename, options, builder, scope, dependencyFiles, creationErrors, parseErrors, tcErrors)) - - member bc.RecordTypeCheckFileInProjectResults(filename,options,parsingOptions,parseResults,fileVersion,priorTimeStamp,checkAnswer,sourceText) = + member __.RecordTypeCheckFileInProjectResults(filename,options,parsingOptions,parseResults,fileVersion,priorTimeStamp,checkAnswer,sourceText) = match checkAnswer with | None | Some FSharpCheckFileAnswer.Aborted -> () @@ -2468,28 +403,28 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC foregroundTypeCheckCount <- foregroundTypeCheckCount + 1 parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.Set(ltok, (filename,options),(parseResults,typedResults,fileVersion)) - checkFileInProjectCache.Set(ltok, (filename,sourceText,options),(parseResults,typedResults,fileVersion,priorTimeStamp)) + checkFileInProjectCache.Set(ltok, (filename, sourceText, options),(parseResults,typedResults,fileVersion,priorTimeStamp)) parseFileCache.Set(ltok, (filename, sourceText, parsingOptions), parseResults)) member bc.ImplicitlyStartCheckProjectInBackground(options, userOpName) = if implicitlyStartBackgroundWork then bc.CheckProjectInBackground(options, userOpName + ".ImplicitlyStartCheckProjectInBackground") - member bc.ParseFile(filename: string, sourceText: ISourceText, options: FSharpParsingOptions, userOpName: string) = + member __.ParseFile(filename: string, sourceText: ISourceText, options: FSharpParsingOptions, userOpName: string) = async { let hash = sourceText.GetHashCode() match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, hash, options))) with | Some res -> return res | None -> foregroundParseCount <- foregroundParseCount + 1 - let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile(sourceText, filename, options, userOpName, suggestNamesForErrors) + let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile(sourceText, filename, options, userOpName, suggestNamesForErrors) let res = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, options.SourceFiles) parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, hash, options), res)) return res } /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API) - member bc.GetBackgroundParseResultsForFileInProject(filename, options, userOpName) = + member __.GetBackgroundParseResultsForFileInProject(filename, options, userOpName) = reactor.EnqueueAndAwaitOpAsync(userOpName, "GetBackgroundParseResultsForFileInProject ", filename, fun ctok -> cancellable { let! builderOpt, creationErrors = getOrCreateBuilder (ctok, options, userOpName) @@ -2502,21 +437,21 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } ) - member bc.GetCachedCheckFileResult(builder: IncrementalBuilder,filename,sourceText: ISourceText,options) = - // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date - let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename,sourceText.GetHashCode(),options))) + member __.GetCachedCheckFileResult(builder: IncrementalBuilder, filename, sourceText: ISourceText, options) = + // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date + let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename, sourceText.GetHashCode(), options))) - match cachedResults with + match cachedResults with // | Some (parseResults, checkResults, _, _) when builder.AreCheckResultsBeforeFileInProjectReady(filename) -> - | Some (parseResults, checkResults,_,priorTimeStamp) - when - (match builder.GetCheckResultsBeforeFileInProjectEvenIfStale filename with - | None -> false - | Some(tcPrior) -> - tcPrior.TimeStamp = priorTimeStamp && - builder.AreCheckResultsBeforeFileInProjectReady(filename)) -> - Some (parseResults,checkResults) - | _ -> None + | Some (parseResults, checkResults,_,priorTimeStamp) + when + (match builder.GetCheckResultsBeforeFileInProjectEvenIfStale filename with + | None -> false + | Some(tcPrior) -> + tcPrior.TimeStamp = priorTimeStamp && + builder.AreCheckResultsBeforeFileInProjectReady(filename)) -> + Some (parseResults,checkResults) + | _ -> None /// 1. Repeatedly try to get cached file check results or get file "lock". /// @@ -2559,12 +494,31 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC // Get additional script #load closure information if applicable. // For scripts, this will have been recorded by GetProjectOptionsFromScript. let loadClosure = scriptClosureCacheLock.AcquireLock (fun ltok -> scriptClosureCache.TryGet (ltok, options)) - let! tcErrors, tcFileResult = - Parser.CheckOneFile(parseResults, sourceText, fileName, options.ProjectFileName, tcPrior.TcConfig, tcPrior.TcGlobals, tcPrior.TcImports, - tcPrior.TcState, tcPrior.ModuleNamesDict, loadClosure, tcPrior.TcErrors, reactorOps, textSnapshotInfo, userOpName, suggestNamesForErrors) + let! checkAnswer = + FSharpCheckFileResults.CheckOneFile + (parseResults, + sourceText, + fileName, + options.ProjectFileName, + tcPrior.TcConfig, + tcPrior.TcGlobals, + tcPrior.TcImports, + tcPrior.TcState, + tcPrior.ModuleNamesDict, + loadClosure, + tcPrior.TcErrors, + reactorOps, + textSnapshotInfo, + userOpName, + options.IsIncompleteTypeCheckEnvironment, + builder, + Array.ofList tcPrior.TcDependencyFiles, + creationErrors, + parseResults.Errors, + keepAssemblyContents, + suggestNamesForErrors) let parsingOptions = FSharpParsingOptions.FromTcConfig(tcPrior.TcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules) - let checkAnswer = MakeCheckFileAnswer(fileName, tcFileResult, options, builder, Array.ofList tcPrior.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors) - bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText.GetHashCode()) + bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText.GetHashCode()) return checkAnswer finally let dummy = ref () @@ -2630,7 +584,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC reactor.CancelBackgroundOp() // cancel the background work, since we will start new work after we're done let! builderOpt,creationErrors = execWithReactorAsync (fun ctok -> getOrCreateBuilder (ctok, options, userOpName)) match builderOpt with - | None -> return FSharpCheckFileAnswer.Succeeded (MakeCheckFileResultsEmpty(filename, creationErrors)) + | None -> return FSharpCheckFileAnswer.Succeeded (FSharpCheckFileResults.MakeEmpty(filename, creationErrors, reactorOps, keepAssemblyContents)) | Some builder -> // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date let cachedResults = bc.GetCachedCheckFileResult(builder, filename, sourceText, options) @@ -2681,7 +635,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC // Do the parsing. let parsingOptions = FSharpParsingOptions.FromTcConfig(builder.TcConfig, Array.ofList (builder.SourceFiles), options.UseScriptResolutionRules) - let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile (sourceText, filename, parsingOptions, userOpName, suggestNamesForErrors) + let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile (sourceText, filename, parsingOptions, userOpName, suggestNamesForErrors) let parseResults = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, builder.AllDependenciesDeprecated) let! checkResults = bc.CheckOneFileImpl(parseResults, sourceText, filename, options, textSnapshotInfo, fileVersion, builder, tcPrior, creationErrors, userOpName) @@ -2693,14 +647,14 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API) - member bc.GetBackgroundCheckResultsForFileInProject(filename, options, userOpName) = + member __.GetBackgroundCheckResultsForFileInProject(filename, options, userOpName) = reactor.EnqueueAndAwaitOpAsync(userOpName, "GetBackgroundCheckResultsForFileInProject", filename, fun ctok -> cancellable { let! builderOpt, creationErrors = getOrCreateBuilder (ctok, options, userOpName) match builderOpt with | None -> let parseResults = FSharpParseFileResults(creationErrors, None, true, [| |]) - let typedResults = MakeCheckFileResultsEmpty(filename, creationErrors) + let typedResults = FSharpCheckFileResults.MakeEmpty(filename, creationErrors, reactorOps, keepAssemblyContents) return (parseResults, typedResults) | Some builder -> let! (parseTreeOpt, _, _, untypedErrors) = builder.GetParseResultsForFile (ctok, filename) @@ -2710,24 +664,36 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC let tcErrors = [| yield! creationErrors; yield! ErrorHelpers.CreateErrorInfos (errorOptions, false, filename, tcProj.TcErrors, suggestNamesForErrors) |] let parseResults = FSharpParseFileResults(errors = untypedErrors, input = parseTreeOpt, parseHadErrors = false, dependencyFiles = builder.AllDependenciesDeprecated) let loadClosure = scriptClosureCacheLock.AcquireLock (fun ltok -> scriptClosureCache.TryGet (ltok, options) ) - let scope = - TypeCheckInfo(tcProj.TcConfig, tcProj.TcGlobals, - Option.get tcProj.LastestCcuSigForFile, - tcProj.TcState.Ccu, tcProj.TcImports, tcProj.TcEnvAtEnd.AccessRights, - options.ProjectFileName, filename, - List.head tcProj.TcResolutionsRev, - List.head tcProj.TcSymbolUsesRev, - tcProj.TcEnvAtEnd.NameEnv, - loadClosure, reactorOps, None, - tcProj.LatestImplementationFile, - List.head tcProj.TcOpenDeclarationsRev) - let typedResults = MakeCheckFileResults(filename, options, builder, scope, Array.ofList tcProj.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors) + let typedResults = + FSharpCheckFileResults.Make + (filename, + options.ProjectFileName, + tcProj.TcConfig, + tcProj.TcGlobals, + options.IsIncompleteTypeCheckEnvironment, + builder, + Array.ofList tcProj.TcDependencyFiles, + creationErrors, + parseResults.Errors, + tcErrors, + reactorOps, + keepAssemblyContents, + Option.get tcProj.LastestCcuSigForFile, + tcProj.TcState.Ccu, + tcProj.TcImports, + tcProj.TcEnvAtEnd.AccessRights, + List.head tcProj.TcResolutionsRev, + List.head tcProj.TcSymbolUsesRev, + tcProj.TcEnvAtEnd.NameEnv, + loadClosure, + tcProj.LatestImplementationFile, + List.head tcProj.TcOpenDeclarationsRev) return (parseResults, typedResults) }) /// Try to get recent approximate type check results for a file. - member bc.TryGetRecentCheckResultsForFile(filename: string, options:FSharpProjectOptions, sourceText: ISourceText option, _userOpName: string) = + member __.TryGetRecentCheckResultsForFile(filename: string, options:FSharpProjectOptions, sourceText: ISourceText option, _userOpName: string) = match sourceText with | Some sourceText -> parseCacheLock.AcquireLock (fun ltok -> @@ -2737,7 +703,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options))) /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated) - member private bc.ParseAndCheckProjectImpl(options, ctok, userOpName) : Cancellable = + member private __.ParseAndCheckProjectImpl(options, ctok, userOpName) : Cancellable = cancellable { let! builderOpt,creationErrors = getOrCreateBuilder (ctok, options, userOpName) match builderOpt with @@ -2755,7 +721,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC } /// Get the timestamp that would be on the output if fully built immediately - member private bc.TryGetLogicalTimeStampForProject(cache, ctok, options, userOpName: string) = + member private __.TryGetLogicalTimeStampForProject(cache, ctok, options, userOpName: string) = // NOTE: This creation of the background builder is currently run as uncancellable. Creating background builders is generally // cheap though the timestamp computations look suspicious for transitive project references. @@ -2768,7 +734,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC member bc.ParseAndCheckProject(options, userOpName) = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckProject", options.ProjectFileName, fun ctok -> bc.ParseAndCheckProjectImpl(options, ctok, userOpName)) - member bc.GetProjectOptionsFromScript(filename, sourceText, loadedTimeStamp, otherFlags, useFsiAuxLib: bool option, useSdkRefs: bool option, assumeDotNetFramework: bool option, extraProjectInfo: obj option, optionsStamp: int64 option, userOpName) = + member __.GetProjectOptionsFromScript(filename, sourceText, loadedTimeStamp, otherFlags, useFsiAuxLib: bool option, useSdkRefs: bool option, assumeDotNetFramework: bool option, extraProjectInfo: obj option, optionsStamp: int64 option, userOpName) = reactor.EnqueueAndAwaitOpAsync (userOpName, "GetProjectOptionsFromScript", filename, fun ctok -> cancellable { use errors = new ErrorScope() @@ -2794,7 +760,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC let loadClosure = LoadClosure.ComputeClosureOfScriptText(ctok, legacyReferenceResolver, - defaultFSharpBinariesDir, filename, sourceText, + FSharpCheckerResultsSettings.defaultFSharpBinariesDir, filename, sourceText, CodeContext.Editing, useSimpleResolution, useFsiAuxLib, useSdkRefs, new Lexhelp.LexResourceManager(), applyCompilerOptions, assumeDotNetFramework, tryGetMetadataSnapshot=tryGetMetadataSnapshot, @@ -2843,7 +809,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC if startBackgroundCompileIfAlreadySeen then bc.CheckProjectInBackground(options, userOpName + ".StartBackgroundCompile")) - member bc.NotifyProjectCleaned (options : FSharpProjectOptions, userOpName) = + member __.NotifyProjectCleaned (options : FSharpProjectOptions, userOpName) = reactor.EnqueueAndAwaitOpAsync(userOpName, "NotifyProjectCleaned", options.ProjectFileName, fun ctok -> cancellable { // If there was a similar entry (as there normally will have been) then re-establish an empty builder . This @@ -2856,7 +822,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC incrementalBuildersCache.Set(ctok, options, newBuilderInfo) }) - member bc.CheckProjectInBackground (options, userOpName) = + member __.CheckProjectInBackground (options, userOpName) = reactor.SetBackgroundOp (Some (userOpName, "CheckProjectInBackground", options.ProjectFileName, (fun ctok ct -> // The creation of the background builder can't currently be cancelled match getOrCreateBuilder (ctok, options, userOpName) |> Cancellable.run ct with @@ -2870,25 +836,30 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC | ValueOrCancelled.Value v -> v | ValueOrCancelled.Cancelled _ -> false))) - member bc.StopBackgroundCompile () = + member __.StopBackgroundCompile () = reactor.SetBackgroundOp(None) - member bc.WaitForBackgroundCompile() = + member __.WaitForBackgroundCompile() = reactor.WaitForBackgroundOpCompletion() - member bc.CompleteAllQueuedOps() = + member __.CompleteAllQueuedOps() = reactor.CompleteAllQueuedOps() - member bc.Reactor = reactor - member bc.ReactorOps = reactorOps - member bc.BeforeBackgroundFileCheck = beforeFileChecked.Publish - member bc.FileParsed = fileParsed.Publish - member bc.FileChecked = fileChecked.Publish - member bc.ProjectChecked = projectChecked.Publish + member __.Reactor = reactor + + member __.ReactorOps = reactorOps + + member __.BeforeBackgroundFileCheck = beforeFileChecked.Publish + + member __.FileParsed = fileParsed.Publish - member bc.CurrentQueueLength = reactor.CurrentQueueLength + member __.FileChecked = fileChecked.Publish - member bc.ClearCachesAsync (userOpName) = + member __.ProjectChecked = projectChecked.Publish + + member __.CurrentQueueLength = reactor.CurrentQueueLength + + member __.ClearCachesAsync (userOpName) = reactor.EnqueueAndAwaitOpAsync (userOpName, "ClearCachesAsync", "", fun ctok -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.Clear ltok @@ -2899,7 +870,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC scriptClosureCacheLock.AcquireLock (fun ltok -> scriptClosureCache.Clear ltok) cancellable.Return ()) - member bc.DownsizeCaches(userOpName) = + member __.DownsizeCaches(userOpName) = reactor.EnqueueAndAwaitOpAsync (userOpName, "DownsizeCaches", "", fun ctok -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.Resize(ltok, keepStrongly=1) @@ -2911,25 +882,27 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC cancellable.Return ()) member __.FrameworkImportsCache = frameworkTcImportsCache + member __.ImplicitlyStartBackgroundWork with get() = implicitlyStartBackgroundWork and set v = implicitlyStartBackgroundWork <- v + static member GlobalForegroundParseCountStatistic = foregroundParseCount + static member GlobalForegroundTypeCheckCountStatistic = foregroundTypeCheckCount -//---------------------------------------------------------------------------- -// FSharpChecker -// - -[] -[] -// There is typically only one instance of this type in a Visual Studio process. -type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors) = +[] +// There is typically only one instance of this type in an IDE process. +type FSharpChecker(legacyReferenceResolver, + projectCacheSize, + keepAssemblyContents, + keepAllBackgroundResolutions, + tryGetMetadataSnapshot, + suggestNamesForErrors) = let backgroundCompiler = BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors) static let globalInstance = lazy FSharpChecker.Create() - - + // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.braceMatchCache. Most recently used cache for brace matching. Accessed on the // background UI thread, not on the compiler thread. // @@ -2937,16 +910,18 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten let braceMatchCache = MruCache(braceMatchCacheSize, areSimilar = AreSimilarForParsing, areSame = AreSameForParsing) let mutable maxMemoryReached = false + let mutable maxMB = maxMBDefault + let maxMemEvent = new Event() /// Instantiate an interactive checker. static member Create(?projectCacheSize, ?keepAssemblyContents, ?keepAllBackgroundResolutions, ?legacyReferenceResolver, ?tryGetMetadataSnapshot, ?suggestNamesForErrors) = let legacyReferenceResolver = - match legacyReferenceResolver with - | None -> SimulatedMSBuildReferenceResolver.GetBestAvailableResolver() + match legacyReferenceResolver with | Some rr -> rr + | None -> SimulatedMSBuildReferenceResolver.getResolver() let keepAssemblyContents = defaultArg keepAssemblyContents false let keepAllBackgroundResolutions = defaultArg keepAllBackgroundResolutions true @@ -2955,16 +930,16 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten let suggestNamesForErrors = defaultArg suggestNamesForErrors false new FSharpChecker(legacyReferenceResolver, projectCacheSizeReal,keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors) - member ic.ReferenceResolver = legacyReferenceResolver + member __.ReferenceResolver = legacyReferenceResolver - member ic.MatchBraces(filename, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) = + member __.MatchBraces(filename, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" let hash = sourceText.GetHashCode() async { match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options)) with | Some res -> return res | None -> - let res = Parser.matchBraces(sourceText, filename, options, userOpName, suggestNamesForErrors) + let res = ParseAndCheckFile.matchBraces(sourceText, filename, options, userOpName, suggestNamesForErrors) braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options), res) return res } @@ -2989,27 +964,27 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten let parsingOptions, _ = ic.GetParsingOptionsFromProjectOptions(options) ic.ParseFile(filename, SourceText.ofString source, parsingOptions, userOpName) - member ic.GetBackgroundParseResultsForFileInProject (filename,options, ?userOpName: string) = + member __.GetBackgroundParseResultsForFileInProject (filename,options, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetBackgroundParseResultsForFileInProject(filename, options, userOpName) - member ic.GetBackgroundCheckResultsForFileInProject (filename,options, ?userOpName: string) = + member __.GetBackgroundCheckResultsForFileInProject (filename,options, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetBackgroundCheckResultsForFileInProject(filename,options, userOpName) /// Try to get recent approximate type check results for a file. - member ic.TryGetRecentCheckResultsForFile(filename: string, options:FSharpProjectOptions, ?sourceText, ?userOpName: string) = + member __.TryGetRecentCheckResultsForFile(filename: string, options:FSharpProjectOptions, ?sourceText, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.TryGetRecentCheckResultsForFile(filename,options,sourceText, userOpName) - member ic.Compile(argv: string[], ?userOpName: string) = + member __.Compile(argv: string[], ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.Reactor.EnqueueAndAwaitOpAsync (userOpName, "Compile", "", fun ctok -> cancellable { return CompileHelpers.compileFromArgs (ctok, argv, legacyReferenceResolver, None, None) }) - member ic.Compile (ast:ParsedInput list, assemblyName:string, outFile:string, dependencies:string list, ?pdbFile:string, ?executable:bool, ?noframework:bool, ?userOpName: string) = + member __.Compile (ast:ParsedInput list, assemblyName:string, outFile:string, dependencies:string list, ?pdbFile:string, ?executable:bool, ?noframework:bool, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.Reactor.EnqueueAndAwaitOpAsync (userOpName, "Compile", assemblyName, fun ctok -> cancellable { @@ -3018,7 +993,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten } ) - member ic.CompileToDynamicAssembly (otherFlags: string[], execute: (TextWriter * TextWriter) option, ?userOpName: string) = + member __.CompileToDynamicAssembly (otherFlags: string[], execute: (TextWriter * TextWriter) option, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.Reactor.EnqueueAndAwaitOpAsync (userOpName, "CompileToDynamicAssembly", "", fun ctok -> cancellable { @@ -3046,7 +1021,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten } ) - member ic.CompileToDynamicAssembly (asts:ParsedInput list, assemblyName:string, dependencies:string list, execute: (TextWriter * TextWriter) option, ?debug:bool, ?noframework:bool, ?userOpName: string) = + member __.CompileToDynamicAssembly (asts:ParsedInput list, assemblyName:string, dependencies:string list, execute: (TextWriter * TextWriter) option, ?debug:bool, ?noframework:bool, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.Reactor.EnqueueAndAwaitOpAsync (userOpName, "CompileToDynamicAssembly", assemblyName, fun ctok -> cancellable { @@ -3086,7 +1061,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten member ic.InvalidateAll() = ic.ClearCaches() - member ic.ClearCachesAsync(?userOpName: string) = + member __.ClearCachesAsync(?userOpName: string) = let utok = AssumeAnyCallerThreadWithoutEvidence() let userOpName = defaultArg userOpName "Unknown" braceMatchCache.Clear(utok) @@ -3095,7 +1070,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten member ic.ClearCaches(?userOpName) = ic.ClearCachesAsync(?userOpName=userOpName) |> Async.Start // this cache clearance is not synchronous, it will happen when the background op gets run - member ic.CheckMaxMemoryReached() = + member __.CheckMaxMemoryReached() = if not maxMemoryReached && System.GC.GetTotalMemory(false) > int64 maxMB * 1024L * 1024L then Trace.TraceWarning("!!!!!!!! MAX MEMORY REACHED, DOWNSIZING F# COMPILER CACHES !!!!!!!!!!!!!!!") // If the maxMB limit is reached, drastic action is taken @@ -3117,18 +1092,18 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten /// This function is called when the configuration is known to have changed for reasons not encoded in the ProjectOptions. /// For example, dependent references may have been deleted or created. - member ic.InvalidateConfiguration(options: FSharpProjectOptions, ?startBackgroundCompile, ?userOpName: string) = + member __.InvalidateConfiguration(options: FSharpProjectOptions, ?startBackgroundCompile, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.InvalidateConfiguration(options, startBackgroundCompile, userOpName) /// This function is called when a project has been cleaned, and thus type providers should be refreshed. - member ic.NotifyProjectCleaned(options: FSharpProjectOptions, ?userOpName: string) = + member __.NotifyProjectCleaned(options: FSharpProjectOptions, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.NotifyProjectCleaned (options, userOpName) /// Typecheck a source code file, returning a handle to the results of the /// parse including the reconstructed types in the file. - member ic.CheckFileInProjectAllowingStaleCachedResults(parseResults:FSharpParseFileResults, filename:string, fileVersion:int, source:string, options:FSharpProjectOptions, ?textSnapshotInfo:obj, ?userOpName: string) = + member __.CheckFileInProjectAllowingStaleCachedResults(parseResults:FSharpParseFileResults, filename:string, fileVersion:int, source:string, options:FSharpProjectOptions, ?textSnapshotInfo:obj, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.CheckFileInProjectAllowingStaleCachedResults(parseResults,filename,fileVersion,SourceText.ofString source,options,textSnapshotInfo, userOpName) @@ -3152,11 +1127,11 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten backgroundCompiler.ParseAndCheckProject(options, userOpName) /// For a given script file, get the ProjectOptions implied by the #load closure - member ic.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib, ?useSdkRefs, ?assumeDotNetFramework, ?extraProjectInfo: obj, ?optionsStamp: int64, ?userOpName: string) = + member __.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib, ?useSdkRefs, ?assumeDotNetFramework, ?extraProjectInfo: obj, ?optionsStamp: int64, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetProjectOptionsFromScript(filename, source, loadedTimeStamp, otherFlags, useFsiAuxLib, useSdkRefs, assumeDotNetFramework, extraProjectInfo, optionsStamp, userOpName) - member ic.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?extraProjectInfo: obj) = + member __.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?extraProjectInfo: obj) = let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading { ProjectFileName = projectFileName ProjectId = None @@ -3171,7 +1146,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten ExtraProjectInfo=extraProjectInfo Stamp = None } - member ic.GetParsingOptionsFromCommandLineArgs(initialSourceFiles, argv, ?isInteractive) = + member __.GetParsingOptionsFromCommandLineArgs(initialSourceFiles, argv, ?isInteractive) = let isInteractive = defaultArg isInteractive false use errorScope = new ErrorScope() let tcConfigBuilder = TcConfigBuilder.Initial @@ -3184,7 +1159,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten ic.GetParsingOptionsFromCommandLineArgs([], argv, ?isInteractive=isInteractive) /// Begin background parsing the given project. - member ic.StartBackgroundCompile(options, ?userOpName) = + member __.StartBackgroundCompile(options, ?userOpName) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.CheckProjectInBackground(options, userOpName) @@ -3193,37 +1168,45 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten ic.StartBackgroundCompile(options, ?userOpName=userOpName) /// Stop the background compile. - member ic.StopBackgroundCompile() = + member __.StopBackgroundCompile() = backgroundCompiler.StopBackgroundCompile() /// Block until the background compile finishes. // // This is for unit testing only - member ic.WaitForBackgroundCompile() = backgroundCompiler.WaitForBackgroundCompile() + member __.WaitForBackgroundCompile() = backgroundCompiler.WaitForBackgroundCompile() // Publish the ReactorOps from the background compiler for internal use member ic.ReactorOps = backgroundCompiler.ReactorOps - member ic.CurrentQueueLength = backgroundCompiler.CurrentQueueLength + member __.CurrentQueueLength = backgroundCompiler.CurrentQueueLength + + member __.BeforeBackgroundFileCheck = backgroundCompiler.BeforeBackgroundFileCheck + + member __.FileParsed = backgroundCompiler.FileParsed + + member __.FileChecked = backgroundCompiler.FileChecked + + member __.ProjectChecked = backgroundCompiler.ProjectChecked - member ic.BeforeBackgroundFileCheck = backgroundCompiler.BeforeBackgroundFileCheck - member ic.FileParsed = backgroundCompiler.FileParsed - member ic.FileChecked = backgroundCompiler.FileChecked - member ic.ProjectChecked = backgroundCompiler.ProjectChecked - member ic.ImplicitlyStartBackgroundWork with get() = backgroundCompiler.ImplicitlyStartBackgroundWork and set v = backgroundCompiler.ImplicitlyStartBackgroundWork <- v - member ic.PauseBeforeBackgroundWork with get() = Reactor.Singleton.PauseBeforeBackgroundWork and set v = Reactor.Singleton.PauseBeforeBackgroundWork <- v + member __.ImplicitlyStartBackgroundWork with get() = backgroundCompiler.ImplicitlyStartBackgroundWork and set v = backgroundCompiler.ImplicitlyStartBackgroundWork <- v + + member __.PauseBeforeBackgroundWork with get() = Reactor.Singleton.PauseBeforeBackgroundWork and set v = Reactor.Singleton.PauseBeforeBackgroundWork <- v static member GlobalForegroundParseCountStatistic = BackgroundCompiler.GlobalForegroundParseCountStatistic + static member GlobalForegroundTypeCheckCountStatistic = BackgroundCompiler.GlobalForegroundTypeCheckCountStatistic - member ic.MaxMemoryReached = maxMemEvent.Publish - member ic.MaxMemory with get() = maxMB and set v = maxMB <- v + member __.MaxMemoryReached = maxMemEvent.Publish + + member __.MaxMemory with get() = maxMB and set v = maxMB <- v static member Instance with get() = globalInstance.Force() + member internal __.FrameworkImportsCache = backgroundCompiler.FrameworkImportsCache /// Tokenize a single line, returning token information and a tokenization state represented by an integer - member x.TokenizeLine (line: string, state: FSharpTokenizerLexState) = + member __.TokenizeLine (line: string, state: FSharpTokenizerLexState) = let tokenizer = FSharpSourceTokenizer([], None) let lineTokenizer = tokenizer.CreateLineTokenizer line let mutable state = (None, state) @@ -3243,51 +1226,6 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten yield tokens |] tokens - -type FsiInteractiveChecker(legacyReferenceResolver, reactorOps: IReactorOperations, tcConfig: TcConfig, tcGlobals, tcImports, tcState) = - let keepAssemblyContents = false - - member __.ParseAndCheckInteraction (ctok, sourceText: ISourceText, ?userOpName: string) = - async { - let userOpName = defaultArg userOpName "Unknown" - let filename = Path.Combine(tcConfig.implicitIncludeDir, "stdin.fsx") - let suggestNamesForErrors = true // Will always be true, this is just for readability - // Note: projectSourceFiles is only used to compute isLastCompiland, and is ignored if Build.IsScript(mainInputFileName) is true (which it is in this case). - let parsingOptions = FSharpParsingOptions.FromTcConfig(tcConfig, [| filename |], true) - let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile (sourceText, filename, parsingOptions, userOpName, suggestNamesForErrors) - let dependencyFiles = [| |] // interactions have no dependencies - let parseResults = FSharpParseFileResults(parseErrors, parseTreeOpt, parseHadErrors = anyErrors, dependencyFiles = dependencyFiles) - - let backgroundDiagnostics = [| |] - let reduceMemoryUsage = ReduceMemoryFlag.Yes - let assumeDotNetFramework = true - - let applyCompilerOptions tcConfigB = - let fsiCompilerOptions = CompileOptions.GetCoreFsiCompilerOptions tcConfigB - CompileOptions.ParseCompilerOptions (ignore, fsiCompilerOptions, [ ]) - - let loadClosure = LoadClosure.ComputeClosureOfScriptText(ctok, legacyReferenceResolver, defaultFSharpBinariesDir, filename, sourceText, CodeContext.Editing, tcConfig.useSimpleResolution, tcConfig.useFsiAuxLib, tcConfig.useSdkRefs, new Lexhelp.LexResourceManager(), applyCompilerOptions, assumeDotNetFramework, tryGetMetadataSnapshot=(fun _ -> None), reduceMemoryUsage=reduceMemoryUsage) - let! tcErrors, tcFileResult = Parser.CheckOneFile(parseResults, sourceText, filename, "project", tcConfig, tcGlobals, tcImports, tcState, Map.empty, Some loadClosure, backgroundDiagnostics, reactorOps, None, userOpName, suggestNamesForErrors) - - return - match tcFileResult with - | Parser.TypeCheckAborted.No tcFileInfo -> - let errors = [| yield! parseErrors; yield! tcErrors |] - let typeCheckResults = FSharpCheckFileResults (filename, errors, Some tcFileInfo, dependencyFiles, None, reactorOps, false) - let projectResults = - FSharpCheckProjectResults (filename, Some tcConfig, keepAssemblyContents, errors, - Some(tcGlobals, tcImports, tcFileInfo.ThisCcu, tcFileInfo.CcuSigForFile, - [tcFileInfo.ScopeSymbolUses], None, None, mkSimpleAssemblyRef "stdin", - tcState.TcEnvFromImpls.AccessRights, None, dependencyFiles)) - parseResults, typeCheckResults, projectResults - | _ -> - failwith "unexpected aborted" - } - -//---------------------------------------------------------------------------- -// CompilerEnvironment, DebuggerEnvironment -// - type CompilerEnvironment = static member BinFolderOfDefaultFSharpCompiler(?probePoint) = FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(probePoint) diff --git a/src/fsharp/service/service.fsi b/src/fsharp/service/service.fsi index f5e2d373d6e..ec368942d7e 100755 --- a/src/fsharp/service/service.fsi +++ b/src/fsharp/service/service.fsi @@ -8,308 +8,16 @@ namespace FSharp.Compiler.SourceCodeServices open System open System.IO -open System.Collections.Generic -open FSharp.Compiler.AbstractIL -open FSharp.Compiler.AbstractIL.IL -open FSharp.Compiler.AbstractIL.Internal.Library open FSharp.Compiler.AbstractIL.ILBinaryReader open FSharp.Compiler open FSharp.Compiler.Ast -open FSharp.Compiler.Driver -open FSharp.Compiler.ErrorLogger open FSharp.Compiler.Range -open FSharp.Compiler.TcGlobals -open FSharp.Compiler.NameResolution -open FSharp.Compiler.CompileOps -open FSharp.Compiler.Infos -open FSharp.Compiler.InfoReader -open FSharp.Compiler.Tast -open FSharp.Compiler.Tastops open FSharp.Compiler.Text -/// Represents the reason why the GetDeclarationLocation operation failed. -[] -type public FSharpFindDeclFailureReason = - - /// Generic reason: no particular information about error apart from a message - | Unknown of message: string - - /// Source code file is not available - | NoSourceCode - - /// Trying to find declaration of ProvidedType without TypeProviderDefinitionLocationAttribute - | ProvidedType of string - - /// Trying to find declaration of ProvidedMember without TypeProviderDefinitionLocationAttribute - | ProvidedMember of string - -/// Represents the result of the GetDeclarationLocation operation. -[] -type public FSharpFindDeclResult = - /// Indicates a declaration location was not found, with an additional reason - | DeclNotFound of FSharpFindDeclFailureReason - /// Indicates a declaration location was found - | DeclFound of range - /// Indicates an external declaration was found - | ExternalDecl of assembly : string * externalSym : ExternalSymbol - -/// Represents the checking context implied by the ProjectOptions -[] -type public FSharpProjectContext = - /// Get the resolution and full contents of the assemblies referenced by the project options - member GetReferencedAssemblies : unit -> FSharpAssembly list - - /// Get the accessibility rights for this project context w.r.t. InternalsVisibleTo attributes granting access to other assemblies - member AccessibilityRights : FSharpAccessibilityRights - - -[] -type public SemanticClassificationType = - | ReferenceType - | ValueType - | UnionCase - | Function - | Property - | MutableVar - | Module - | Printf - | ComputationExpression - | IntrinsicFunction - | Enumeration - | Interface - | TypeArgument - | Operator - | Disposable - -/// A handle to the results of CheckFileInProject. -[] -type public FSharpCheckFileResults = - /// The errors returned by parsing a source file. - member Errors : FSharpErrorInfo[] - - /// Get a view of the contents of the assembly up to and including the file just checked - member PartialAssemblySignature : FSharpAssemblySignature - - /// Get the resolution of the ProjectOptions - member ProjectContext : FSharpProjectContext - - /// Indicates whether type checking successfully occurred with some results returned. If false, indicates that - /// an unrecoverable error in earlier checking/parsing/resolution steps. - member HasFullTypeCheckInfo: bool - - /// Tries to get the current successful TcImports. This is only used in testing. Do not use it for other stuff. - member internal TryGetCurrentTcImports: unit -> TcImports option - - /// Indicates the set of files which must be watched to accurately track changes that affect these results, - /// Clients interested in reacting to updates to these files should watch these files and take actions as described - /// in the documentation for compiler service. - member DependencyFiles : string[] - - /// Get the items for a declaration list - /// - /// - /// If this is present, it is used to filter declarations based on location in the - /// parse tree, specifically at 'open' declarations, 'inherit' of class or interface - /// 'record field' locations and r.h.s. of 'range' operator a..b - /// - /// The line number where the completion is happening - /// - /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. - /// - /// - /// The text of the line where the completion is happening. This is only used to make a couple - /// of adhoc corrections to completion accuracy (e.g. checking for "..") - /// - /// - /// Function that returns all entities from current and referenced assemblies. - /// - /// - /// If text has been used from a captured name resolution from the typecheck, then - /// callback to the client to check if the text has changed. If it has, then give up - /// and assume that we're going to repeat the operation later on. - /// - /// An optional string used for tracing compiler operations associated with this request. - member GetDeclarationListInfo : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * ?getAllEntities: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async - - /// Get the items for a declaration list in FSharpSymbol format - /// - /// - /// If this is present, it is used to filter declarations based on location in the - /// parse tree, specifically at 'open' declarations, 'inherit' of class or interface - /// 'record field' locations and r.h.s. of 'range' operator a..b - /// - /// The line number where the completion is happening - /// - /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. - /// - /// - /// The text of the line where the completion is happening. This is only used to make a couple - /// of adhoc corrections to completion accuracy (e.g. checking for "..") - /// - /// - /// Function that returns all entities from current and referenced assemblies. - /// - /// - /// If text has been used from a captured name resolution from the typecheck, then - /// callback to the client to check if the text has changed. If it has, then give up - /// and assume that we're going to repeat the operation later on. - /// - /// An optional string used for tracing compiler operations associated with this request. - member GetDeclarationListSymbols : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * ?getAllEntities: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async - - - /// Compute a formatted tooltip for the given location - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// Used to discriminate between 'identifiers', 'strings' and others. For strings, an attempt is made to give a tooltip for a #r "..." location. Use a value from FSharpTokenInfo.Tag, or FSharpTokenTag.Identifier, unless you have other information available. - /// An optional string used for tracing compiler operations associated with this request. - member GetStructuredToolTipText : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int * ?userOpName: string -> Async - - /// Compute a formatted tooltip for the given location - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// Used to discriminate between 'identifiers', 'strings' and others. For strings, an attempt is made to give a tooltip for a #r "..." location. Use a value from FSharpTokenInfo.Tag, or FSharpTokenTag.Identifier, unless you have other information available. - /// An optional string used for tracing compiler operations associated with this request. - member GetToolTipText : line:int * colAtEndOfNames:int * lineText:string * names:string list * tokenTag:int * ?userOpName: string -> Async - - /// Compute the Visual Studio F1-help key identifier for the given location, based on name resolution results - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// An optional string used for tracing compiler operations associated with this request. - member GetF1Keyword : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async - - - /// Compute a set of method overloads to show in a dialog relevant to the given code location. - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// An optional string used for tracing compiler operations associated with this request. - member GetMethods : line:int * colAtEndOfNames:int * lineText:string * names:string list option * ?userOpName: string -> Async - - /// Compute a set of method overloads to show in a dialog relevant to the given code location. The resulting method overloads are returned as symbols. - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// An optional string used for tracing compiler operations associated with this request. - member GetMethodsAsSymbols : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async - - /// Resolve the names at the given location to the declaration location of the corresponding construct. - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// If not given, then get the location of the symbol. If false, then prefer the location of the corresponding symbol in the implementation of the file (rather than the signature if present). If true, prefer the location of the corresponding symbol in the signature of the file (rather than the implementation). - /// An optional string used for tracing compiler operations associated with this request. - member GetDeclarationLocation : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?preferFlag:bool * ?userOpName: string -> Async - - - /// Resolve the names at the given location to a use of symbol. - /// - /// The line number where the information is being requested. - /// The column number at the end of the identifiers where the information is being requested. - /// The text of the line where the information is being requested. - /// The identifiers at the location where the information is being requested. - /// An optional string used for tracing compiler operations associated with this request. - member GetSymbolUseAtLocation : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async - - /// Get any extra colorization info that is available after the typecheck - member GetSemanticClassification : range option -> (range * SemanticClassificationType)[] - - /// Get the locations of format specifiers - [] - member GetFormatSpecifierLocations : unit -> range[] - - /// Get the locations of and number of arguments associated with format specifiers - member GetFormatSpecifierLocationsAndArity : unit -> (range*int)[] - - /// Get all textual usages of all symbols throughout the file - member GetAllUsesOfAllSymbolsInFile : unit -> Async - - /// Get the textual usages that resolved to the given symbol throughout the file - member GetUsesOfSymbolInFile : symbol:FSharpSymbol -> Async - - member internal GetVisibleNamespacesAndModulesAtPoint : pos -> Async - - /// Find the most precise display environment for the given line and column. - member internal GetDisplayEnvForPos : pos : pos -> Async - - /// Determines if a long ident is resolvable at a specific point. - /// An optional string used for tracing compiler operations associated with this request. - member internal IsRelativeNameResolvable: cursorPos : pos * plid : string list * item: Item * ?userOpName: string -> Async - - /// Determines if a long ident is resolvable at a specific point. - /// An optional string used for tracing compiler operations associated with this request. - member IsRelativeNameResolvableFromSymbol: cursorPos : pos * plid : string list * symbol: FSharpSymbol * ?userOpName: string -> Async - - /// Represents complete typechecked implementation file, including its typechecked signatures if any. - member ImplementationFile: FSharpImplementationFileContents option - - /// Open declarations in the file, including auto open modules. - member OpenDeclarations: FSharpOpenDeclaration[] - -/// A handle to the results of CheckFileInProject. -[] -type public FSharpCheckProjectResults = - - /// The errors returned by processing the project - member Errors: FSharpErrorInfo[] - - /// Get a view of the overall signature of the assembly. Only valid to use if HasCriticalErrors is false. - member AssemblySignature: FSharpAssemblySignature - - /// Get a view of the overall contents of the assembly. Only valid to use if HasCriticalErrors is false. - member AssemblyContents: FSharpAssemblyContents - - /// Get an optimized view of the overall contents of the assembly. Only valid to use if HasCriticalErrors is false. - member GetOptimizedAssemblyContents: unit -> FSharpAssemblyContents - - /// Get the resolution of the ProjectOptions - member ProjectContext: FSharpProjectContext - - /// Get the textual usages that resolved to the given symbol throughout the project - member GetUsesOfSymbol: symbol:FSharpSymbol -> Async - - /// Get all textual usages of all symbols throughout the project - member GetAllUsesOfAllSymbols: unit -> Async - - /// Indicates if critical errors existed in the project options - member HasCriticalErrors: bool - - /// Indicates the set of files which must be watched to accurately track changes that affect these results, - /// Clients interested in reacting to updates to these files should watch these files and take actions as described - /// in the documentation for compiler service. - member DependencyFiles: string[] - /// Unused in this API type public UnresolvedReferencesSet -/// Options used to determine active --define conditionals and other options relevant to parsing files in a project -type public FSharpParsingOptions = - { - SourceFiles: string[] - ConditionalCompilationDefines: string list - ErrorSeverityOptions: FSharpErrorSeverityOptions - IsInteractive: bool - LightSyntax: bool option - CompilingFsLib: bool - IsExe: bool - } - static member Default: FSharpParsingOptions - /// A set of information describing a project or script build configuration. type public FSharpProjectOptions = { @@ -357,12 +65,6 @@ type public FSharpProjectOptions = Stamp: int64 option } -/// The result of calling TypeCheckResult including the possibility of abort and background compiler not caught up. -[] -type public FSharpCheckFileAnswer = - | Aborted // because cancellation caused an abandonment of the operation - | Succeeded of FSharpCheckFileResults - [] /// Used to parse and check F# source code. type public FSharpChecker = @@ -711,15 +413,6 @@ type public FSharpChecker = /// Tokenize an entire file, line by line member TokenizeFile: source:string -> FSharpTokenInfo [] [] - - -// An object to typecheck source in a given typechecking environment. -// Used internally to provide intellisense over F# Interactive. -type internal FsiInteractiveChecker = - internal new : ReferenceResolver.Resolver * ops: IReactorOperations * tcConfig: TcConfig * tcGlobals: TcGlobals * tcImports: TcImports * tcState: TcState -> FsiInteractiveChecker - - /// An optional string used for tracing compiler operations associated with this request. - member internal ParseAndCheckInteraction : CompilationThreadToken * sourceText:ISourceText * ?userOpName: string -> Async /// Information about the compilation environment [] diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index d80beda39cc..f6b93d4e324 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -34,7 +34,7 @@ type FSharpAccessibility(a:Accessibility, ?isProtected) = | _ when List.forall isInternalCompPath p -> Internal | _ -> Private - member __.IsPublic = not isProtected && match a with Public -> true | _ -> false + member __.IsPublic = not isProtected && match a with TAccess [] -> true | _ -> false member __.IsPrivate = not isProtected && match a with Private -> true | _ -> false diff --git a/src/fsharp/symbols/Symbols.fsi b/src/fsharp/symbols/Symbols.fsi index bd5e0b188d5..c8580fedf3a 100644 --- a/src/fsharp/symbols/Symbols.fsi +++ b/src/fsharp/symbols/Symbols.fsi @@ -25,17 +25,20 @@ type internal SymbolEnv = type public FSharpAccessibility = internal new: Accessibility * ?isProtected: bool -> FSharpAccessibility - /// Indicates the symbol has public accessibility - member IsPublic : bool + /// Indicates the symbol has public accessibility. + member IsPublic: bool - /// Indicates the symbol has private accessibility - member IsPrivate : bool + /// Indicates the symbol has private accessibility. + member IsPrivate: bool - /// Indicates the symbol has internal accessibility - member IsInternal : bool + /// Indicates the symbol has internal accessibility. + member IsInternal: bool + + /// Indicates the symbol has protected accessibility. + member IsProtected: bool /// The underlying Accessibility - member internal Contents : Accessibility + member internal Contents: Accessibility /// Represents the information needed to format types and other information in a style diff --git a/src/utils/CompilerLocationUtils.fs b/src/utils/CompilerLocationUtils.fs index 41de8bb911c..df0c7123b4d 100644 --- a/src/utils/CompilerLocationUtils.fs +++ b/src/utils/CompilerLocationUtils.fs @@ -220,7 +220,6 @@ module internal FSharpEnvironment = // For the prototype compiler, we can just use the current domain tryCurrentDomain() with e -> - System.Diagnostics.Debug.Assert(false, "Error while determining default location of F# compiler") None diff --git a/tests/FSharp.Compiler.LanguageServer.UnitTests/FSharp.Compiler.LanguageServer.UnitTests.fsproj b/tests/FSharp.Compiler.LanguageServer.UnitTests/FSharp.Compiler.LanguageServer.UnitTests.fsproj index a5e8bdf3264..78c2689eb24 100644 --- a/tests/FSharp.Compiler.LanguageServer.UnitTests/FSharp.Compiler.LanguageServer.UnitTests.fsproj +++ b/tests/FSharp.Compiler.LanguageServer.UnitTests/FSharp.Compiler.LanguageServer.UnitTests.fsproj @@ -11,6 +11,7 @@ + diff --git a/tests/FSharp.Compiler.LanguageServer.UnitTests/ProtocolTests.fs b/tests/FSharp.Compiler.LanguageServer.UnitTests/ProtocolTests.fs index 75c6e15cee7..4d0a789b9cd 100644 --- a/tests/FSharp.Compiler.LanguageServer.UnitTests/ProtocolTests.fs +++ b/tests/FSharp.Compiler.LanguageServer.UnitTests/ProtocolTests.fs @@ -26,15 +26,34 @@ type ProtocolTests() = startInfo.RedirectStandardOutput <- true let proc = Process.Start(startInfo) - // create a fake client + // create a fake client over stdin/stdout let client = new JsonRpc(proc.StandardInput.BaseStream, proc.StandardOutput.BaseStream) client.StartListening() // initialize - let! capabilitites = client.InvokeAsync("initialize", "") |> Async.AwaitTask - Assert.True(capabilitites.hoverProvider) + let capabilities = + { ClientCapabilities.workspace = None + textDocument = None + experimental = None + supportsVisualStudioExtensions = None } + let! result = + client.InvokeAsync( + "initialize", // method + 0, // processId + "rootPath", + "rootUri", + null, // initializationOptions + capabilities, // client capabilities + "none") // trace + |> Async.AwaitTask + Assert.True(result.capabilities.hoverProvider) + do! client.NotifyAsync("initialized") |> Async.AwaitTask - // shutdown the server - do! client.NotifyAsync("shutdown") |> Async.AwaitTask + // shutdown + let! shutdownResponse = client.InvokeAsync("shutdown") |> Async.AwaitTask + Assert.IsNull(shutdownResponse) + + // exit + do! client.NotifyAsync("exit") |> Async.AwaitTask if not (proc.WaitForExit(5000)) then failwith "Expected server process to exit." } |> Async.StartAsTask :> Task diff --git a/tests/FSharp.Compiler.LanguageServer.UnitTests/SerializationTests.fs b/tests/FSharp.Compiler.LanguageServer.UnitTests/SerializationTests.fs new file mode 100644 index 00000000000..5d8457c4e5d --- /dev/null +++ b/tests/FSharp.Compiler.LanguageServer.UnitTests/SerializationTests.fs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.LanguageServer.UnitTests + +open System +open FSharp.Compiler.LanguageServer +open NUnit.Framework +open Newtonsoft.Json + +[] +type SerializationTests() = + + let verifyRoundTrip (str: string) (typ: Type) = + let deserialized = JsonConvert.DeserializeObject(str, typ) + let roundTripped = JsonConvert.SerializeObject(deserialized) + Assert.AreEqual(str, roundTripped) + + let verifyRoundTripWithConverter (str: string) (typ: Type) (converter: JsonConverter) = + let deserialized = JsonConvert.DeserializeObject(str, typ, converter) + let roundTripped = JsonConvert.SerializeObject(deserialized, converter) + Assert.AreEqual(str, roundTripped) + + [] + member __.``Discriminated union as lower-case string``() = + verifyRoundTrip "\"plaintext\"" typeof + verifyRoundTrip "\"markdown\"" typeof + + [] + member __.``Option<'T> as obj/null``() = + verifyRoundTripWithConverter "1" typeof> (JsonOptionConverter()) + verifyRoundTripWithConverter "null" typeof> (JsonOptionConverter()) + verifyRoundTripWithConverter "{\"contents\":{\"kind\":\"plaintext\",\"value\":\"v\"},\"range\":{\"start\":{\"line\":1,\"character\":2},\"end\":{\"line\":3,\"character\":4}}}" typeof> (JsonOptionConverter()) + verifyRoundTripWithConverter "null" typeof> (JsonOptionConverter()) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule2.fs index 63ae937b561..342d5678bf2 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule2.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule2.fs @@ -102,7 +102,16 @@ type ListModule02() = let longerList = [1; 2] CheckThrowsArgumentException (fun () -> List.map3 funcInt shortList shortList longerList |> ignore) CheckThrowsArgumentException (fun () -> List.map3 funcInt shortList longerList shortList |> ignore) - CheckThrowsArgumentException (fun () -> List.map3 funcInt shortList shortList longerList |> ignore) + CheckThrowsArgumentException (fun () -> List.map3 funcInt shortList shortList longerList |> ignore) + + // exception message checking + let expectedMessage = + "The lists had different lengths.\n" + + sprintf " list1.Length = %i, list2.Length = %i, list3.Length = %i" shortList.Length shortList.Length longerList.Length + + Environment.NewLine + "Parameter name: list1, list2, list3" + let ex = Assert.Throws(typeof, + (fun () -> List.map3 funcInt shortList shortList longerList |> ignore)) + Assert.AreEqual(expectedMessage, ex.Message) // empty List let resultEpt = List.map3 funcInt List.empty List.empty List.empty diff --git a/tests/fsharpqa/testenv/src/HostedCompilerServer/Program.fs b/tests/fsharpqa/testenv/src/HostedCompilerServer/Program.fs index 440af1b7d75..858382b9f26 100644 --- a/tests/fsharpqa/testenv/src/HostedCompilerServer/Program.fs +++ b/tests/fsharpqa/testenv/src/HostedCompilerServer/Program.fs @@ -25,7 +25,7 @@ type HostedCompilerServer(port) = let (|FscCompile|Unknown|) (message : string) = match message.Split([|MessageDelimiter|], StringSplitOptions.RemoveEmptyEntries) with | [|directory; commandLine|] -> - let legacyReferenceResolver = MSBuildReferenceResolver.Resolver + let legacyReferenceResolver = LegacyMSBuildReferenceResolver.getResolver() let args = CompilerHelpers.parseCommandLine commandLine log <| sprintf "Args parsed as [%s]" (String.Join("] [", args)) log <| sprintf "Dir parsed as [%s]" directory diff --git a/tests/service/ProjectAnalysisTests.fs b/tests/service/ProjectAnalysisTests.fs index 16ef433e691..c9635c70830 100644 --- a/tests/service/ProjectAnalysisTests.fs +++ b/tests/service/ProjectAnalysisTests.fs @@ -5291,7 +5291,7 @@ let ``Test line directives in foreground analysis`` () = // see https://github.c for e in checkResults1.Errors do printfn "ProjectLineDirectives checkResults1 error file: <<<%s>>>" e.FileName - [ for e in checkResults1.Errors -> e.StartLineAlternate, e.EndLineAlternate, e.FileName ] |> shouldEqual [(4, 4, ProjectLineDirectives.fileName1)] + [ for e in checkResults1.Errors -> e.StartLineAlternate, e.EndLineAlternate, e.FileName ] |> shouldEqual [(5, 5, ProjectLineDirectives.fileName1)] //------------------------------------------------------ diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 24c55cee890..ac4e1565617 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -7,14 +7,13 @@ module Tests.Service.ServiceUntypedParseTests #endif -open System open System.IO -open System.Text -open NUnit.Framework +open FsUnit +open FSharp.Compiler.Ast open FSharp.Compiler.Range open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Service.Tests.Common -open Tests.Service +open NUnit.Framework let [] private Marker = "(* marker *)" @@ -99,4 +98,54 @@ type T = %s type T = { F: int } -""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) \ No newline at end of file +""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) + + + +[] +let ``Attribute lists`` () = + let source = """ +[] +let foo1 = () + +[] +[] +let foo2 = () + +[] [] +let foo3 = () + +[] +let foo7 = () +""" + match parseSourceCode ("test", source) with + | Some (ParsedInput.ImplFile (ParsedImplFileInput (_,_,_,_,_,[SynModuleOrNamespace (_,_,_,decls,_,_,_,_)],_))) -> + decls |> List.map (fun decl -> + match decl with + | SynModuleDecl.Let (_,[Binding(_,_,_,_,attributeLists,_,_,_,_,_,_,_)],_) -> + attributeLists |> List.map (fun list -> + let r = list.Range + + list.Attributes.Length, + ((r.StartLine, r.StartColumn), (r.EndLine, r.EndColumn))) + + | _ -> failwith "Could not get binding") + |> shouldEqual + [ [ (1, ((2, 0), (2, 5))) ] + [ (1, ((5, 0), (5, 5))); (2, ((6, 0), (6, 7))) ] + [ (1, ((9, 0), (9, 5))); (2, ((9, 6), (9, 13))) ] + [ (1, ((12, 0), (13, 0))) ] + [ (1, ((15, 0), (15, 4))) ] + [ (0, ((18, 0), (18, 2))) ] + [ (0, ((21, 0), (21, 4))) ] ] + + | _ -> failwith "Could not get module decls" diff --git a/vsintegration/Utils/LanguageServiceProfiling/Options.fs b/vsintegration/Utils/LanguageServiceProfiling/Options.fs index 07d3b5804ee..8ffb41aef0f 100644 --- a/vsintegration/Utils/LanguageServiceProfiling/Options.fs +++ b/vsintegration/Utils/LanguageServiceProfiling/Options.fs @@ -190,6 +190,8 @@ let FCS (repositoryDir: string) : Options = @"src\fsharp\service\ServiceUntypedParse.fs" @"src\utils\reshapedmsbuild.fs" @"src\fsharp\SimulatedMSBuildReferenceResolver.fs" + @"src\fsharp\service\FSharpCheckerResults.fsi" + @"src\fsharp\service\FSharpCheckerResults.fs" @"src\fsharp\service\service.fsi" @"src\fsharp\service\service.fs" @"src\fsharp\service\SimpleServices.fsi" diff --git a/vsintegration/Vsix/VisualFSharpFull/Source.extension.vsixmanifest b/vsintegration/Vsix/VisualFSharpFull/Source.extension.vsixmanifest index 075855a0614..e272923a3da 100644 --- a/vsintegration/Vsix/VisualFSharpFull/Source.extension.vsixmanifest +++ b/vsintegration/Vsix/VisualFSharpFull/Source.extension.vsixmanifest @@ -20,6 +20,7 @@ + @@ -27,6 +28,7 @@ + diff --git a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj index cfe668403ca..68b8b5e1122 100644 --- a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj +++ b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpFull.csproj @@ -7,6 +7,7 @@ Microsoft\FSharp netcoreapp1.0 true + net472 @@ -40,7 +41,7 @@ All 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {649FA588-F02E-457C-9FCF-87E46407481E} @@ -51,7 +52,15 @@ All 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) + + + {60BAFFA5-6631-4328-B044-2E012AB76DCA} + FSharp.Compiler.LanguageServer + PublishedProjectOutputGroup%3b + false + Build;Publish + TargetFramework=$(DependencyTargetFramework) {D5870CF0-ED51-4CBC-B3D7-6F56DA84AC06} @@ -62,7 +71,7 @@ All 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {2E4D67B4-522D-4CF7-97E4-BA940F0B18F3} @@ -73,7 +82,7 @@ All 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {DED3BBD7-53F4-428A-8C9F-27968E768605} @@ -96,7 +105,7 @@ X64 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {D0E98C0D-490B-4C61-9329-0862F6E87645} @@ -108,7 +117,7 @@ X86 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {C94C257C-3C0A-4858-B5D8-D746498D1F08} @@ -120,7 +129,7 @@ All 2 True - TargetFramework=net472 + TargetFramework=$(DependencyTargetFramework) {65e0e82a-eace-4787-8994-888674c2fe87} @@ -132,6 +141,16 @@ 2 True + + {0A3099F1-F0C7-4ADE-AB9B-526EF193A56F} + FSharp.Editor.Helpers + BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bPkgDefProjectOutputGroup%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + true + All + 2 + True + {c4586a06-1402-48bc-8e35-a1b8642f895b} FSharp.UIResources @@ -259,6 +278,7 @@ + diff --git a/vsintegration/src/FSharp.Editor.Helpers/FSharp.Editor.Helpers.csproj b/vsintegration/src/FSharp.Editor.Helpers/FSharp.Editor.Helpers.csproj new file mode 100644 index 00000000000..cb6cfb36cb0 --- /dev/null +++ b/vsintegration/src/FSharp.Editor.Helpers/FSharp.Editor.Helpers.csproj @@ -0,0 +1,22 @@ + + + + Library + net472 + + + + + + + + + + + FSharp.Editor.Helpers + $(VSAssemblyVersion) + $PackageFolder$\FSharp.Editor.Helpers.dll + + + + diff --git a/vsintegration/src/FSharp.Editor.Helpers/LanguageClient.cs b/vsintegration/src/FSharp.Editor.Helpers/LanguageClient.cs new file mode 100644 index 00000000000..87759ceef7b --- /dev/null +++ b/vsintegration/src/FSharp.Editor.Helpers/LanguageClient.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.LanguageServer.Client; +using Microsoft.VisualStudio.Threading; + +namespace Microsoft.VisualStudio.FSharp.Editor.Helpers +{ + /// + /// Exists as an abstract implementor of purely to manage the non-standard async + /// event handlers. + /// + public abstract class LanguageClient : ILanguageClient + { + public abstract string Name { get; } + + public abstract IEnumerable ConfigurationSections { get; } + + public abstract object InitializationOptions { get; } + + public abstract IEnumerable FilesToWatch { get; } + + public event AsyncEventHandler StartAsync; + +#pragma warning disable 67 // The event 'LanguageClient.StopAsync' is never used + public event AsyncEventHandler StopAsync; +#pragma warning restore 67 + + public abstract Task ActivateAsync(CancellationToken token); + + protected abstract Task DoLoadAsync(); + + public async Task OnLoadedAsync() + { + await DoLoadAsync(); + await StartAsync.InvokeAsync(this, EventArgs.Empty); + } + + public abstract Task OnServerInitializeFailedAsync(Exception e); + + public abstract Task OnServerInitializedAsync(); + } +} diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs index aaa39980f87..032ae032b66 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs @@ -7,7 +7,6 @@ open System.Composition open System.Threading open System.Threading.Tasks -open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Formatting open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes diff --git a/vsintegration/src/FSharp.Editor/CodeLens/AbstractCodeLensDisplayService.fs b/vsintegration/src/FSharp.Editor/CodeLens/AbstractCodeLensDisplayService.fs index e6d60baf093..b5c4b8fb905 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/AbstractCodeLensDisplayService.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/AbstractCodeLensDisplayService.fs @@ -2,7 +2,6 @@ namespace rec Microsoft.VisualStudio.FSharp.Editor -open System open System.Windows.Controls open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Text.Editor @@ -123,7 +122,9 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam let startLineNumber = snapshot.GetLineNumberFromPosition(trackingSpan.GetStartPoint(snapshot).Position) let uiElement = if self.UiElements.ContainsKey trackingSpan then +#if DEBUG logErrorf "Added a tracking span twice, this is not allowed and will result in invalid values! %A" (trackingSpan.GetText snapshot) +#endif self.UiElements.[trackingSpan] else let defaultStackPanel = self.CreateDefaultStackPanel() @@ -152,7 +153,12 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam let firstLine = view.TextViewLines.FirstVisibleLine view.DisplayTextLineContainingBufferPosition (firstLine.Start, 0., ViewRelativePosition.Top) self.RelayoutRequested.Enqueue(()) - with e -> logErrorf "Error in line lens provider: %A" e + with e -> +#if DEBUG + logErrorf "Error in line lens provider: %A" e +#else + ignore e +#endif /// Public non-thread-safe method to add line lens for a given tracking span. /// Returns an UIElement which can be used to add Ui elements and to remove the line lens later. @@ -171,20 +177,30 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam self.UiElements.Remove trackingSpan |> ignore try self.CodeLensLayer.RemoveAdornment(Grid) - with e -> + with e -> +#if DEBUG logExceptionWithContext(e, "Removing line lens") +#else + ignore e +#endif +#if DEBUG else logWarningf "No ui element is attached to this tracking span!" +#endif let lineNumber = (trackingSpan.GetStartPoint self.CurrentBufferSnapshot).Position |> self.CurrentBufferSnapshot.GetLineNumberFromPosition if self.TrackingSpans.ContainsKey lineNumber then +#if DEBUG if self.TrackingSpans.[lineNumber].Remove trackingSpan |> not then logWarningf "No tracking span is accociated with this line number %d!" lineNumber +#endif if self.TrackingSpans.[lineNumber].Count = 0 then self.TrackingSpans.Remove lineNumber |> ignore +#if DEBUG else logWarningf "No tracking span is accociated with this line number %d!" lineNumber +#endif abstract member AddUiElementToCodeLens : ITrackingSpan * UIElement -> unit default self.AddUiElementToCodeLens (trackingSpan:ITrackingSpan, uiElement:UIElement) = @@ -234,7 +250,12 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam applyFuncOnLineStackPanels line (fun ui -> ui.Visibility <- Visibility.Hidden ) - with e -> logErrorf "Error in non visible lines iteration %A" e + with e -> +#if DEBUG + logErrorf "Error in non visible lines iteration %A" e +#else + ignore e +#endif for lineNumber in newVisibleLineNumbers do try let line = @@ -244,7 +265,12 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam ui.Visibility <- Visibility.Visible self.LayoutUIElementOnLine view line ui ) - with e -> logErrorf "Error in new visible lines iteration %A" e + with e -> +#if DEBUG + logErrorf "Error in new visible lines iteration %A" e +#else + ignore e +#endif if not e.VerticalTranslation && e.NewViewState.ViewportHeight <> e.OldViewState.ViewportHeight then self.RelayoutRequested.Enqueue() // Unfortunately zooming requires a relayout too, to ensure that no weird layout happens due to unkown reasons. if self.RelayoutRequested.Count > 0 then @@ -267,8 +293,13 @@ type CodeLensDisplayService (view : IWpfTextView, buffer : ITextBuffer, layerNam self.AsyncCustomLayoutOperation visibleLineNumbers buffer |> RoslynHelpers.StartAsyncSafe self.LayoutChangedCts.Token "HandleLayoutChanged" - with e -> logExceptionWithContext (e, "Layout changed") + with e -> +#if DEBUG + logExceptionWithContext (e, "Layout changed") +#else + ignore e +#endif abstract LayoutUIElementOnLine : IWpfTextView -> ITextViewLine -> Grid -> unit - abstract AsyncCustomLayoutOperation : int Set -> ITextSnapshot -> unit Async + abstract AsyncCustomLayoutOperation : int Set -> ITextSnapshot -> unit Async \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensGeneralTagger.fs b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensGeneralTagger.fs index 9ed05edf0ac..3702ac62878 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensGeneralTagger.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensGeneralTagger.fs @@ -8,7 +8,6 @@ open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Text.Editor open Microsoft.VisualStudio.Text.Formatting open System.Windows -open System.Collections.Generic open Microsoft.VisualStudio.Text.Tagging open Microsoft.VisualStudio.FSharp.Editor.Logging @@ -32,7 +31,9 @@ type CodeLensGeneralTagger (view, buffer) as self = let left = Canvas.GetLeft parent let top = Canvas.GetTop parent let width = parent.ActualWidth +#if DEBUG logInfof "Width of parent: %.4f" width +#endif left + width, top | _ -> try @@ -47,12 +48,18 @@ type CodeLensGeneralTagger (view, buffer) as self = // Calling the method twice fixes this bug and ensures that all values are correct. // Okay not really :( Must be replaced later with an own calculation depending on editor font settings! if 7 * offset > int g.Left then +#if DEBUG logErrorf "Incorrect return from geometry measure" +#endif Canvas.GetLeft ui, g.Top else g.Left, g.Top with e -> +#if DEBUG logExceptionWithContext (e, "Error in layout ui element on line") +#else + ignore e +#endif Canvas.GetLeft ui, Canvas.GetTop ui Canvas.SetLeft(ui, left) Canvas.SetTop(ui, top) @@ -89,7 +96,12 @@ type CodeLensGeneralTagger (view, buffer) as self = self, stackPanel, AdornmentRemovedCallback(fun _ _ -> ())) |> ignore self.AddedAdornments.Add stackPanel |> ignore | _ -> () - with e -> logExceptionWithContext (e, "LayoutChanged, processing new visible lines") + with e -> +#if DEBUG + logExceptionWithContext (e, "LayoutChanged, processing new visible lines") +#else + ignore e +#endif } |> Async.Ignore override self.AddUiElementToCodeLens (trackingSpan:ITrackingSpan, uiElement:UIElement)= @@ -114,7 +126,13 @@ type CodeLensGeneralTagger (view, buffer) as self = let lineNumber = try snapshot.GetLineNumberFromPosition(span.Start.Position) - with e -> logExceptionWithContext (e, "line number tagging"); 0 + with e -> +#if DEBUG + logExceptionWithContext (e, "line number tagging") +#else + ignore e +#endif + 0 if self.TrackingSpans.ContainsKey(lineNumber) && self.TrackingSpans.[lineNumber] |> Seq.isEmpty |> not then let tagSpan = snapshot.GetLineFromLineNumber(lineNumber).Extent @@ -128,13 +146,25 @@ type CodeLensGeneralTagger (view, buffer) as self = let span = try tagSpan.TranslateTo(span.Snapshot, SpanTrackingMode.EdgeExclusive) - with e -> logExceptionWithContext (e, "tag span translation"); tagSpan + with e -> +#if DEBUG + logExceptionWithContext (e, "tag span translation") +#else + ignore e +#endif + tagSpan let sizes = try stackPanels |> Seq.map (fun ui -> ui.Measure(Size(10000., 10000.)) ui.DesiredSize ) - with e -> logExceptionWithContext (e, "internal tagging"); Seq.empty + with e -> +#if DEBUG + logExceptionWithContext (e, "internal tagging") +#else + ignore e +#endif + Seq.empty let height = try sizes @@ -142,12 +172,20 @@ type CodeLensGeneralTagger (view, buffer) as self = |> Seq.sortDescending |> Seq.tryHead |> Option.defaultValue 0. - with e -> logExceptionWithContext (e, "height tagging"); 0. + with e -> +#if DEBUG + logExceptionWithContext (e, "height tagging") +#else + ignore e +#endif + 0.0 yield TagSpan(span, CodeLensGeneralTag(0., height, 0., 0., 0., PositionAffinity.Predecessor, stackPanels, self)) :> ITagSpan } - with e -> + with e -> +#if DEBUG logErrorf "Error in code lens get tags %A" e - Seq.empty - - +#else + ignore e +#endif + Seq.empty \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs index 10c7f14ab95..eaf4b541a84 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/CodeLensProvider.fs @@ -2,23 +2,15 @@ namespace rec Microsoft.VisualStudio.FSharp.Editor - open System open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Text.Editor open System.ComponentModel.Composition open Microsoft.VisualStudio.Utilities -open Microsoft.CodeAnalysis open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio open Microsoft.VisualStudio.LanguageServices -open System.Collections.Generic -open Microsoft.CodeAnalysis.Editor.Shared.Utilities open Microsoft.VisualStudio.Text.Tagging -open Microsoft.VisualStudio.Text.Classification -open Microsoft.VisualStudio.ComponentModelHost -open System.Threading -open Microsoft.VisualStudio.FSharp.Editor.Logging open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Shared.Utilities [)>] @@ -108,4 +100,4 @@ type internal CodeLensProvider interface IWpfTextViewCreationListener with override __.TextViewCreated view = if settings.CodeLens.Enabled && settings.CodeLens.ReplaceWithLineLens then - addLineLensProviderOnce view (view.TextBuffer) |> ignore + addLineLensProviderOnce view (view.TextBuffer) |> ignore \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs index 44e9322dffb..add51361f19 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs @@ -13,12 +13,9 @@ open System.Windows.Media.Animation open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Editor.Shared.Extensions -open Microsoft.CodeAnalysis.Editor.Shared.Utilities -open Microsoft.CodeAnalysis.Classification open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Classification open FSharp.Compiler -open FSharp.Compiler.Ast open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Range @@ -28,7 +25,6 @@ open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Text.Classification -open Microsoft.VisualStudio.Text.Formatting open Internal.Utilities.StructuredFormat @@ -90,8 +86,10 @@ type internal FSharpCodeLensService do! Async.SwitchToContext uiContext let! res = lens.TaggedText match res with - | Some (taggedText, navigation) -> + | Some (taggedText, navigation) -> +#if DEBUG logInfof "Tagged text %A" taggedText +#endif let textBlock = new TextBlock(Background = Brushes.AliceBlue, Opacity = 0.0, TextTrimming = TextTrimming.None) DependencyObjectExtensions.SetDefaultTextProperties(textBlock, formatMap.Value) @@ -143,15 +141,20 @@ type internal FSharpCodeLensService let executeCodeLenseAsync () = asyncMaybe { do! Async.Sleep 800 |> liftAsync +#if DEBUG logInfof "Rechecking code due to buffer edit!" +#endif let! document = workspace.CurrentSolution.GetDocument(documentId.Value) |> Option.ofObj let! _, options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, bufferChangedCts.Token) let! _, parsedInput, checkFileResults = checker.ParseAndCheckDocument(document, options, "LineLens", allowStaleResults=true) +#if DEBUG logInfof "Getting uses of all symbols!" +#endif let! symbolUses = checkFileResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync let textSnapshot = buffer.CurrentSnapshot +#if DEBUG logInfof "Updating due to buffer edit!" - +#endif // Clear existing data and cache flags // The results which are left. let oldResults = Dictionary(lastResults) @@ -171,13 +174,9 @@ type internal FSharpCodeLensService if (lineNumber >= 0 || lineNumber < textSnapshot.LineCount) then match func.FullTypeSafe with | Some _ -> - let! displayEnv = checkFileResults.GetDisplayEnvForPos func.DeclarationLocation.Start + let! maybeContext = checkFileResults.GetDisplayContextForPos(func.DeclarationLocation.Start) - let displayContext = - match displayEnv with - | Some denv -> FSharpDisplayContext(fun _ -> denv) - | None -> displayContext - + let displayContext = Option.defaultValue displayContext maybeContext let typeLayout = func.FormatLayout displayContext let taggedText = ResizeArray() @@ -187,17 +186,25 @@ type internal FSharpCodeLensService // Because the data is available notify that this line should be updated, displaying the results return Some (taggedText, navigation) | None -> +#if DEBUG logWarningf "Couldn't acquire CodeLens data for function %A" func +#endif return None else return None with e -> +#if DEBUG logErrorf "Error in lazy line lens computation. %A" e +#else + ignore e +#endif return None } let inline setNewResultsAndWarnIfOverriden fullDeclarationText value = +#if DEBUG if newResults.ContainsKey fullDeclarationText then logWarningf "New results already contains: %A" fullDeclarationText +#endif newResults.[fullDeclarationText] <- value for symbolUse in symbolUses do @@ -298,7 +305,11 @@ type internal FSharpCodeLensService textSnapshot.CreateTrackingSpan(declarationSpan, SpanTrackingMode.EdgeExclusive) codeLensToAdd.Add (trackingSpan, res) newResults.[fullDeclarationText] <- (trackingSpan, res) - with e -> logExceptionWithContext (e, "Line Lens tracking tag span creation") + with e -> +#if DEBUG + logExceptionWithContext (e, "Line Lens tracking tag span creation") +#endif + ignore e () lastResults <- newResults do! Async.SwitchToContext uiContext |> liftAsync @@ -308,7 +319,9 @@ type internal FSharpCodeLensService let! res = createTextBox codeLens if res then do! Async.SwitchToContext uiContext +#if DEBUG logInfof "Adding ui element for %A" (codeLens.TaggedText) +#endif let uiElement = codeLens.UiElement let animation = DoubleAnimation( @@ -324,16 +337,16 @@ type internal FSharpCodeLensService lineLens.RelayoutRequested.Enqueue () sb.Begin() else +#if DEBUG logWarningf "Couldn't retrieve code lens information for %A" codeLens.FullTypeSignature - // logInfo "Adding text box!" +#endif + () } |> (RoslynHelpers.StartAsyncSafe CancellationToken.None) "UIElement creation" for value in tagsToUpdate do let trackingSpan, (newTrackingSpan, _, codeLens) = value.Key, value.Value - // logInfof "Adding ui element for %A" (codeLens.TaggedText) lineLens.RemoveCodeLens trackingSpan |> ignore let Grid = lineLens.AddCodeLens newTrackingSpan - // logInfof "Trackingspan %A is being added." trackingSpan if codeLens.Computed && (isNull codeLens.UiElement |> not) then let uiElement = codeLens.UiElement lineLens.AddUiElementToCodeLensOnce (newTrackingSpan, uiElement) @@ -345,7 +358,9 @@ type internal FSharpCodeLensService for value in codeLensToAdd do let trackingSpan, codeLens = value let Grid = lineLens.AddCodeLens trackingSpan - logInfof "Trackingspan %A is being added." trackingSpan +#if DEBUG + logInfof "Trackingspan %A is being added." trackingSpan +#endif Grid.IsVisibleChanged |> Event.filter (fun eventArgs -> eventArgs.NewValue :?> bool) @@ -353,10 +368,10 @@ type internal FSharpCodeLensService for oldResult in oldResults do let trackingSpan, _ = oldResult.Value - // logInfof "removing trackingSpan %A" trackingSpan lineLens.RemoveCodeLens trackingSpan |> ignore - +#if DEBUG logInfof "Finished updating line lens." +#endif if not firstTimeChecked then firstTimeChecked <- true @@ -372,8 +387,13 @@ type internal FSharpCodeLensService do! executeCodeLenseAsync() do! Async.Sleep(1000) with - | e -> logErrorf "Line Lens startup failed with: %A" e - numberOfFails <- numberOfFails + 1 + | e -> +#if DEBUG + logErrorf "Line Lens startup failed with: %A" e +#else + ignore e +#endif + numberOfFails <- numberOfFails + 1 } |> Async.Start end @@ -381,5 +401,4 @@ type internal FSharpCodeLensService bufferChangedCts.Cancel() // Stop all ongoing async workflow. bufferChangedCts.Dispose() bufferChangedCts <- new CancellationTokenSource() - executeCodeLenseAsync () |> Async.Ignore |> RoslynHelpers.StartAsyncSafe bufferChangedCts.Token "Buffer Changed" - + executeCodeLenseAsync () |> Async.Ignore |> RoslynHelpers.StartAsyncSafe bufferChangedCts.Token "Buffer Changed" \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeLens/LineLensDisplayService.fs b/vsintegration/src/FSharp.Editor/CodeLens/LineLensDisplayService.fs index 138c5ac785f..a34df51c722 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/LineLensDisplayService.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/LineLensDisplayService.fs @@ -5,11 +5,8 @@ namespace rec Microsoft.VisualStudio.FSharp.Editor open System open System.Windows.Controls -open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Text.Editor open Microsoft.VisualStudio.Text.Formatting -open System.Windows -open System.Collections.Generic open Microsoft.VisualStudio.FSharp.Editor.Logging @@ -29,8 +26,12 @@ type internal LineLensDisplayService (view, buffer) = try let bounds = line.GetCharacterBounds(line.Start) line.TextRight + 5.0, bounds.Top - 1. - with e -> + with e -> +#if DEBUG logExceptionWithContext (e, "Error in layout ui element on line") +#else + ignore e +#endif Canvas.GetLeft ui, Canvas.GetTop ui Canvas.SetLeft(ui, left) Canvas.SetTop(ui, top) @@ -63,5 +64,10 @@ type internal LineLensDisplayService (view, buffer) = view.GetTextViewLineContainingBufferPosition l.Start self.LayoutUIElementOnLine view line grid ) - with e -> logExceptionWithContext (e, "LayoutChanged, processing new visible lines") + with e -> +#if DEBUG + logExceptionWithContext (e, "LayoutChanged, processing new visible lines") +#else + ignore e +#endif } |> Async.Ignore \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index ae848297b4a..2c6e7c58fdc 100644 --- a/vsintegration/src/FSharp.Editor/Common/Constants.fs +++ b/vsintegration/src/FSharp.Editor/Common/Constants.fs @@ -31,6 +31,10 @@ module internal FSharpConstants = /// "F#" let FSharpContentTypeName = "F#" + [] + /// ".fs" + let FSharpFileExtension = ".fs" + [] /// "F# Signature Help" let FSharpSignatureHelpContentTypeName = "F# Signature Help" diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 222f4b5f09f..894ef61b23b 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -11,10 +11,9 @@ open Microsoft.CodeAnalysis.Host open FSharp.Compiler.Text open FSharp.Compiler.Ast open FSharp.Compiler.SourceCodeServices -open Microsoft.CodeAnalysis.ExternalAccess.FSharp type private FSharpGlyph = FSharp.Compiler.SourceCodeServices.FSharpGlyph -type private Glyph = Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph +type private FSharpRoslynGlyph = Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph type Path with @@ -136,79 +135,77 @@ type SourceText with SourceText.weakTable.GetValue(this, Runtime.CompilerServices.ConditionalWeakTable<_,_>.CreateValueCallback(SourceText.create)) type FSharpNavigationDeclarationItem with - member x.RoslynGlyph : Glyph = + member x.RoslynGlyph : FSharpRoslynGlyph = match x.Glyph with | FSharpGlyph.Class | FSharpGlyph.Typedef | FSharpGlyph.Type | FSharpGlyph.Exception -> match x.Access with - | Some SynAccess.Private -> Glyph.ClassPrivate - | Some SynAccess.Internal -> Glyph.ClassInternal - | _ -> Glyph.ClassPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.ClassPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.ClassInternal + | _ -> FSharpRoslynGlyph.ClassPublic | FSharpGlyph.Constant -> match x.Access with - | Some SynAccess.Private -> Glyph.ConstantPrivate - | Some SynAccess.Internal -> Glyph.ConstantInternal - | _ -> Glyph.ConstantPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.ConstantPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.ConstantInternal + | _ -> FSharpRoslynGlyph.ConstantPublic | FSharpGlyph.Delegate -> match x.Access with - | Some SynAccess.Private -> Glyph.DelegatePrivate - | Some SynAccess.Internal -> Glyph.DelegateInternal - | _ -> Glyph.DelegatePublic + | Some SynAccess.Private -> FSharpRoslynGlyph.DelegatePrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.DelegateInternal + | _ -> FSharpRoslynGlyph.DelegatePublic | FSharpGlyph.Union | FSharpGlyph.Enum -> match x.Access with - | Some SynAccess.Private -> Glyph.EnumPrivate - | Some SynAccess.Internal -> Glyph.EnumInternal - | _ -> Glyph.EnumPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.EnumPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.EnumInternal + | _ -> FSharpRoslynGlyph.EnumPublic | FSharpGlyph.EnumMember | FSharpGlyph.Variable | FSharpGlyph.Field -> match x.Access with - | Some SynAccess.Private -> Glyph.FieldPrivate - | Some SynAccess.Internal -> Glyph.FieldInternal - | _ -> Glyph.FieldPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.FieldPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.FieldInternal + | _ -> FSharpRoslynGlyph.FieldPublic | FSharpGlyph.Event -> match x.Access with - | Some SynAccess.Private -> Glyph.EventPrivate - | Some SynAccess.Internal -> Glyph.EventInternal - | _ -> Glyph.EventPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.EventPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.EventInternal + | _ -> FSharpRoslynGlyph.EventPublic | FSharpGlyph.Interface -> match x.Access with - | Some SynAccess.Private -> Glyph.InterfacePrivate - | Some SynAccess.Internal -> Glyph.InterfaceInternal - | _ -> Glyph.InterfacePublic + | Some SynAccess.Private -> FSharpRoslynGlyph.InterfacePrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.InterfaceInternal + | _ -> FSharpRoslynGlyph.InterfacePublic | FSharpGlyph.Method | FSharpGlyph.OverridenMethod -> match x.Access with - | Some SynAccess.Private -> Glyph.MethodPrivate - | Some SynAccess.Internal -> Glyph.MethodInternal - | _ -> Glyph.MethodPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.MethodPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.MethodInternal + | _ -> FSharpRoslynGlyph.MethodPublic | FSharpGlyph.Module -> match x.Access with - | Some SynAccess.Private -> Glyph.ModulePrivate - | Some SynAccess.Internal -> Glyph.ModuleInternal - | _ -> Glyph.ModulePublic - | FSharpGlyph.NameSpace -> Glyph.Namespace + | Some SynAccess.Private -> FSharpRoslynGlyph.ModulePrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.ModuleInternal + | _ -> FSharpRoslynGlyph.ModulePublic + | FSharpGlyph.NameSpace -> FSharpRoslynGlyph.Namespace | FSharpGlyph.Property -> match x.Access with - | Some SynAccess.Private -> Glyph.PropertyPrivate - | Some SynAccess.Internal -> Glyph.PropertyInternal - | _ -> Glyph.PropertyPublic + | Some SynAccess.Private -> FSharpRoslynGlyph.PropertyPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.PropertyInternal + | _ -> FSharpRoslynGlyph.PropertyPublic | FSharpGlyph.Struct -> match x.Access with - | Some SynAccess.Private -> Glyph.StructurePrivate - | Some SynAccess.Internal -> Glyph.StructureInternal - | _ -> Glyph.StructurePublic + | Some SynAccess.Private -> FSharpRoslynGlyph.StructurePrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.StructureInternal + | _ -> FSharpRoslynGlyph.StructurePublic | FSharpGlyph.ExtensionMethod -> match x.Access with - | Some SynAccess.Private -> Glyph.ExtensionMethodPrivate - | Some SynAccess.Internal -> Glyph.ExtensionMethodInternal - | _ -> Glyph.ExtensionMethodPublic - | FSharpGlyph.Error -> Glyph.Error - - + | Some SynAccess.Private -> FSharpRoslynGlyph.ExtensionMethodPrivate + | Some SynAccess.Internal -> FSharpRoslynGlyph.ExtensionMethodInternal + | _ -> FSharpRoslynGlyph.ExtensionMethodPublic + | FSharpGlyph.Error -> FSharpRoslynGlyph.Error [] module String = @@ -234,21 +231,6 @@ module Option = let attempt (f: unit -> 'T) = try Some <| f() with _ -> None - let inline ofNull value = - if obj.ReferenceEquals(value, null) then None else Some value - - /// Gets the option if Some x, otherwise try to get another value - let inline orTry f = - function - | Some x -> Some x - | None -> f() - - /// Gets the value if Some x, otherwise try to get another value by calling a function - let inline getOrTry f = - function - | Some x -> x - | None -> f() - /// Returns 'Some list' if all elements in the list are Some, otherwise None let ofOptionList (xs : 'a option list) : 'a list option = @@ -273,48 +255,6 @@ module Array = i <- i + 1 state - /// Optimized arrays equality. ~100x faster than `array1 = array2` on strings. - /// ~2x faster for floats - /// ~0.8x slower for ints - let areEqual (xs: 'T []) (ys: 'T []) = - match xs, ys with - | null, null -> true - | [||], [||] -> true - | null, _ | _, null -> false - | _ when xs.Length <> ys.Length -> false - | _ -> - let mutable stop = false - let mutable i = 0 - let mutable result = true - while i < xs.Length && not stop do - if xs.[i] <> ys.[i] then - stop <- true - result <- false - i <- i + 1 - result - - /// check if subArray is found in the wholeArray starting - /// at the provided index - let isSubArray (subArray: 'T []) (wholeArray:'T []) index = - if isNull subArray || isNull wholeArray then false - elif subArray.Length = 0 then true - elif subArray.Length > wholeArray.Length then false - elif subArray.Length = wholeArray.Length then areEqual subArray wholeArray else - let rec loop subidx idx = - if subidx = subArray.Length then true - elif subArray.[subidx] = wholeArray.[idx] then loop (subidx+1) (idx+1) - else false - loop 0 index - - /// Returns true if one array has another as its subset from index 0. - let startsWith (prefix: _ []) (whole: _ []) = - isSubArray prefix whole 0 - - /// Returns true if one array has trailing elements equal to another's. - let endsWith (suffix: _ []) (whole: _ []) = - isSubArray suffix whole (whole.Length-suffix.Length) - - [] module Exception = diff --git a/vsintegration/src/FSharp.Editor/Common/LspService.fs b/vsintegration/src/FSharp.Editor/Common/LspService.fs new file mode 100644 index 00000000000..433f8338394 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/Common/LspService.fs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.ComponentModel.Composition +open FSharp.Compiler.LanguageServer +open FSharp.Compiler.LanguageServer.Extensions +open StreamJsonRpc + +[)>] +type LspService() = + let mutable options = Options.Default() + let mutable jsonRpc: JsonRpc option = None + + let sendOptions () = + async { + match jsonRpc with + | None -> () + | Some rpc -> do! rpc.SetOptionsAsync(options) + } + + member __.SetJsonRpc(rpc: JsonRpc) = + jsonRpc <- Some rpc + sendOptions() + + member __.SetOptions(opt: Options) = + options <- opt + sendOptions() diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs index 976dbf67f8d..9dd525d609c 100644 --- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs +++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs @@ -3,11 +3,8 @@ module Microsoft.VisualStudio.FSharp.Editor.Pervasive open System open System.IO -open System.Threading.Tasks open System.Diagnostics -type IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider - /// Checks if the filePath ends with ".fsi" let isSignatureFile (filePath:string) = String.Equals (Path.GetExtension filePath, ".fsi", StringComparison.OrdinalIgnoreCase) @@ -17,12 +14,8 @@ let isScriptFile (filePath:string) = let ext = Path.GetExtension filePath String.Equals (ext, ".fsx", StringComparison.OrdinalIgnoreCase) || String.Equals (ext, ".fsscript", StringComparison.OrdinalIgnoreCase) -/// Path combination operator -let () path1 path2 = Path.Combine (path1, path2) - type internal ISetThemeColors = abstract member SetColors: unit -> unit - [] type MaybeBuilder () = // 'T -> M<'T> @@ -199,7 +192,4 @@ module Async = let! replyCh = agent.Receive () replyCh.Reply res } - async { return! agent.PostAndAsyncReply id } - - - + async { return! agent.PostAndAsyncReply id } \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs b/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs index d675803a4d4..09a7951e047 100644 --- a/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs +++ b/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs @@ -4,14 +4,13 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Runtime.CompilerServices -open System.Runtime.Caching open System.Text.RegularExpressions -open Internal.Utilities.Collections open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Layout open FSharp.Compiler.Layout.TaggedTextOps +open System.Collections.Generic type internal ITaggedTextCollector = abstract Add: text: TaggedText -> unit @@ -215,50 +214,20 @@ module internal XmlDocumentation = /// Provide Xml Documentation type Provider(xmlIndexService:IVsXMLMemberIndexService) = /// Index of assembly name to xml member index. - let mutable xmlCache = new AgedLookup(10,areSimilar=(fun (x,y) -> x = y)) + let cache = Dictionary() - do Events.SolutionEvents.OnAfterCloseSolution.Add (fun _ -> - xmlCache.Clear(vsToken)) - - #if DEBUG // Keep under DEBUG so that it can keep building. - - let _AppendTypeParameters (collector: ITaggedTextCollector) (memberData:IVsXMLMemberData3) = - let ok,count = memberData.GetTypeParamCount() - if Com.Succeeded(ok) && count > 0 then - for param in 0..count do - let ok,name,text = memberData.GetTypeParamTextAt(param) - if Com.Succeeded(ok) then - EnsureHardLine collector - collector.Add(tagTypeParameter name) - collector.Add(Literals.space) - collector.Add(tagPunctuation "-") - collector.Add(Literals.space) - collector.Add(tagText text) - - let _AppendRemarks (collector: ITaggedTextCollector) (memberData:IVsXMLMemberData3) = - let ok, remarksText = memberData.GetRemarksText() - if Com.Succeeded(ok) then - AppendOnNewLine collector remarksText - #endif - - let _AppendReturns (collector: ITaggedTextCollector) (memberData:IVsXMLMemberData3) = - let ok,returnsText = memberData.GetReturnsText() - if Com.Succeeded(ok) then - if not collector.EndsWithLineBreak then - AppendHardLine(collector) - AppendHardLine(collector) - AppendOnNewLine collector returnsText + do Events.SolutionEvents.OnAfterCloseSolution.Add (fun _ -> cache.Clear()) /// Retrieve the pre-existing xml index or None let GetMemberIndexOfAssembly(assemblyName) = - match xmlCache.TryGet(vsToken, assemblyName) with - | Some(memberIndex) -> Some(memberIndex) - | None -> + match cache.TryGetValue(assemblyName) with + | true, memberIndex -> Some(memberIndex) + | false, _ -> let ok,memberIndex = xmlIndexService.CreateXMLMemberIndex(assemblyName) if Com.Succeeded(ok) then let ok = memberIndex.BuildMemberIndex() - if Com.Succeeded(ok) then - xmlCache.Put(vsToken, assemblyName,memberIndex) + if Com.Succeeded(ok) then + cache.Add(assemblyName, memberIndex) Some(memberIndex) else None else None @@ -275,7 +244,7 @@ module internal XmlDocumentation = interface IDocumentationBuilder with /// Append the given processed XML formatted into the string builder - override this.AppendDocumentationFromProcessedXML(xmlCollector, exnCollector, processedXml, showExceptions, showParameters, paramName) = + override __.AppendDocumentationFromProcessedXML(xmlCollector, exnCollector, processedXml, showExceptions, showParameters, paramName) = match XmlDocReader.TryCreate processedXml with | Some xmlDocReader -> match paramName with diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index e43e61af58a..8b99167f24e 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -36,6 +36,10 @@ + + Common\LspExternalAccess.fs + + @@ -52,6 +56,10 @@ + + LanguageService\JsonOptionConverter.fs + + @@ -116,6 +124,7 @@ + @@ -144,6 +153,7 @@ + @@ -158,6 +168,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index b43e5caf14a..8397825d71a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -6,14 +6,11 @@ open System open System.ComponentModel.Composition open System.Diagnostics open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Diagnostics open FSharp.Compiler.SourceCodeServices open Microsoft.VisualStudio open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.LanguageServices -open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem open FSharp.NativeInterop -open Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics #nowarn "9" // NativePtr.toNativeInt @@ -59,8 +56,8 @@ type internal FSharpCheckerProvider projectCacheSize = settings.LanguageServicePerformance.ProjectCheckCacheSize, keepAllBackgroundResolutions = false, // Enabling this would mean that if devenv.exe goes above 2.3GB we do a one-off downsize of the F# Compiler Service caches - (* , MaxMemory = 2300 *) - legacyReferenceResolver=FSharp.Compiler.MSBuildReferenceResolver.Resolver, + (* , MaxMemory = 2300 *) + legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver(), tryGetMetadataSnapshot = tryGetMetadataSnapshot) // This is one half of the bridge between the F# background builder and the Roslyn analysis engine. diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpLanguageClient.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpLanguageClient.fs new file mode 100644 index 00000000000..52f978d6662 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpLanguageClient.fs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor.LanguageService + +open System.ComponentModel.Composition +open System.Diagnostics +open System.IO +open System.Threading +open System.Threading.Tasks +open FSharp.Compiler.LanguageServer +open Microsoft.FSharp.Control +open Microsoft.VisualStudio.FSharp.Editor +open Microsoft.VisualStudio.FSharp.Editor.Helpers +open Microsoft.VisualStudio.LanguageServer.Client +open Microsoft.VisualStudio.Utilities +open StreamJsonRpc + +// https://docs.microsoft.com/en-us/visualstudio/extensibility/adding-an-lsp-extension?view=vs-2019 + +/// Provides exports necessary to register the language client. +type FSharpContentDefinition() = + + [] + [] + [] + static member val FSharpContentTypeDefinition: ContentTypeDefinition = null with get, set + + [] + [] + [] + static member val FSharpFileExtensionDefinition: FileExtensionToContentTypeDefinition = null with get, set + +[)>] +[] +type internal FSharpLanguageClient + [] + ( + lspService: LspService + ) = + inherit LanguageClient() + override __.Name = "F# Language Service" + override this.ActivateAsync(_token: CancellationToken) = + async { + let thisAssemblyPath = Path.GetDirectoryName(this.GetType().Assembly.Location) + let serverAssemblyPath = Path.Combine(thisAssemblyPath, "Agent", "FSharp.Compiler.LanguageServer.exe") + let startInfo = ProcessStartInfo(serverAssemblyPath) + startInfo.UseShellExecute <- false + startInfo.CreateNoWindow <- true // comment to see log messages written to stderr + startInfo.RedirectStandardInput <- true + startInfo.RedirectStandardOutput <- true + let proc = new Process() + proc.StartInfo <- startInfo + return + if proc.Start() then new Connection(proc.StandardOutput.BaseStream, proc.StandardInput.BaseStream) + else null + } |> Async.StartAsTask + override __.ConfigurationSections = null + override __.FilesToWatch = null + override __.InitializationOptions = null + override __.DoLoadAsync() = Task.CompletedTask + override __.OnServerInitializeFailedAsync(_e: exn) = Task.CompletedTask + override __.OnServerInitializedAsync() = Task.CompletedTask + interface ILanguageClientCustomMessage with + member __.CustomMessageTarget = null + member __.MiddleLayer = null + member __.AttachForCustomMessageAsync(rpc: JsonRpc) = + rpc.JsonSerializer.Converters.Add(JsonOptionConverter()) // ensure we can set `'T option` values + lspService.SetJsonRpc(rpc) |> Async.StartAsTask :> Task diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs index e982d10d028..3d71e2522c9 100644 --- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs +++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs @@ -3,10 +3,11 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.ComponentModel.Composition open System.Runtime.InteropServices +open System.Windows +open System.Windows.Controls +open FSharp.Compiler.LanguageServer open Microsoft.VisualStudio.Shell - open Microsoft.VisualStudio.FSharp.UIResources -open Microsoft.VisualStudio.Shell module DefaultTuning = let UnusedDeclarationsAnalyzerInitialDelay = 0 (* 1000 *) (* milliseconds *) @@ -91,10 +92,12 @@ type CodeLensOptions = [] type AdvancedOptions = { IsBlockStructureEnabled: bool - IsOutliningEnabled: bool } + IsOutliningEnabled: bool + UsePreviewTextHover: bool } static member Default = { IsBlockStructureEnabled = true - IsOutliningEnabled = true } + IsOutliningEnabled = true + UsePreviewTextHover = false } [] type FormattingOptions = @@ -195,6 +198,15 @@ module internal OptionsUI = inherit AbstractOptionPage() override __.CreateView() = upcast AdvancedOptionsControl() + override this.OnApply(args) = + base.OnApply(args) + async { + let lspService = this.GetService() + let settings = this.GetService() + let options = + { Options.usePreviewTextHover = settings.Advanced.UsePreviewTextHover } + do! lspService.SetOptions options + } |> Async.Start [] type internal FormattingOptionPage() = diff --git a/vsintegration/src/FSharp.Editor/Options/UIHelpers.fs b/vsintegration/src/FSharp.Editor/Options/UIHelpers.fs index 2adae7a7a46..2c16ff10275 100644 --- a/vsintegration/src/FSharp.Editor/Options/UIHelpers.fs +++ b/vsintegration/src/FSharp.Editor/Options/UIHelpers.fs @@ -57,6 +57,10 @@ module internal OptionsUIHelpers = // next time needsLoadOnNextActivate <- true + member this.GetService<'T when 'T : not struct>() = + let scm = this.Site.GetService(typeof) :?> IComponentModel + scm.GetService<'T>() + //data binding helpers let radioButtonCoverter = { new IValueConverter with diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index 01edaa39eb8..8ac6f929186 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -163,7 +163,8 @@ type internal FSharpAsyncQuickInfoSource xmlMemberIndexService: IVsXMLMemberIndexService, checkerProvider:FSharpCheckerProvider, projectInfoManager:FSharpProjectOptionsManager, - textBuffer:ITextBuffer + textBuffer:ITextBuffer, + settings: EditorOptions ) = static let joinWithLineBreaks segments = @@ -205,6 +206,9 @@ type internal FSharpAsyncQuickInfoSource // This method can be called from the background thread. // Do not call IServiceProvider.GetService here. override __.GetQuickInfoItemAsync(session:IAsyncQuickInfoSession, cancellationToken:CancellationToken) : Task = + // if using LSP, just bail early + if settings.Advanced.UsePreviewTextHover then Task.FromResult(null) + else let triggerPoint = session.GetTriggerPoint(textBuffer.CurrentSnapshot) match triggerPoint.HasValue with | false -> Task.FromResult(null) @@ -269,7 +273,8 @@ type internal FSharpAsyncQuickInfoSourceProvider ( [)>] serviceProvider: IServiceProvider, checkerProvider:FSharpCheckerProvider, - projectInfoManager:FSharpProjectOptionsManager + projectInfoManager:FSharpProjectOptionsManager, + settings: EditorOptions ) = interface IAsyncQuickInfoSourceProvider with @@ -278,4 +283,4 @@ type internal FSharpAsyncQuickInfoSourceProvider // It is safe to do it here (see #4713) let statusBar = StatusBar(serviceProvider.GetService()) let xmlMemberIndexService = serviceProvider.XMLMemberIndexService - new FSharpAsyncQuickInfoSource(statusBar, xmlMemberIndexService, checkerProvider, projectInfoManager, textBuffer) :> IAsyncQuickInfoSource + new FSharpAsyncQuickInfoSource(statusBar, xmlMemberIndexService, checkerProvider, projectInfoManager, textBuffer, settings) :> IAsyncQuickInfoSource diff --git a/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs b/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs index ca720f0927f..aa4e9441f8a 100644 --- a/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs +++ b/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs @@ -5,15 +5,10 @@ namespace Microsoft.VisualStudio.FSharp.LanguageService open System -open System.IO -open System.Collections.Generic open System.Diagnostics open Microsoft.VisualStudio -open Microsoft.VisualStudio.Shell -open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.TextManager.Interop open FSharp.Compiler -open FSharp.Compiler.Lib open FSharp.Compiler.SourceCodeServices module internal OperatorToken = @@ -21,7 +16,9 @@ module internal OperatorToken = let asIdentifier_DEPRECATED (token : TokenInfo) = // Typechecker reports information about all values in the same fashion no matter whether it is named value (let binding) or operator // here we piggyback on this fact and just pretend that we need data time for identifier - let tagOfIdentToken = FSharp.Compiler.Parser.tagOfToken(FSharp.Compiler.Parser.IDENT "") + + let tagOfIdentToken = FSharpTokenTag.IDENT + let endCol = token.EndIndex + 1 // EndIndex from GetTokenInfoAt points to the last operator char, but here it should point to column 'after' the last char tagOfIdentToken, token.StartIndex, endCol @@ -69,7 +66,9 @@ module internal GotoDefinition = |> GotoDefinitionResult_DEPRECATED.MakeError | Some(colIdent, tag, qualId) -> if typedResults.HasFullTypeCheckInfo then - if Parser.tokenTagToTokenId tag <> Parser.TOKEN_IDENT then + // Used to be the Parser's internal definition, now hard-coded to avoid an IVT into the parser itsef. + // Dead code (aside from legacy tests), ignore + if tag <> FSharpTokenTag.IDENT then Strings.GotoDefinitionFailed_NotIdentifier() |> GotoDefinitionResult_DEPRECATED.MakeError else diff --git a/vsintegration/src/FSharp.LanguageService/Intellisense.fs b/vsintegration/src/FSharp.LanguageService/Intellisense.fs index b380bf13e20..dbf9d7fa5ab 100644 --- a/vsintegration/src/FSharp.LanguageService/Intellisense.fs +++ b/vsintegration/src/FSharp.LanguageService/Intellisense.fs @@ -224,7 +224,7 @@ type internal FSharpDeclarations_DEPRECATED(documentationBuilder, declarations: // We intercept this call only to get the initial extent // of what was committed to the source buffer. let result = decl.GetName(filterText, index) - FSharp.Compiler.Lexhelp.Keywords.QuoteIdentifierIfNeeded result + Keywords.QuoteIdentifierIfNeeded result override decl.IsCommitChar(commitCharacter) = // Usual language identifier rules... diff --git a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj index e192cccbf90..716a450a971 100644 --- a/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj +++ b/vsintegration/src/FSharp.ProjectSystem.PropertyPages/FSharp.PropertiesPages.vbproj @@ -66,44 +66,20 @@ - - Code - + - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - + + + + + + + + + - - Code - - - Code - + + @@ -114,12 +90,8 @@ True Settings.settings - - Code - - - Code - + + UserControl @@ -129,9 +101,7 @@ Form - - Code - + UserControl @@ -149,18 +119,12 @@ UserControl - - Code - + Form - - Code - - - Code - + + Form @@ -170,12 +134,8 @@ - - Code - - - Code - + + diff --git a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml index 7126d6efb8b..a14d4cbadfb 100644 --- a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml +++ b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml @@ -24,6 +24,10 @@ + + + diff --git a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs index eecea77c2ae..661fffa5699 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs +++ b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs @@ -357,6 +357,15 @@ public static string Suggest_names_for_errors_code_fix { } } + /// + /// Looks up a localized string similar to Text hover. + /// + public static string Text_hover { + get { + return ResourceManager.GetString("Text_hover", resourceCulture); + } + } + /// /// Looks up a localized string similar to Time until stale results are used (in milliseconds). /// @@ -401,5 +410,14 @@ public static string Unused_opens_code_fix { return ResourceManager.GetString("Unused_opens_code_fix", resourceCulture); } } + + /// + /// Looks up a localized string similar to (Preview) Use out of process language server. + /// + public static string Use_out_of_process_language_server { + get { + return ResourceManager.GetString("Use_out_of_process_language_server", resourceCulture); + } + } } } diff --git a/vsintegration/src/FSharp.UIResources/Strings.resx b/vsintegration/src/FSharp.UIResources/Strings.resx index 0d509d85de2..f4066cf3460 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.resx +++ b/vsintegration/src/FSharp.UIResources/Strings.resx @@ -231,4 +231,10 @@ Suggest names for unresolved identifiers + + (Preview) Use out of process language server + + + Text hover + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf index 2e928e6fc2f..9da4f3c87aa 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf @@ -102,6 +102,11 @@ _Plné podtržení + + Text hover + Text hover + + Remove unused open statements Odebrat nepoužívané otevřené výkazy @@ -192,6 +197,11 @@ Navrhovat názvy pro nerozpoznané identifikátory + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf index 339fde70c66..594bdd6a66d 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf @@ -102,6 +102,11 @@ _Durchgezogene Unterstreichung + + Text hover + Text hover + + Remove unused open statements Nicht verwendete "open"-Anweisungen entfernen @@ -192,6 +197,11 @@ Namen für nicht aufgelöste Bezeichner vorschlagen + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf index 48a36fbec9b..0dd79115a6d 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf @@ -102,6 +102,11 @@ Su_brayado sólido + + Text hover + Text hover + + Remove unused open statements Quitar instrucciones open no usadas @@ -192,6 +197,11 @@ Sugerir nombres para los identificadores no resueltos + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf index 4e987a4e393..6cd63471707 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf @@ -102,6 +102,11 @@ Soulig_nement avec un trait uni + + Text hover + Text hover + + Remove unused open statements Supprimer les instructions open inutilisées @@ -192,6 +197,11 @@ Suggérer des noms pour les identificateurs non résolus + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf index d2573be1eaa..a053b865918 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf @@ -102,6 +102,11 @@ Sottolineatura _continua + + Text hover + Text hover + + Remove unused open statements Rimuovi istruzioni OPEN inutilizzate @@ -192,6 +197,11 @@ Suggerisci nomi per gli identificatori non risolti + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf index 2d0b053a6e7..444ceb8b6da 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf @@ -102,6 +102,11 @@ 実線の下線(_S) + + Text hover + Text hover + + Remove unused open statements 未使用の Open ステートメントを削除する @@ -192,6 +197,11 @@ 未解決の識別子の名前を提案します + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf index 53f8a84e137..b8b14271ac4 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf @@ -102,6 +102,11 @@ 실선 밑줄(_S) + + Text hover + Text hover + + Remove unused open statements 사용되지 않는 open 문 제거 @@ -192,6 +197,11 @@ 확인되지 않은 식별자의 이름 제안 + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf index c3a7fb1f18c..e0f43b53d9f 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf @@ -102,6 +102,11 @@ Podkreślenie _ciągłe + + Text hover + Text hover + + Remove unused open statements Usuń nieużywane otwarte instrukcje @@ -192,6 +197,11 @@ Sugeruj nazwy w przypadku nierozpoznanych identyfikatorów + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf index f5d961bacfd..a23bcaf79d0 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf @@ -102,6 +102,11 @@ Sublinhado _sólido + + Text hover + Text hover + + Remove unused open statements Remover instruções abertas não usadas @@ -192,6 +197,11 @@ Sugerir nomes para identificadores não resolvidos + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf index 11f53ed12aa..8a8a30892ff 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf @@ -102,6 +102,11 @@ _Сплошнное подчеркивание + + Text hover + Text hover + + Remove unused open statements Удалить неиспользуемые открытые операторы @@ -192,6 +197,11 @@ Предлагать имена для неразрешенных идентификаторов + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf index fc87f84a73a..228bd69b6c7 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf @@ -102,6 +102,11 @@ _Kesintisiz alt çizgi + + Text hover + Text hover + + Remove unused open statements Kullanılmayan açık deyimleri kaldır @@ -192,6 +197,11 @@ Çözümlenmemiş tanımlayıcılar için ad öner + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf index 546048846d7..0db63d7df1f 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf @@ -102,6 +102,11 @@ 实线下划线(_S) + + Text hover + Text hover + + Remove unused open statements 删除未使用的 open 语句 @@ -192,6 +197,11 @@ 为未解析标识符建议名称 + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf index a17f37f491c..ea7c015a9d8 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf @@ -102,6 +102,11 @@ 實線底線(_S) + + Text hover + Text hover + + Remove unused open statements 移除未使用的 open 陳述式 @@ -192,6 +197,11 @@ 為未解析的識別碼建議名稱 + + (Preview) Use out of process language server + (Preview) Use out of process language server + + \ No newline at end of file diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index b9fbf322c04..a31777b9435 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -68,7 +68,7 @@ type internal FSharpLanguageServiceTestable() as this = member this.Initialize (sp, dp, prefs, sourceFact) = if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState artifacts <- Some (ProjectSitesAndFiles()) - let checker = FSharpChecker.Create(legacyReferenceResolver=FSharp.Compiler.MSBuildReferenceResolver.Resolver) + let checker = FSharpChecker.Create(legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver()) checker.BeforeBackgroundFileCheck.Add (fun (filename,_) -> UIThread.Run(fun () -> this.NotifyFileTypeCheckStateIsDirty(filename))) checkerContainerOpt <- Some (checker) serviceProvider <- Some sp diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs index 47e8e3b1134..6bc62eda151 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs @@ -101,25 +101,6 @@ type UsingMSBuild() = n ) 0 - - [] - member public this.``PublicSurfaceArea.DotNetReflection``() = - let ps = publicTypesInAsm @"FSharp.ProjectSystem.FSharp.dll" - Assert.AreEqual(1, ps) // BuildPropertyDescriptor - let ls = publicTypesInAsm @"FSharp.LanguageService.dll" - Assert.AreEqual(0, ls) - let compis = publicTypesInAsm @"FSharp.Compiler.Interactive.Settings.dll" - Assert.AreEqual(5, compis) - let compserver = publicTypesInAsm @"FSharp.Compiler.Server.Shared.dll" - Assert.AreEqual(0, compserver) - let lsbase = publicTypesInAsm @"FSharp.LanguageService.Base.dll" - Assert.AreEqual(0, lsbase) - let psbase = publicTypesInAsm @"FSharp.ProjectSystem.Base.dll" - Assert.AreEqual(17, psbase) - let fsi = publicTypesInAsm @"FSharp.VS.FSI.dll" - Assert.AreEqual(1, fsi) - - [] member public this.``ReconcileErrors.Test1``() = let (_solution, project, file) = this.CreateSingleFileProject(["erroneous"]) diff --git a/vsintegration/tests/UnitTests/QuickInfoTests.fs b/vsintegration/tests/UnitTests/QuickInfoTests.fs index 19d140de88b..bfa6c7df729 100644 --- a/vsintegration/tests/UnitTests/QuickInfoTests.fs +++ b/vsintegration/tests/UnitTests/QuickInfoTests.fs @@ -440,6 +440,6 @@ module Test = } """ let quickInfo = GetQuickInfoTextFromCode code - let expected = "property MyDistance.asNautical: MyDistance" + let expected = "property MyDistance.asNautical: MyDistance with get" Assert.AreEqual(expected, quickInfo) () diff --git a/vsintegration/tests/UnitTests/TestLib.LanguageService.fs b/vsintegration/tests/UnitTests/TestLib.LanguageService.fs index 6ba264252b5..f19ecb1ffc2 100644 --- a/vsintegration/tests/UnitTests/TestLib.LanguageService.fs +++ b/vsintegration/tests/UnitTests/TestLib.LanguageService.fs @@ -21,7 +21,7 @@ open Microsoft.VisualStudio.FSharp [] module internal Globals = - let checker = FSharpChecker.Create(legacyReferenceResolver=FSharp.Compiler.MSBuildReferenceResolver.Resolver) + let checker = FSharpChecker.Create(legacyReferenceResolver=LegacyMSBuildReferenceResolver.getResolver()) //open Internal.Utilities diff --git a/vsintegration/tests/UnitTests/Tests.Watson.fs b/vsintegration/tests/UnitTests/Tests.Watson.fs index 9cb4b7afa65..db44c89af0f 100644 --- a/vsintegration/tests/UnitTests/Tests.Watson.fs +++ b/vsintegration/tests/UnitTests/Tests.Watson.fs @@ -4,6 +4,7 @@ namespace Tests.Compiler.Watson #nowarn "52" // The value has been copied to ensure the original is not mutated +open FSharp.Compiler open FSharp.Compiler.AbstractIL.ILBinaryReader open FSharp.Compiler.AbstractIL.Internal.Library open FSharp.Compiler.CompileOps @@ -33,7 +34,7 @@ type Check = |] let ctok = AssumeCompilationThreadWithoutEvidence () - let _code = mainCompile (ctok, argv, FSharp.Compiler.MSBuildReferenceResolver.Resolver, false, ReduceMemoryFlag.No, CopyFSharpCoreFlag.No, FSharp.Compiler.ErrorLogger.QuitProcessExiter, ConsoleLoggerProvider(), None, None) + let _code = mainCompile (ctok, argv, LegacyMSBuildReferenceResolver.getResolver(), false, ReduceMemoryFlag.No, CopyFSharpCoreFlag.No, FSharp.Compiler.ErrorLogger.QuitProcessExiter, ConsoleLoggerProvider(), None, None) () with | :? 'TException as e -> diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj index 1395972fcf4..7b11fb96c6c 100644 --- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj +++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj @@ -165,10 +165,10 @@ Roslyn\DocumentHighlightsServiceTests.fs - + {{FSCoreVersion}} $(FSCoreVersion) - +