diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000..07354ce197e6f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# Contributing to Roslyn + +Guidelines for contributing to the Roslyn repo. + +## Submitting Pull Requests + +- **DO** ensure submissions pass all Jenkins legs and are merge conflict free. +- **DO** follow the [.editorconfig](http://editorconfig.org/) settings for each directory. +- **DO** submit language feature requests as issues in the [C# language](https://github.com/dotnet/csharplang#discussion) / [VB language](https://github.com/dotnet/vblang) repos. Once a feature is championed and validated by LDM a developer will be assigned to help begin a prototype on this repo inside a feature branch. +- **DO NOT** submit language features as PRs to this repo first, or they will likely be declined. +- **DO** submit issues for other features. This facilitates discussion of a feature separately from its implementation, and increases the acceptance rates for pull requests. +- **DO NOT** submit large code formatting changes without discussing with the team first. + +When you are ready to proceed with making a change, get set up to [build](https://github.com/dotnet/roslyn/blob/master/docs/contributing/Building%2C%20Debugging%2C%20and%20Testing%20on%20Windows.md) the code and familiarize yourself with our developer workflow. + +These two blogs posts on contributing code to open source projects are good too: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don’t “Push” Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik. + +## Creating Issues + +- **DO** use a descriptive title that identifies the issue to be addressed or the requested feature. For example when describing an issue where the compiler is not behaving as expected, write your bug title in terms of what the compiler should do rather than what it is doing – “C# compiler should report CS1234 when Xyz is used in Abcd.” +- **DO** specify a detailed description of the issue or requested feature. +- **DO** provide the following for bug reports + - Describe the expected behavior and the actual behavior. If it is not self-evident such as in the case of a crash, provide an explanation for why the expected behavior is expected. + - Provide example code that reproduces the issue. + - Specify any relevant exception messages and stack traces. +- **DO** subscribe to notifications for the created issue in case there are any follow up questions. + +## Coding Style + +The Roslyn project is a member of the [.NET Foundation](https://github.com/orgs/dotnet) and follow the same [developer guide](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md). The repo also includes [.editorconfig](http://editorconfig.org) files to help enforce this convention. Contributors should ensure they follow these guidelines when making submissions. + +### CSharp + +- **DO** use the coding style outlined in the [.NET Foundation Coding Guidelines](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md) +- **DO** use plain code to validate parameters at public boundaries. Do not use Contracts or magic helpers. + +```csharp +if (argument == null) +{ + throw new ArgumentNullException(nameof(argument)); +} +``` + +- **DO** use `Debug.Assert()` for checks not needed in retail builds. Always include a “message” string in your assert to identify failure conditions. Add assertions to document assumptions on non-local program state or parameter values, e.g. “At this point in parsing the scanner should have been advanced to a ‘.’ token by the caller”. +- **DO** avoid allocations in compiler hot paths: + - Avoid LINQ. + - Avoid using `foreach` over collections that do not have a `struct` enumerator. + - Consider using an object pool. There are many usages of object pools in the compiler to see an example. + +### Visual Basic Conventions + +- **DO** apply the spirit of C# guidelines to Visual Basic when there are natural analogs. +- **DO** place all field declarations at the beginning of a type definition + +### Tips 'n' Tricks +Our team finds using [this enhanced source view](http://source.roslyn.io/) of Roslyn helpful when developing. \ No newline at end of file diff --git a/Makefile b/Makefile index c407143699ef2..c00e72729f700 100644 --- a/Makefile +++ b/Makefile @@ -43,8 +43,8 @@ bootstrap: restore $(BUILD_CMD) src/Compilers/CSharp/CscCore && \ $(BUILD_CMD) src/Compilers/VisualBasic/VbcCore && \ mkdir -p $(BOOTSTRAP_PATH)/csc && mkdir -p $(BOOTSTRAP_PATH)/vbc && \ - dotnet publish -r $(RUNTIME_ID) src/Compilers/CSharp/CscCore -o $(BOOTSTRAP_PATH)/csc && \ - dotnet publish -r $(RUNTIME_ID) src/Compilers/VisualBasic/VbcCore -o $(BOOTSTRAP_PATH)/vbc + dotnet publish -c $(BUILD_CONFIGURATION) -r $(RUNTIME_ID) src/Compilers/CSharp/CscCore -o $(BOOTSTRAP_PATH)/csc && \ + dotnet publish -c $(BUILD_CONFIGURATION) -r $(RUNTIME_ID) src/Compilers/VisualBasic/VbcCore -o $(BOOTSTRAP_PATH)/vbc rm -rf Binaries/$(BUILD_CONFIGURATION) test: diff --git a/NuGet.Config b/NuGet.Config index 43d2539f28212..0ea90a3391db9 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -19,6 +19,7 @@ + diff --git a/Roslyn.sln b/Roslyn.sln index c865f2080720b..d84b0ca587bd8 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26507.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{A41D1B99-F489-4C43-BBDF-96D61B19A6B9}" EndProject diff --git a/build/NuGetAdditionalFiles/Microsoft.Net.Compilers.props b/build/NuGetAdditionalFiles/Microsoft.Net.Compilers.props index 769370f3a3252..d65f4c022464d 100644 --- a/build/NuGetAdditionalFiles/Microsoft.Net.Compilers.props +++ b/build/NuGetAdditionalFiles/Microsoft.Net.Compilers.props @@ -23,6 +23,8 @@ AssemblyFile="$(MSBuildThisFileDirectory)..\tools\Microsoft.Build.Tasks.CodeAnalysis.dll" /> + false diff --git a/build/Targets/Packages.props b/build/Targets/Packages.props index d359d97267662..877de7a25362b 100644 --- a/build/Targets/Packages.props +++ b/build/Targets/Packages.props @@ -36,9 +36,9 @@ 0.8.31-beta 1.0.35 1.1.0 - 1.0.0-beta1-61618-01 - 1.5.0 - 1.2.0 + 1.0.0-beta1-61708-01 + 1.6.0-beta2-25304 + 1.3.0-beta1-61619-01 4.7.1-alpha-00001 3.13.8 15.0.26201-alpha @@ -47,7 +47,7 @@ 8.0.0.0-alpha 2.0.1 2.0.0-rc2-61102-09 - 1.2.0-beta2 + 2.0.0-beta1-61628-06 1.1.0 2.3.0-dev-56735-00 5.0.0 @@ -221,6 +221,7 @@ 12.0.4 8.0.0.0-alpha 2.2.0-beta4-build3444 + 2.2.0-beta4-build3444 1.0.2-prerelease-00104 2.2.0-beta4-build3444 2.2.0-beta4-build1194 diff --git a/build/Targets/Settings.props b/build/Targets/Settings.props index f0933dfe67e3a..9e4567f567f03 100644 --- a/build/Targets/Settings.props +++ b/build/Targets/Settings.props @@ -14,7 +14,7 @@ $(RepoRoot)build\ToolsetPackages\ RoslynTools.Microsoft.VSIXExpInstaller 0.2.1-beta - 2.0.0-beta1-61628-06 + $(MicrosoftNetRoslynDiagnosticsVersion) $(NuGetPackageRoot)\Microsoft.Net.RoslynDiagnostics\$(RoslynDiagnosticsNugetPackageVersion)\build\Microsoft.Net.RoslynDiagnostics.props $(NuGetPackageRoot)\Roslyn.Build.Util\0.9.4-portable\lib\dotnet\Roslyn.MSBuild.Util.dll win7-x64 @@ -239,6 +239,7 @@ $(InitialDefineConstants) + On $(NoWarn);1591 diff --git a/build/scripts/test-determinism.ps1 b/build/scripts/test-determinism.ps1 index 74f497ebe9e65..c087c9510729a 100644 --- a/build/scripts/test-determinism.ps1 +++ b/build/scripts/test-determinism.ps1 @@ -46,7 +46,7 @@ function Run-Build() { } Write-Host "Building the Solution" - Exec-Command $msbuild "/nologo /v:m /nodeReuse:false /m /p:DebugDeterminism=true /p:BootstrapBuildPath=$script:bootstrapDir /p:Features=`"debug-determinism;pdb-path-determinism`" /p:UseRoslynAnalyzers=false $pathMapBuildOption $sln" + Exec-Command $msbuild "/nologo /v:m /nodeReuse:false /m /p:DebugDeterminism=true /p:BootstrapBuildPath=$script:bootstrapDir /p:Features=`"debug-determinism`" /p:UseRoslynAnalyzers=false $pathMapBuildOption $sln" } finally { Pop-Location diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index ab18ef653b598..4ad73499c3690 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,11 +10,11 @@ efforts behind them. | Feature | Branch | State | Developers | Reviewer | LDM Champ | | ------- | ------ | ----- | ---------- | -------- | --------- | -| [Async Main](https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md) | [async-main](https://github.com/dotnet/roslyn/tree/features/async-main) | Prototype | [tyoverby](https://github.com/tyoverby) | [vsadov](https://github.com/vsadov) | [stephentoub](https://github.com/stephentoub) | +| [Async Main](https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md) | master | Merged | [tyoverby](https://github.com/tyoverby) | [vsadov](https://github.com/vsadov) | [stephentoub](https://github.com/stephentoub) | | [Default Expressions](https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-default.md) | master | Merged | [jcouv](https://github.com/jcouv) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv) | -| [Ref Assemblies](https://github.com/dotnet/roslyn/blob/features/refout/docs/features/refout.md) | [refout](https://github.com/dotnet/roslyn/tree/features/refout) | Integration & validation | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | N/A | -| [Infer tuple names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md) | master | Merged | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/generics-pattern-match.md) | https://github.com/dotnet/roslyn/pull/18784 | Implementation | [gafter](https://github.com/gafter) | [agocke](https://github.com/agocke) | [gafter](https://github.com/gafter) | +| [Ref Assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md) | master | Merged (MSBuild integration ongoing) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | N/A | +| [Infer tuple names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md) | master | Merged | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | +| [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/generics-pattern-match.md) | master | Merged | [gafter](https://github.com/gafter) | [agocke](https://github.com/agocke) | [gafter](https://github.com/gafter) | # C# 7.2 diff --git a/docs/compilers/CSharp/CommandLine.md b/docs/compilers/CSharp/CommandLine.md index 8244ce7bfc71e..79c30791229c9 100644 --- a/docs/compilers/CSharp/CommandLine.md +++ b/docs/compilers/CSharp/CommandLine.md @@ -4,6 +4,7 @@ | ---- | ---- | | **OUTPUT FILES** | | `/out:`*file* | Specify output file name (default: base name of file with main class or first file) +| `/refout:`*file* | Specify the reference assembly's output file name | `/target:exe` | Build a console executable (default) (Short form: `/t:exe`) | `/target:winexe` | Build a Windows executable (Short form: `/t:winexe` ) | `/target:library` | Build a library (Short form: `/t:library`) @@ -29,9 +30,14 @@ | `/linkresource`:*resinfo* | Link the specified resource to this assembly (Short form: `/linkres`) Where the *resinfo* format is *file*{`,`*string name*{`,``public``|``private`}} | **CODE GENERATION** | `/debug`{`+`|`-`} | Emit (or do not Emit) debugging information -| `/debug`:{`full`|`pdbonly`|`portable`} | Specify debugging type (`full` is default, and enables attaching a debugger to a running program. `portable` is a cross-platform format) +| `/debug`:`full` | Emit debugging information to .pdb file using default format for the current platform: _Windows PDB_ on Windows, _Portable PDB_ on other systems +| `/debug`:`pdbonly` | Same as `/debug:full`. For backward compatibility. +| `/debug`:`portable` | Emit debugging information to to .pdb file using cross-platform [Portable PDB format](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/portable_pdb.md) +| `/debug`:`embedded` | Emit debugging information into the .dll/.exe itself (.pdb file is not produced) using [Portable PDB format](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/portable_pdb.md). +| `/sourcelink`:*file* | [Source link](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/source_link.md) info to embed into PDB. | `/optimize`{`+`|`-`} | Enable optimizations (Short form: `/o`) | `/deterministic` | Produce a deterministic assembly (including module version GUID and timestamp) +| `/refonly | Produce a reference assembly, instead of a full assembly, as the primary output | **ERRORS AND WARNINGS** | `/warnaserror`{`+`|`-`} | Report all warnings as errors | `/warnaserror`{`+`|`-`}`:`*warn list* | Report specific warnings as errors diff --git a/docs/compilers/Visual Basic/CommandLine.md b/docs/compilers/Visual Basic/CommandLine.md index 0a7afcefd67da..052da8442d06c 100644 --- a/docs/compilers/Visual Basic/CommandLine.md +++ b/docs/compilers/Visual Basic/CommandLine.md @@ -4,6 +4,7 @@ | ---- | ---- | | **OUTPUT FILE** | `/out:`*file* | Specifies the output file name. +| `/refout:`*file* | Specify the reference assembly's output file name | `/target:exe` | Create a console application (default). (Short form: `/t`) | `/target:winexe` | Create a Windows application. | `/target:library` | Create a library assembly. @@ -27,13 +28,19 @@ | `/win32manifest:`*file* | The provided file is embedded in the manifest section of the output PE. | `/win32resource:`*file* | Specifies a Win32 resource file (.res). | **CODE GENERATION** +| `/debug`{`+`|`-`} | Emit debugging information. +| `/debug`:`full` | Emit debugging information to .pdb file using default format for the current platform: _Windows PDB_ on Windows, _Portable PDB_ on other systems +| `/debug`:`pdbonly` | Same as `/debug:full`. For backward compatibility. +| `/debug`:`portable` | Emit debugging information to to .pdb file using cross-platform [Portable PDB format](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/portable_pdb.md) +| `/debug`:`embedded` | Emit debugging information into the .dll/.exe itself (.pdb file is not produced) using [Portable PDB format](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/portable_pdb.md). +| `/sourcelink`:*file* | [Source link](https://github.com/dotnet/core/blob/master/Documentation/diagnostics/source_link.md) info to embed into PDB. | `/optimize`{`+`|`-`} | Enable optimizations. | `/removeintchecks`{`+`|`-`} | Remove integer checks. Default off. -| `/debug`{`+`|`-`} | Emit debugging information. | `/debug:full` | Emit full debugging information (default). | `/debug:portable` | Emit debugging information in the portable format. | `/debug:pdbonly` | Emit PDB file only. | `/deterministic` | Produce a deterministic assembly (including module version GUID and timestamp) +| `/refonly | Produce a reference assembly, instead of a full assembly, as the primary output | **ERRORS AND WARNINGS** | `/nowarn` | Disable all warnings. | `/nowarn:`*number_list* | Disable a list of individual warnings. diff --git a/docs/contributing/Building, Debugging, and Testing on Unix.md b/docs/contributing/Building, Debugging, and Testing on Unix.md index 1ea526cdcc90e..cb2b21c691309 100644 --- a/docs/contributing/Building, Debugging, and Testing on Unix.md +++ b/docs/contributing/Building, Debugging, and Testing on Unix.md @@ -10,4 +10,4 @@ After building run `make test` in order to run the unit tests. # Contributing -Please see [Contributing Code](https://github.com/dotnet/roslyn/wiki/Contributing-Code) for details on contributing changes back to the code. +Please see [Contributing Code](https://github.com/dotnet/roslyn/blob/master/CONTRIBUTING.md) for details on contributing changes back to the code. diff --git a/docs/contributing/Building, Debugging, and Testing on Windows.md b/docs/contributing/Building, Debugging, and Testing on Windows.md index 14b8af173cd45..0148f02cb3386 100644 --- a/docs/contributing/Building, Debugging, and Testing on Windows.md +++ b/docs/contributing/Building, Debugging, and Testing on Windows.md @@ -119,4 +119,4 @@ csc and vbc inside it. You can check the cibuild.cmd and see how it is used. ## Contributing -Please see [Contributing Code](https://github.com/dotnet/roslyn/wiki/Contributing-Code) for details on contributing changes back to the code. +Please see [Contributing Code](https://github.com/dotnet/roslyn/blob/master/CONTRIBUTING.md)) for details on contributing changes back to the code. diff --git a/docs/features/async-main.md b/docs/features/async-main.md new file mode 100644 index 0000000000000..b98068cd8c603 --- /dev/null +++ b/docs/features/async-main.md @@ -0,0 +1,12 @@ +# Async Task Main +## [dotnet/csharplang proposal](https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md) + +## Technical Details + +* The compiler must recognize `Task` and `Task` as valid entrypoint return types in addition to `void` and `int`. +* The compiler must allow `async` to be placed on a main method that returns a `Task` or a `Task` (but not void). +* The compiler must generate a shim method `$EntrypointMain` that mimics the arguments of the user-defined main. + * `static async Task Main(...)` -> `static void $EntrypointMain(...)` + * `static async Task Main(...)` -> `static int $EntrypointMain(...)` + * The parameters between the user-defined main and the generated main should match exactly. +* The body of the generated main should be `return Main(args...).GetAwaiter().GetResult();` \ No newline at end of file diff --git a/docs/features/async-main.test.md b/docs/features/async-main.test.md new file mode 100644 index 0000000000000..9b4a51467e559 --- /dev/null +++ b/docs/features/async-main.test.md @@ -0,0 +1,99 @@ +Main areas to test: +* Signature acceptance / rejection +* Method body creation + +# Signature acceptance / rejection + +## Single Mains +Classes that contain only a single "main" method + +### Single legal main +* Past: Ok +* New 7: Ok +* New 7.1 Ok + +### Single async (void) main +* Past: ERR (Async can't be main) +* New 7: ERR (Update to get this to work) +* New 7.1: Ok + +### Single async (Task) main +* Past: ERR (No entrypoints found), WARN (has the wrong signature to be an entry point) +* New 7: ERR (Update to get this to work) +* New 7.1 Ok + +### Single async (Task) main +* Past: ERR (No entrypoints found), WARN (has the wrong signature) +* New 7: ERR (Update to get this to work) +* New 7.1: Ok + +## Multiple Mains +Classes that contain more than one main + +### Multiple legal mains +* Past: Err +* New 7: Err +* New 7.1: Err + +### Single legal main, single async (void) main +* Past: Err (an entrypoint cannot be marked with async) +* New 7: Err (new error here?) +* New 7.1: Err (new error here? "void is not an acceptable async return type") + +### Single legal main, single legal Task main +* Past: Ok (warning: task main has wrong signature) +* New 7: Ok (new warning here) +* New 7.1: Ok (new warning here?) + +### Single legal main, single legal Task main +* Past: Ok (warning: task main has wrong signature) +* New 7: Ok (new warning here) +* New 7.1: Ok (new warning here?) + +# Method body creation + +* Inspect IL for correct codegen. +* Make sure that attributes are correctly applied to the synthesized mains. + +## Broken methods on Task and Task + +* GetAwatier or GetResult are missing +* GetAwaiter or GetResult don't have the required signature +* Has the right shape, but types are missing + +## Task or Task is missing + +This will be caught during entrypoint detection, should be a binding error. + +## Void or int is missing + +If Task can be found, but void or int can't be found, then the compiler should behave gracefully. + +# Vlad Test Plan + +## Public interface of compiler APIs + -N/A? + +## General functionality +- `Task` and `Task` returns are allowed. Do we require `async`. +- Multiple valid/invalid async candidates. +- process exit code on success (void and int) and on exception +- `/main:` cmd switch is still functional + +## Old versions, compat +- langver +- when both async and regular Main avaialble. With old/new compiler versions. +- async void Main. With old.new langver. With another applicable Main present. + +## Type and members +- Access modifiers (public, protected, internal, protected internal, private), static modifier +- Parameter modifiers (ref, out, params) +- STA attribute +- Partial method +- Named and optional parameters +- `Task` +- Task-Like types + +## Debug +- F11 works +- stepping through Main diff --git a/docs/features/refout.md b/docs/features/refout.md new file mode 100644 index 0000000000000..9231dd37ee3f9 --- /dev/null +++ b/docs/features/refout.md @@ -0,0 +1,78 @@ +# Reference assemblies + +Reference assemblies are metadata-only assemblies with the minimum amount of metadata to preserve the compile-time behavior of consumers (diagnostics may be affected, though). +The compiler may choose to remove more metadata in later versions, if it is determined to be safe (ie. respects the principle above). + +## Scenarios +There are 4 scenarios: + +1. The traditional one, where an assembly is emitted as the primary output (`/out` command-line parameter, or `peStream` parameter in `Compilation.Emit` APIs). +2. The IDE scenario, where the metadata-only assembly is emitted (via `Emit` API), still as the primary output. Later on, the IDE is interested to get metadata-only assemblies even when there are errors in the compilation. +3. The CoreFX scenario, where only the ref assembly is emitted, still as the primary output (`/refonly` command-line parameter) +4. The MSBuild scenario, which is the new scenario, where both a real assembly is emitted as the primary output, and a ref assembly is emitted as the secondary output (`/refout` command-line parameter, or `metadataPeStream` parameter in `Emit`). + + +## Definition of ref assemblies +Metadata-only assembly have their method bodies replaced with a single `throw null` body, but include all members except anonymous types. The reason for using `throw null` bodies (as opposed to no bodies) is so that PEVerify could run and pass (thus validating the completeness of the metadata). +Ref assemblies will include an assembly-level `ReferenceAssembly` attribute. This attribute may be specified in source (then we won't need to synthesize it). Because of this attribute, runtimes will refuse to load ref assemblies for execution (but they can still be loaded Reflection-only mode). +Ref assemblies further remove metadata (private members) from metadata-only assemblies: + +- A ref assembly will only have references for what it needs in the API surface. The real assembly may have additional references related to specific implementations. For instance, the ref assembly for `class C { private void M() { dynamic d = 1; ... } }` will not reference any types required for `dynamic`. +- Private function-members (methods, properties and events) will be removed. If there are no `InternalsVisibleTo` attributes, do the same for internal function-members +- But all types (including private or nested types) must be kept in ref assemblies. All attributes must be kept (even internal ones). +- All virtual methods will be kept. Explicit interface implementations will be kept. +- All fields of a struct will be kept. (This is a candidate for post-C#-7.1 refinement) + +## API changes + +### Command-line +Two mutually exclusive command-line parameters will be added to `csc.exe` and `vbc.exe`: +- `/refout` +- `/refonly` + +The `/refout` parameter specifies a file path where the ref assembly should be output. This translates to `metadataPeStream` in the `Emit` API (see details below). The filename for the ref assembly should generally match that of the primary assembly (we will warn when they don't match). The recommended convention (used by MSBuild) is to place the ref assembly in a "ref/" sub-folder relative to the primary assembly. + +The `/refonly` parameter is a flag that indicates that a ref assembly should be output instead of an implementation assembly, as the primary output. +The `/refonly` parameter is not allowed together with the `/refout` parameter, as it doesn't make sense to have both the primary and secondary outputs be ref assemblies. Also, the `/refonly` parameter silently disables outputting PDBs, as ref assemblies cannot be executed. +The `/refonly` parameter translates to `EmitMetadataOnly` being `true`, and `IncludePrivateMembers` being `false` in the `Emit` API (see details below). +Neither `/refonly` nor `/refout` are permitted with net modules (`/target:module`, `/addmodule` options). + +The compilation from the command-line will either produce both assemblies (implementation and ref) or neither. There is no "partial success" scenario. +When the compiler produces documentation, it is un-affected by either the `/refonly` or `/refout` parameters. This may change in the future. +The main purpose of the `/refout` option is to speed up incremental build scenarios. The current implementation for this flag can produce a ref assembly with more metadata than `/refonly` (for instance, anonymous types). This is a candidate for post-C#-7.1 refinement. + +### CscTask/CoreCompile +The `CoreCompile` target will support a new output, called `IntermediateRefAssembly`, which parallels the existing `IntermediateAssembly`. +The `Csc` task will support a new output, called `OutputRefAssembly`, which parallels the existing `OutputAssembly`. +Both of those basically map to the `/refout` command-line parameter. + +An additional task, called `CopyRefAssembly`, will be provided along with the existing `Csc` task. It takes a `SourcePath` and a `DestinationPath` and generally copies the file from the source over to the destination. But if it can determine that the contents of those two files match (by comparing their MVIDs, see details below), then the destination file is left untouched. + +As a side-note, `CopyRefAssembly` uses the same assembly resolution/redirection trick as `Csc` and `Vbc`, to avoid type loading problems with `System.IO.FileSystem`. + +### CodeAnalysis APIs +It is already possible to produce metadata-only assemblies by using `EmitOptions.EmitMetadataOnly`, which is used in IDE scenarios with cross-language dependencies. +The compiler will be updated to honour the `EmitOptions.IncludePrivateMembers` flag as well. When combined with `EmitMetadataOnly` or a `metadataPeStream` in `Emit`, a ref assembly will be produced. +The diagnostic check for emitting methods lacking a body (`void M();`) will be filtered from declaration diagnostics, so that code will successfully emit with `EmitMetadataOnly`. +Later on, the `EmitOptions.TolerateErrors` flag will allow emitting error types as well. +`Emit` is also modified to produce a new PE section called ".mvid" containing a copy of the MVID, when producing ref assemblies. This makes it easy for `CopyRefAssembly` to extract and compare MVIDs from ref assemblies. + +Going back to the 4 driving scenarios: +1. For a regular compilation, `EmitMetadataOnly` is left to `false` and no `metadataPeStream` is passed into `Emit`. +2. For the IDE scenario, `EmitMetadataOnly` is set to `true`, but `IncludePrivateMembers` is left to `true`. +3. For the CoreFX scenario, ref assembly source code is used as input, `EmitMetadataOnly` is set to `true`, and `IncludePrivateMembers` is set to `false`. +4. For the MSBuild scenario, `EmitMetadataOnly` is left to `false`, a `metadataPeStream` is passed in and `IncludePrivateMembers` is set to `false`. + +## Future +As mentioned above, there may be further refinements after C# 7.1: +- Further reduce the metadata in ref assemblies produced by `/refout`, to match those produced by `/refonly`. +- Controlling internals (producing public ref assemblies) +- Produce ref assemblies even when there are errors outside method bodies (emitting error types when `EmitOptions.TolerateErrors` is set) +- When the compiler produces documentation, the contents produced could be filtered down to match the APIs that go into the primary output. In other words, the documentation could be filtered down when using the `/refonly` parameter. + +## Open questions + +## Related issues +- Produce ref assemblies from command-line and msbuild (https://github.com/dotnet/roslyn/issues/2184) +- Refine what is in reference assemblies and what diagnostics prevent generating one (https://github.com/dotnet/roslyn/issues/17612) +- [Are private members part of the API surface?](http://blog.paranoidcoding.com/2016/02/15/are-private-members-api-surface.html) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs index 371874bcdeae1..61611318c2954 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs @@ -29,7 +29,7 @@ private BoundExpression BindAwait(BoundExpression expression, SyntaxNode node, D bool hasErrors = ReportBadAwaitWithoutAsync(node, diagnostics) | ReportBadAwaitContext(node, diagnostics) | - !GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, node, diagnostics); + !GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, out _, node, diagnostics); // Spec 7.7.7.2: // The expression await t is classified the same way as the expression (t).GetAwaiter().GetResult(). Thus, @@ -209,17 +209,19 @@ private bool ReportBadAwaitContext(SyntaxNode node, DiagnosticBag diagnostics) /// Finds and validates the required members of an awaitable expression, as described in spec 7.7.7.1. /// /// True if the expression is awaitable; false otherwise. - private bool GetAwaitableExpressionInfo( + internal bool GetAwaitableExpressionInfo( BoundExpression expression, out MethodSymbol getAwaiter, out PropertySymbol isCompleted, out MethodSymbol getResult, + out BoundExpression getAwaiterGetResultCall, SyntaxNode node, DiagnosticBag diagnostics) { getAwaiter = null; isCompleted = null; getResult = null; + getAwaiterGetResultCall = null; if (!ValidateAwaitedExpression(expression, node, diagnostics)) { @@ -231,7 +233,8 @@ private bool GetAwaitableExpressionInfo( return true; } - if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter)) + BoundExpression getAwaiterCall = null; + if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter, out getAwaiterCall)) { return false; } @@ -239,7 +242,7 @@ private bool GetAwaitableExpressionInfo( TypeSymbol awaiterType = getAwaiter.ReturnType; return GetIsCompletedProperty(awaiterType, node, expression.Type, diagnostics, out isCompleted) && AwaiterImplementsINotifyCompletion(awaiterType, node, diagnostics) - && GetGetResultMethod(awaiterType, node, expression.Type, diagnostics, out getResult); + && GetGetResultMethod(getAwaiterCall, node, expression.Type, diagnostics, out getResult, out getAwaiterGetResultCall); } /// @@ -273,19 +276,21 @@ private static bool ValidateAwaitedExpression(BoundExpression expression, Syntax /// NOTE: this is an error in the spec. An extension method of the form /// Awaiter<T> GetAwaiter<T>(this Task<T>) may be used. /// - private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod) + private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod, out BoundExpression getAwaiterCall) { if (expression.Type.SpecialType == SpecialType.System_Void) { Error(diagnostics, ErrorCode.ERR_BadAwaitArgVoidCall, node); getAwaiterMethod = null; + getAwaiterCall = null; return false; } - var getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray.Empty, diagnostics); + getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray.Empty, diagnostics); if (getAwaiterCall.HasAnyErrors) // && !expression.HasAnyErrors? { getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -293,6 +298,7 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Di { Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type); getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -303,6 +309,7 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Di { Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type); getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -383,28 +390,31 @@ private bool AwaiterImplementsINotifyCompletion(TypeSymbol awaiterType, SyntaxNo /// Spec 7.7.7.1: /// An Awaiter A has an accessible instance method GetResult with no parameters and no type parameters. /// - private bool GetGetResultMethod(TypeSymbol awaiterType, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod) + private bool GetGetResultMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod, out BoundExpression getAwaiterGetResultCall) { - var receiver = new BoundLiteral(node, ConstantValue.Null, awaiterType); - var getResultCall = MakeInvocationExpression(node, receiver, WellKnownMemberNames.GetResult, ImmutableArray.Empty, diagnostics); - if (getResultCall.HasAnyErrors) + var awaiterType = awaiterExpression.Type; + getAwaiterGetResultCall = MakeInvocationExpression(node, awaiterExpression, WellKnownMemberNames.GetResult, ImmutableArray.Empty, diagnostics); + if (getAwaiterGetResultCall.HasAnyErrors) { getResultMethod = null; + getAwaiterGetResultCall = null; return false; } - if (getResultCall.Kind != BoundKind.Call) + if (getAwaiterGetResultCall.Kind != BoundKind.Call) { Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } - getResultMethod = ((BoundCall)getResultCall).Method; + getResultMethod = ((BoundCall)getAwaiterGetResultCall).Method; if (getResultMethod.IsExtensionMethod) { Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } @@ -412,6 +422,7 @@ private bool GetGetResultMethod(TypeSymbol awaiterType, SyntaxNode node, TypeSym { Error(diagnostics, ErrorCode.ERR_BadAwaiterPattern, node, awaiterType, awaitedExpressionType); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 42648bd0353c2..9a72799ed7212 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Roslyn.Utilities; -using System.Collections.Immutable; -using System; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -114,40 +113,13 @@ protected BoundExpression CreateConversion( syntax, source, conversion, - IsCheckedConversion(source.Type, destination), + @checked: CheckOverflowAtRuntime, explicitCastInCode: isCast && !wasCompilerGenerated, constantValueOpt: constantValue, type: destination) { WasCompilerGenerated = wasCompilerGenerated }; } - private bool IsCheckedConversion(TypeSymbol source, TypeSymbol target) - { - Debug.Assert((object)target != null); - - if ((object)source == null || !CheckOverflowAtRuntime) - { - return false; - } - - if (source.IsDynamic()) - { - return true; - } - - SpecialType sourceST = source.StrippedType().EnumUnderlyingType().SpecialType; - SpecialType targetST = target.StrippedType().EnumUnderlyingType().SpecialType; - - // integral to double or float is never checked, but float/double to integral - // may be checked. - bool sourceIsNumeric = SpecialType.System_Char <= sourceST && sourceST <= SpecialType.System_Double; - bool targetIsNumeric = SpecialType.System_Char <= targetST && targetST <= SpecialType.System_UInt64; - - return - sourceIsNumeric && (targetIsNumeric || target.IsPointerType()) || - targetIsNumeric && source.IsPointerType(); - } - protected BoundExpression CreateUserDefinedConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics) { if (!conversion.IsValid) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index b1ad911ea2e27..4703735b71f37 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -3433,6 +3433,7 @@ private BoundExpression BindConstructorInitializerCore( invokedAsExtensionMethod: false, argsToParamsOpt: memberResolutionResult.Result.ArgsToParamsOpt, resultKind: LookupResultKind.Viable, + binderOpt: this, type: constructorReturnType, hasErrors: hasErrors) { WasCompilerGenerated = initializerArgumentListOpt == null }; @@ -4526,6 +4527,7 @@ protected BoundExpression BindClassCreationExpression( memberResolutionResult.Result.ArgsToParamsOpt, constantValueOpt, boundInitializerOpt, + this, type, hasError); @@ -4665,7 +4667,7 @@ private BoundExpression BindComImportCoClassCreationExpression(ObjectCreationExp var creation = (BoundObjectCreationExpression)classCreation; return creation.Update(creation.Constructor, creation.ConstructorsGroup, creation.Arguments, creation.ArgumentNamesOpt, creation.ArgumentRefKindsOpt, creation.Expanded, creation.ArgsToParamsOpt, creation.ConstantValueOpt, - creation.InitializerExpressionOpt, interfaceType); + creation.InitializerExpressionOpt, creation.BinderOpt, interfaceType); case BoundKind.BadExpression: var bad = (BoundBadExpression)classCreation; @@ -6829,6 +6831,8 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentRefKinds, isExpanded, argsToParams, + this, + false, property.Type, gotError); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 01444f1e0d683..19d872a4142fd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -982,7 +982,7 @@ private BoundCall BindInvocationExpressionContinued( { return new BoundCall(node, receiver, method, args, argNames, argRefKinds, isDelegateCall: true, expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError); + argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); } else { @@ -1003,7 +1003,7 @@ private BoundCall BindInvocationExpressionContinued( return new BoundCall(node, receiver, method, args, argNames, argRefKinds, isDelegateCall: false, expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError); + argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); } } @@ -1124,7 +1124,7 @@ private BoundCall CreateBadCall( args = BuildArgumentsForErrorRecovery(analyzedArguments, methods); var argNames = analyzedArguments.GetNames(); var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); - return BoundCall.ErrorCall(node, receiver, method, args, argNames, argRefKinds, isDelegate, invokedAsExtensionMethod: invokedAsExtensionMethod, originalMethods: methods, resultKind: resultKind); + return BoundCall.ErrorCall(node, receiver, method, args, argNames, argRefKinds, isDelegate, invokedAsExtensionMethod: invokedAsExtensionMethod, originalMethods: methods, resultKind: resultKind, binder: this); } private static bool IsUnboundGeneric(MethodSymbol method) @@ -1330,7 +1330,7 @@ private BoundCall CreateBadCall( var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); var originalMethods = (expr.Kind == BoundKind.MethodGroup) ? ((BoundMethodGroup)expr).Methods : ImmutableArray.Empty; - return BoundCall.ErrorCall(node, expr, method, args, argNames, argRefKinds, isDelegateCall: false, invokedAsExtensionMethod: false, originalMethods: originalMethods, resultKind: resultKind); + return BoundCall.ErrorCall(node, expr, method, args, argNames, argRefKinds, isDelegateCall: false, invokedAsExtensionMethod: false, originalMethods: originalMethods, resultKind: resultKind, binder: this); } private static TypeSymbol GetCommonTypeOrReturnType(ImmutableArray members) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 7a540dfa613b3..6267ce3077e6e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -224,7 +224,7 @@ private static BoundCall ReverseLastTwoParameterOrder(BoundCall result) return result.Update( result.ReceiverOpt, result.Method, arguments.ToImmutableAndFree(), default(ImmutableArray), default(ImmutableArray), result.IsDelegateCall, result.Expanded, result.InvokedAsExtensionMethod, - argsToParams.ToImmutableAndFree(), result.ResultKind, result.Type); + argsToParams.ToImmutableAndFree(), result.ResultKind, result.BinderOpt, result.Type); } private void ReduceQuery(QueryTranslationState state, DiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index af58cb970d4e1..9668fc0da4d63 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1153,8 +1153,6 @@ static private ErrorCode GetThisLvalueError(BindValueKind kind) return ErrorCode.ERR_AddrOnReadOnlyLocal; case BindValueKind.IncrementDecrement: return ErrorCode.ERR_IncrementLvalueExpected; - case BindValueKind.RefReturn: - return ErrorCode.ERR_RefReturnStructThis; } } @@ -1578,9 +1576,15 @@ private bool CheckIsVariable(SyntaxNode node, BoundExpression expr, BindValueKin var thisref = expr as BoundThisReference; if (thisref != null) { + if (kind == BindValueKind.RefReturn) + { + Error(diagnostics, thisref.Type.IsValueType ? ErrorCode.ERR_RefReturnStructThis : ErrorCode.ERR_RefReadonlyLocal, node, ThisParameterSymbol.SymbolName); + return false; + } + // We will already have given an error for "this" used outside of a constructor, // instance method, or instance accessor. Assume that "this" is a variable if it is in a struct. - if (!thisref.Type.IsValueType || kind == BindValueKind.RefReturn) + if (!thisref.Type.IsValueType) { // CONSIDER: the Dev10 name has angle brackets (i.e. "") Error(diagnostics, GetThisLvalueError(kind), node, ThisParameterSymbol.SymbolName); @@ -1744,7 +1748,7 @@ private BoundExpression InferTypeForDiscardAssignment(BoundDiscardExpression op1 } private BoundAssignmentOperator BindAssignment(SyntaxNode node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics) - { + { Debug.Assert(op1 != null); Debug.Assert(op2 != null); @@ -1773,7 +1777,7 @@ private BoundAssignmentOperator BindAssignment(SyntaxNode node, BoundExpression { // Event assignment is a call to void WindowsRuntimeMarshal.AddEventHandler(). type = this.GetSpecialType(SpecialType.System_Void, diagnostics, node); - } + } else { type = op1.Type; @@ -1933,6 +1937,25 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind GetNonMethodMemberType(otherSymbol)); } } + else if (expr.Kind == BoundKind.IndexerAccess) + { + // Assigning to an non ref return indexer needs to set 'useSetterForDefaultArgumentGeneration' to true. + // This is for IOperation purpose. + var indexerAccess = (BoundIndexerAccess)expr; + if (valueKind == BindValueKind.Assignment && !indexerAccess.Indexer.ReturnsByRef) + { + expr = indexerAccess.Update(indexerAccess.ReceiverOpt, + indexerAccess.Indexer, + indexerAccess.Arguments, + indexerAccess.ArgumentNamesOpt, + indexerAccess.ArgumentRefKindsOpt, + indexerAccess.Expanded, + indexerAccess.ArgsToParamsOpt, + indexerAccess.BinderOpt, + useSetterForDefaultArgumentGeneration: true, + type: indexerAccess.Type); + } + } if (!hasResolutionErrors && CheckValueKind(expr, valueKind, diagnostics) || expr.HasAnyErrors && valueKind == BindValueKind.RValueOrMethodGroup) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index d02e32d22e28e..2cc61856e9e13 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -2060,12 +2060,12 @@ private AssemblySymbol GetForwardedToAssembly(string fullName, int arity, Diagno return null; } - internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null) + internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null) { var options = (CSharpParseOptions)syntax.SyntaxTree.Options; if (options.IsFeatureEnabled(feature)) { - return; + return true; } var location = locationOpt ?? syntax.GetLocation(); @@ -2073,7 +2073,7 @@ internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID featu if (requiredFeature != null) { diagnostics.Add(ErrorCode.ERR_FeatureIsExperimental, location, feature.Localize(), requiredFeature); - return; + return false; } LanguageVersion availableVersion = options.LanguageVersion; @@ -2081,7 +2081,10 @@ internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID featu if (requiredVersion > availableVersion) { diagnostics.Add(availableVersion.GetErrorCode(), location, feature.Localize(), new CSharpRequiredLanguageVersion(requiredVersion)); + return false; } + + return true; } } } \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Binder/PatternSwitchBinder.cs b/src/Compilers/CSharp/Portable/Binder/PatternSwitchBinder.cs index aa86e537ce024..b5551b2f52be0 100644 --- a/src/Compilers/CSharp/Portable/Binder/PatternSwitchBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/PatternSwitchBinder.cs @@ -64,7 +64,7 @@ internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatement BoundPatternSwitchLabel defaultLabel; bool isComplete; ImmutableArray switchSections = - BindPatternSwitchSections(boundSwitchExpression, node.Sections, originalBinder, out defaultLabel, out isComplete, diagnostics); + BindPatternSwitchSections(originalBinder, out defaultLabel, out isComplete, diagnostics); var locals = GetDeclaredLocalsForScope(node); var functions = GetDeclaredLocalFunctionsForScope(node); return new BoundPatternSwitchStatement( @@ -101,8 +101,6 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel /// the decision tree. /// private ImmutableArray BindPatternSwitchSections( - BoundExpression boundSwitchExpression, - SyntaxList sections, Binder originalBinder, out BoundPatternSwitchLabel defaultLabel, out bool isComplete, @@ -115,11 +113,11 @@ private ImmutableArray BindPatternSwitchSections( // Bind match sections var boundPatternSwitchSectionsBuilder = ArrayBuilder.GetInstance(); - SubsumptionDiagnosticBuilder subsumption = new SubsumptionDiagnosticBuilder(ContainingMemberOrLambda, this.Conversions, boundSwitchExpression); - foreach (var sectionSyntax in sections) + SubsumptionDiagnosticBuilder subsumption = new SubsumptionDiagnosticBuilder(ContainingMemberOrLambda, SwitchSyntax, this.Conversions, SwitchGoverningExpression); + foreach (var sectionSyntax in SwitchSyntax.Sections) { boundPatternSwitchSectionsBuilder.Add(BindPatternSwitchSection( - boundSwitchExpression, sectionSyntax, originalBinder, ref defaultLabel, ref someValueMatched, subsumption, diagnostics)); + SwitchGoverningExpression, sectionSyntax, originalBinder, ref defaultLabel, ref someValueMatched, subsumption, diagnostics)); } isComplete = defaultLabel != null || subsumption.IsComplete || someValueMatched; diff --git a/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs b/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs index f1695a0bda40c..4496196c67e24 100644 --- a/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs @@ -20,11 +20,12 @@ internal class SubsumptionDiagnosticBuilder : DecisionTreeBuilder private readonly DecisionTree _subsumptionTree; internal SubsumptionDiagnosticBuilder(Symbol enclosingSymbol, - Conversions conversions, - BoundExpression expression) - : base(enclosingSymbol, conversions) + SyntaxNode syntax, + Conversions conversions, + BoundExpression expression) + : base(enclosingSymbol, syntax, conversions) { - _subsumptionTree = DecisionTree.Create(expression, expression.Type, enclosingSymbol); + _subsumptionTree = CreateEmptyDecisionTree(expression); } /// diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs index f02545f32360b..0101123611edc 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs @@ -424,6 +424,7 @@ internal BoundObjectCreationExpression UpdateArgumentsAndInitializer( argsToParamsOpt: default(ImmutableArray), constantValueOpt: ConstantValueOpt, initializerExpressionOpt: newInitializerExpression, + binderOpt: BinderOpt, type: changeTypeOpt ?? Type); } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index e4962d7696bee..fca8f89687e1d 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1201,6 +1201,9 @@ + + + @@ -1246,6 +1249,9 @@ + + + @@ -1491,6 +1497,10 @@ + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundObjectCreationExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundObjectCreationExpression.cs index 98b6b588d0232..082c447e850f8 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundObjectCreationExpression.cs @@ -7,13 +7,13 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class BoundObjectCreationExpression { - public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, TypeSymbol type, bool hasErrors = false) - : this(syntax, constructor, ImmutableArray.Empty, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, type, hasErrors) + public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, Binder binderOpt, TypeSymbol type, bool hasErrors = false) + : this(syntax, constructor, ImmutableArray.Empty, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, binderOpt, type, hasErrors) { } - public BoundObjectCreationExpression Update(MethodSymbol constructor, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, TypeSymbol type) + public BoundObjectCreationExpression Update(MethodSymbol constructor, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, Binder binderOpt, TypeSymbol type) { - return this.Update(constructor, ImmutableArray.Empty, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, type); + return this.Update(constructor, ImmutableArray.Empty, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, binderOpt, type); } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index 6f7d940def080..a200b5636c2cd 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -75,7 +75,8 @@ public static BoundCall ErrorCall( bool isDelegateCall, bool invokedAsExtensionMethod, ImmutableArray originalMethods, - LookupResultKind resultKind) + LookupResultKind resultKind, + Binder binder) { if (!originalMethods.IsEmpty) resultKind = resultKind.WorseResultKind(LookupResultKind.OverloadResolutionFailure); @@ -84,14 +85,14 @@ public static BoundCall ErrorCall( var call = new BoundCall(node, receiverOpt, method, arguments, namedArguments, refKinds, isDelegateCall: isDelegateCall, expanded: false, invokedAsExtensionMethod: invokedAsExtensionMethod, argsToParamsOpt: default(ImmutableArray), - resultKind: resultKind, type: method.ReturnType, hasErrors: true); + resultKind: resultKind, binderOpt: binder, type: method.ReturnType, hasErrors: true); call.OriginalMethodsOpt = originalMethods; return call; } public BoundCall Update(BoundExpression receiverOpt, MethodSymbol method, ImmutableArray arguments) { - return this.Update(receiverOpt, method, arguments, ArgumentNamesOpt, ArgumentRefKindsOpt, IsDelegateCall, Expanded, InvokedAsExtensionMethod, ArgsToParamsOpt, ResultKind, Type); + return this.Update(receiverOpt, method, arguments, ArgumentNamesOpt, ArgumentRefKindsOpt, IsDelegateCall, Expanded, InvokedAsExtensionMethod, ArgsToParamsOpt, ResultKind, BinderOpt, Type); } public static BoundCall Synthesized(SyntaxNode syntax, BoundExpression receiverOpt, MethodSymbol method) @@ -122,6 +123,7 @@ public static BoundCall Synthesized(SyntaxNode syntax, BoundExpression receiverO invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray), resultKind: LookupResultKind.Viable, + binderOpt: null, type: method.ReturnType, hasErrors: method.OriginalDefinition is ErrorMethodSymbol ) @@ -131,12 +133,12 @@ public static BoundCall Synthesized(SyntaxNode syntax, BoundExpression receiverO internal sealed partial class BoundObjectCreationExpression { - public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, params BoundExpression[] arguments) - : this(syntax, constructor, ImmutableArray.Create(arguments), default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), null, null, constructor.ContainingType) + public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, Binder binderOpt, params BoundExpression[] arguments) + : this(syntax, constructor, ImmutableArray.Create(arguments), default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), null, null, binderOpt, constructor.ContainingType) { } - public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, ImmutableArray arguments) - : this(syntax, constructor, arguments, default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), null, null, constructor.ContainingType) + public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, Binder binderOpt, ImmutableArray arguments) + : this(syntax, constructor, arguments, default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), null, null, binderOpt, constructor.ContainingType) { } } @@ -161,6 +163,8 @@ public static BoundIndexerAccess ErrorAccess( refKinds, expanded: false, argsToParamsOpt: default(ImmutableArray), + binderOpt: null, + useSetterForDefaultArgumentGeneration: false, type: indexer.Type, hasErrors: true) { diff --git a/src/Compilers/CSharp/Portable/BoundTree/DecisionTree.cs b/src/Compilers/CSharp/Portable/BoundTree/DecisionTree.cs index e030bc16cb558..c6d95ca94f611 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/DecisionTree.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/DecisionTree.cs @@ -78,36 +78,6 @@ public DecisionTree(BoundExpression expression, TypeSymbol type, LocalSymbol tem Debug.Assert(this.Type != null); } - /// - /// Create a fresh decision tree for the given input expression of the given type. - /// - public static DecisionTree Create(BoundExpression expression, TypeSymbol type, Symbol enclosingSymbol) - { - Debug.Assert(expression.Type == type); - LocalSymbol temp = null; - if (expression.ConstantValue == null) - { - // Unless it is a constant, the decision tree acts on a copy of the input expression. - // We create a temp to represent that copy. Lowering will assign into this temp. - temp = new SynthesizedLocal(enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, expression.Syntax, false, RefKind.None); - expression = new BoundLocal(expression.Syntax, temp, null, type); - } - - if (type.CanContainNull() || type.SpecialType == SpecialType.None) - { - // We need the ByType decision tree to separate null from non-null values. - // Note that, for the purpose of the decision tree (and subsumption), we - // ignore the fact that the input may be a constant, and therefore always - // or never null. - return new ByType(expression, type, temp); - } - else - { - // If it is a (e.g. builtin) value type, we can switch on its (constant) values. - return new ByValue(expression, type, temp); - } - } - /// /// A decision tree node that branches based on (1) whether the input value is null, (2) the runtime /// type of the input expression, and finally (3) a default decision tree if nothing in the previous diff --git a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs index 6e72bb9566008..703dffcf48a03 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs @@ -26,17 +26,63 @@ internal abstract class DecisionTreeBuilder protected readonly Symbol _enclosingSymbol; protected readonly Conversions _conversions; protected HashSet _useSiteDiagnostics = new HashSet(); + private Dictionary localByType = new Dictionary(); protected DecisionTreeBuilder( Symbol enclosingSymbol, + SyntaxNode syntax, Conversions conversions) { this._enclosingSymbol = enclosingSymbol; + this.Syntax = syntax; this._conversions = conversions; } protected SyntaxNode Syntax { private get; set; } + private LocalSymbol PatternMatchingTemp(TypeSymbol type) + { + LocalSymbol temp; + if (!localByType.TryGetValue(type, out temp)) + { + temp = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax); + localByType.Add(type, temp); + } + + return temp; + } + + /// + /// Create a fresh decision tree for the given input expression of the given type. + /// + protected DecisionTree CreateEmptyDecisionTree(BoundExpression expression) + { + var type = expression.Type; + + LocalSymbol temp = null; + if (expression.ConstantValue == null) + { + // Unless it is a constant, the decision tree acts on a copy of the input expression. + // We create a temp to represent that copy. Lowering will assign into this temp. + temp = PatternMatchingTemp(type); + expression = new BoundLocal(expression.Syntax, temp, null, type); + } + + if (type.CanContainNull() || type.SpecialType == SpecialType.None) + { + // We need the ByType decision tree to separate null from non-null values. + // Note that, for the purpose of the decision tree (and subsumption), we + // ignore the fact that the input may be a constant, and therefore always + // or never null. + return new DecisionTree.ByType(expression, type, temp); + } + else + { + // If it is a (e.g. builtin) value type, we can switch on its (constant) values. + return new DecisionTree.ByValue(expression, type, temp); + } + } + protected DecisionTree AddToDecisionTree(DecisionTree decisionTree, SyntaxNode sectionSyntax, BoundPatternSwitchLabel label) { var pattern = label.Pattern; @@ -237,7 +283,7 @@ private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern if (forType == null) { var type = value.Value.Type; - var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax, false, RefKind.None); + var localSymbol = PatternMatchingTemp(type); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair(value.Value.Type, forType)); @@ -338,7 +384,7 @@ private DecisionTree AddByType(DecisionTree.ByType byType, TypeSymbol type, Deci if (result == null) { - var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax, false, RefKind.None); + var localSymbol = PatternMatchingTemp(type); var expression = new BoundLocal(Syntax, localSymbol, null, type); result = makeDecision(expression, type); Debug.Assert(result.Temp == null); diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 605ada05830fa..7283f4640b5ab 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -59,7 +60,7 @@ internal partial class BoundCall : IInvocationExpression !this.ReceiverOpt.SuppressVirtualCalls; ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder - => DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Syntax); + => DeriveArguments(this, this.BinderOpt, this.Method, this.Method, this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Method.Parameters, this.Expanded, this.Syntax, this.InvokedAsExtensionMethod); protected override OperationKind ExpressionKind => OperationKind.InvocationExpression; @@ -73,219 +74,69 @@ public override TResult Accept(OperationVisitor DeriveArguments(ImmutableArray boundArguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentsToParametersOpt, ImmutableArray argumentRefKindsOpt, ImmutableArray parameters, SyntaxNode invocationSyntax) - { - ArrayBuilder arguments = ArrayBuilder.GetInstance(boundArguments.Length); - for (int parameterIndex = 0; parameterIndex < parameters.Length; parameterIndex++) - { - int argumentIndex = -1; - if (argumentsToParametersOpt.IsDefault) - { - argumentIndex = parameterIndex; - } - else - { - argumentIndex = argumentsToParametersOpt.IndexOf(parameterIndex); - } - - if ((uint)argumentIndex >= (uint)boundArguments.Length) - { - // No argument has been supplied for the parameter at `parameterIndex`: - // 1. `argumentIndex == -1' when the arguments are specified out of parameter order, and no argument is provided for parameter corresponding to `parameters[parameterIndex]`. - // 2. `argumentIndex >= boundArguments.Length` when the arguments are specified in parameter order, and no argument is provided at `parameterIndex`. - - Symbols.ParameterSymbol parameter = parameters[parameterIndex]; - if (parameter.HasExplicitDefaultValue) - { - // The parameter is optional with a default value. - arguments.Add(new Argument(ArgumentKind.DefaultValue, parameter, new LiteralExpression(parameter.ExplicitDefaultConstantValue, parameter.Type, invocationSyntax))); - } - else - { - // If the invocation is semantically valid, the parameter will be a params array and an empty array will be provided. - // If the argument is otherwise omitted for a parameter with no default value, the invocation is not valid and a null argument will be provided. - arguments.Add(DeriveArgument(parameterIndex, boundArguments.Length, boundArguments, argumentNamesOpt, argumentRefKindsOpt, parameters, invocationSyntax)); - } - } - else - { - arguments.Add(DeriveArgument(parameterIndex, argumentIndex, boundArguments, argumentNamesOpt, argumentRefKindsOpt, parameters, invocationSyntax)); - } - } - - return arguments.ToImmutableAndFree(); - } - - private static readonly ConditionalWeakTable s_argumentMappings = new ConditionalWeakTable(); - - private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, ImmutableArray boundArguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, ImmutableArray parameters, SyntaxNode invocationSyntax) - { - if ((uint)argumentIndex >= (uint)boundArguments.Length) + private static readonly ConditionalWeakTable> s_callToArgumentsMappings + = new ConditionalWeakTable>(); + + internal static IArgument CreateArgumentOperation(ArgumentKind kind, IParameterSymbol parameter, IOperation value) + { + return new Argument(kind, + parameter, + value, + inConversion: null, + outConversion: null, + isInvalid: parameter == null || value.IsInvalid, + syntax: value.Syntax, + type: value.Type, + constantValue: default(Optional)); + } + + internal static ImmutableArray DeriveArguments( + BoundExpression boundNode, + Binder binder, + Symbol methodOrIndexer, + MethodSymbol optionalParametersMethod, + ImmutableArray boundArguments, + ImmutableArray argumentNamesOpt, + ImmutableArray argumentsToParametersOpt, + ImmutableArray argumentRefKindsOpt, + ImmutableArray parameters, + bool expanded, + SyntaxNode invocationSyntax, + bool invokedAsExtensionMethod = false) + { + // We can simply return empty array only if both parameters and boundArguments are empty, because: + // - if only parameters is empty, there's error in code but we still need to return provided expression. + // - if boundArguments is empty, then either there's error or we need to provide values for optional/param-array parameters. + if (parameters.IsDefaultOrEmpty && boundArguments.IsDefaultOrEmpty) { - // Check for an omitted argument that becomes an empty params array. - if (parameters.Length > 0) - { - Symbols.ParameterSymbol lastParameter = parameters[parameters.Length - 1]; - if (lastParameter.IsParams) - { - return new Argument(ArgumentKind.ParamArray, lastParameter, CreateParamArray(lastParameter, boundArguments, argumentIndex, invocationSyntax)); - } - } - - // There is no supplied argument and there is no params parameter. Any action is suspect at this point. - return new SimpleArgument(null, new InvalidExpression(invocationSyntax, ImmutableArray.Empty)); + return ImmutableArray.Empty; } - return s_argumentMappings.GetValue( - boundArguments[argumentIndex], - (argument) => - { - string nameOpt = !argumentNamesOpt.IsDefaultOrEmpty ? argumentNamesOpt[argumentIndex] : null; - Symbols.ParameterSymbol parameterOpt = (uint)parameterIndex < (uint)parameters.Length ? parameters[parameterIndex] : null; - - if ((object)nameOpt == null) + return (ImmutableArray) s_callToArgumentsMappings.GetValue( + boundNode, + (n) => + { + //TODO: https://github.com/dotnet/roslyn/issues/18722 + // Right now, for erroneous code, we exposes all expression in place of arguments as IArgument with Parameter set to null, + // so user needs to check IsInvalid first before using anything we returned. Need to implement a new interface for invalid invocation instead. + if (n.HasErrors || (object)optionalParametersMethod == null) { - RefKind refMode = argumentRefKindsOpt.IsDefaultOrEmpty ? RefKind.None : argumentRefKindsOpt[argumentIndex]; - - if (refMode != RefKind.None) - { - return new Argument(ArgumentKind.Explicit, parameterOpt, argument); - } - - if (argumentIndex >= parameters.Length - 1 && - parameters.Length > 0 && - parameters[parameters.Length - 1].IsParams && - // An argument that is an array of the appropriate type is not a params argument. - (boundArguments.Length > argumentIndex + 1 || - ((object)argument.Type != null && // If argument type is null, we are in an error scenario and cannot tell if it is a param array, or not. - (argument.Type.TypeKind != TypeKind.Array || - !argument.Type.Equals(parameters[parameters.Length - 1].Type, TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds))))) - { - return new Argument(ArgumentKind.ParamArray, parameters[parameters.Length - 1], CreateParamArray(parameters[parameters.Length - 1], boundArguments, argumentIndex, invocationSyntax)); - } - else - { - return new SimpleArgument(parameterOpt, argument); - } - } - - return new Argument(ArgumentKind.Explicit, parameterOpt, argument); + // optionalParametersMethod can be null if we are writing to a readonly indexer or reading from an writeonly indexer, + // in which case HasErrors property would be true, but we still want to treat this as invalid invocation. + return boundArguments.SelectAsArray(arg => CreateArgumentOperation(ArgumentKind.Explicit, null, arg)); + } + + return LocalRewriter.MakeArgumentsInEvaluationOrder( + binder: binder, + syntax: invocationSyntax, + arguments: boundArguments, + methodOrIndexer: methodOrIndexer, + optionalParametersMethod: optionalParametersMethod, + expanded: expanded, + argsToParamsOpt: argumentsToParametersOpt, + invokedAsExtensionMethod: invokedAsExtensionMethod); }); } - - private static IOperation CreateParamArray(IParameterSymbol parameter, ImmutableArray boundArguments, int firstArgumentElementIndex, SyntaxNode invocationSyntax) - { - if (parameter.Type.TypeKind == TypeKind.Array) - { - IArrayTypeSymbol arrayType = (IArrayTypeSymbol)parameter.Type; - ArrayBuilder builder = ArrayBuilder.GetInstance(boundArguments.Length - firstArgumentElementIndex); - for (int index = firstArgumentElementIndex; index < boundArguments.Length; index++) - { - builder.Add(boundArguments[index]); - } - - var paramArrayArguments = builder.ToImmutableAndFree(); - - - // Use the invocation syntax node if there is no actual syntax available for the argument (because the paramarray is empty.) - return new ArrayCreationExpression(arrayType, paramArrayArguments, paramArrayArguments.Length > 0 ? paramArrayArguments[0].Syntax : invocationSyntax); - } - - return new InvalidExpression(invocationSyntax, ImmutableArray.Empty); - } - - internal static IArgument ArgumentMatchingParameter(ImmutableArray arguments, ImmutableArray argumentsToParametersOpt, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, ISymbol targetMethod, ImmutableArray parameters, IParameterSymbol parameter, SyntaxNode invocationSyntax) - { - int argumentIndex = ArgumentIndexMatchingParameter(argumentsToParametersOpt, targetMethod, parameter); - if (argumentIndex >= 0) - { - return DeriveArgument(parameter.Ordinal, argumentIndex, arguments, argumentNamesOpt, argumentRefKindsOpt, parameters, invocationSyntax); - } - - return null; - } - - private static int ArgumentIndexMatchingParameter(ImmutableArray argumentsToParametersOpt, ISymbol targetMethod, IParameterSymbol parameter) - { - if (parameter.ContainingSymbol == targetMethod) - { - int parameterIndex = parameter.Ordinal; - if (!argumentsToParametersOpt.IsDefaultOrEmpty) - { - return argumentsToParametersOpt.IndexOf(parameterIndex); - } - - return parameterIndex; - } - - return -1; - } - - private abstract class ArgumentBase : IArgument - { - public ArgumentBase(IParameterSymbol parameter, IOperation value) - { - Debug.Assert(value != null); - - this.Value = value; - this.Parameter = parameter; - } - - public IParameterSymbol Parameter { get; } - - public IOperation Value { get; } - - IOperation IArgument.InConversion => null; - - IOperation IArgument.OutConversion => null; - - bool IOperation.IsInvalid => (object)this.Parameter == null || this.Value.IsInvalid; - - OperationKind IOperation.Kind => OperationKind.Argument; - - SyntaxNode IOperation.Syntax => this.Value?.Syntax; - - public ITypeSymbol Type => null; - - public Optional ConstantValue => default(Optional); - - public abstract ArgumentKind ArgumentKind { get; } - - void IOperation.Accept(OperationVisitor visitor) - { - visitor.VisitArgument(this); - } - - TResult IOperation.Accept(OperationVisitor visitor, TArgument argument) - { - return visitor.VisitArgument(this, argument); - } - } - - private sealed class SimpleArgument : ArgumentBase - { - public SimpleArgument(IParameterSymbol parameter, IOperation value) - : base(parameter, value) - { } - - public override ArgumentKind ArgumentKind => ArgumentKind.Explicit; - } - - private sealed class Argument : ArgumentBase - { - public Argument(ArgumentKind kind, IParameterSymbol parameter, IOperation value) - : base(parameter, value) - { - this.ArgumentKind = kind; - } - - public override ArgumentKind ArgumentKind { get; } - } } internal partial class BoundLocal : ILocalReferenceExpression @@ -355,7 +206,32 @@ internal partial class BoundIndexerAccess : IIndexedPropertyReferenceExpression ISymbol IMemberReferenceExpression.Member => this.Indexer; - ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder => BoundCall.DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Indexer.Parameters, this.Syntax); + ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder + { + get + { + MethodSymbol accessor = this.UseSetterForDefaultArgumentGeneration + ? this.Indexer.GetOwnOrInheritedSetMethod() + : this.Indexer.GetOwnOrInheritedGetMethod(); + + return BoundCall.DeriveArguments(this, + this.BinderOpt, + this.Indexer, + accessor, + this.Arguments, + this.ArgumentNamesOpt, + this.ArgsToParamsOpt, + this.ArgumentRefKindsOpt, + this.Indexer.Parameters, + this.Expanded, + this.Syntax); + } + } + + bool IOperation.IsInvalid + => this.HasErrors + || (this.Indexer.IsReadOnly && this.UseSetterForDefaultArgumentGeneration) + || (this.Indexer.IsWriteOnly && !this.UseSetterForDefaultArgumentGeneration); protected override OperationKind ExpressionKind => OperationKind.IndexedPropertyReferenceExpression; @@ -414,44 +290,20 @@ public override TResult Accept(OperationVisitor - (object)this.MethodOpt != null && - (this.MethodOpt.IsVirtual || this.MethodOpt.IsAbstract || this.MethodOpt.IsOverride) && - !this.SuppressVirtualCalls; - - ISymbol IMemberReferenceExpression.Member => this.MethodOpt; - - IMethodSymbol IMethodBindingExpression.Method => this.MethodOpt; - - protected override OperationKind ExpressionKind => OperationKind.MethodBindingExpression; + protected override OperationKind ExpressionKind => OperationKind.None; - // SyntaxNode for MethodBindingExpression is the argument of DelegateCreationExpression - SyntaxNode IOperation.Syntax => this.Argument.Syntax; + protected override ImmutableArray Children => ImmutableArray.Create(this.Argument); public override void Accept(OperationVisitor visitor) { - visitor.VisitMethodBindingExpression(this); + visitor.VisitNoneOperation(this); } public override TResult Accept(OperationVisitor visitor, TArgument argument) { - return visitor.VisitMethodBindingExpression(this, argument); + return visitor.VisitNoneOperation(this, argument); } } @@ -513,45 +365,20 @@ internal partial class BoundObjectCreationExpression : IObjectCreationExpression IMethodSymbol IObjectCreationExpression.Constructor => this.Constructor; - ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder => BoundCall.DeriveArguments(this.Arguments, this.ArgumentNamesOpt, this.ArgsToParamsOpt, this.ArgumentRefKindsOpt, this.Constructor.Parameters, this.Syntax); - - ImmutableArray IObjectCreationExpression.MemberInitializers - { - get - { - return (ImmutableArray)s_memberInitializersMappings.GetValue(this, - objectCreationExpression => - { - var objectInitializerExpression = this.InitializerExpressionOpt as BoundObjectInitializerExpression; - if (objectInitializerExpression != null) - { - var builder = ArrayBuilder.GetInstance(objectInitializerExpression.Initializers.Length); - foreach (var memberAssignment in objectInitializerExpression.Initializers) - { - var assignment = memberAssignment as BoundAssignmentOperator; - var leftSymbol = (assignment?.Left as BoundObjectInitializerMember)?.MemberSymbol; - - if ((object)leftSymbol == null) - { - continue; - } - - switch (leftSymbol.Kind) - { - case SymbolKind.Field: - builder.Add(new FieldInitializer(assignment.Syntax, (IFieldSymbol)leftSymbol, assignment.Right)); - break; - case SymbolKind.Property: - builder.Add(new PropertyInitializer(assignment.Syntax, (IPropertySymbol)leftSymbol, assignment.Right)); - break; - } - } - return builder.ToImmutableAndFree(); - } - return ImmutableArray.Empty; - }); - } - } + ImmutableArray IHasArgumentsExpression.ArgumentsInEvaluationOrder + => BoundCall.DeriveArguments(this, + this.BinderOpt, + this.Constructor, + this.Constructor, + this.Arguments, + this.ArgumentNamesOpt, + this.ArgsToParamsOpt, + this.ArgumentRefKindsOpt, + this.Constructor.Parameters, + this.Expanded, + this.Syntax); + + ImmutableArray IObjectCreationExpression.Initializers => GetChildInitializers(this.InitializerExpressionOpt).As(); internal static ImmutableArray GetChildInitializers(BoundExpression objectOrCollectionInitializer) { diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index aa3cbc32d2535..a11f85b18aa76 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -272,6 +272,7 @@ + diff --git a/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs b/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs index 7cfe19323188a..1e3dc0c3c4284 100644 --- a/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs +++ b/src/Compilers/CSharp/Portable/CSharpCompilationOptions.cs @@ -422,7 +422,7 @@ internal CSharpCompilationOptions WithDebugPlusMode(bool debugPlusMode) return new CSharpCompilationOptions(this) { DebugPlusMode_internal_protected_set = debugPlusMode }; } - internal CSharpCompilationOptions WithMetadataImportOptions(MetadataImportOptions value) + internal new CSharpCompilationOptions WithMetadataImportOptions(MetadataImportOptions value) { if (value == this.MetadataImportOptions) { @@ -520,6 +520,9 @@ protected override CompilationOptions CommonWithMetadataReferenceResolver(Metada protected override CompilationOptions CommonWithStrongNameProvider(StrongNameProvider provider) => WithStrongNameProvider(provider); + internal override CompilationOptions CommonWithMetadataImportOptions(MetadataImportOptions value) => + WithMetadataImportOptions(value); + [Obsolete] protected override CompilationOptions CommonWithFeatures(ImmutableArray features) { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 62448e9d142e4..4cb925f51e3ab 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -6838,6 +6838,15 @@ internal static string ERR_NoNamespacePrivate { } } + /// + /// Looks up a localized string similar to Cannot compile net modules when using /refout or /refonly.. + /// + internal static string ERR_NoNetModuleOutputWhenRefOutOrRefOnly { + get { + return ResourceManager.GetString("ERR_NoNetModuleOutputWhenRefOutOrRefOnly", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot create an instance of the abstract class or interface '{0}'. /// @@ -6873,7 +6882,18 @@ internal static string ERR_NonInvocableMemberCalled { return ResourceManager.GetString("ERR_NonInvocableMemberCalled", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Async Main methods must return Task or Task<int>. + /// + internal static string ERR_NonTaskMainCantBeAsync + { + get + { + return ResourceManager.GetString("ERR_NonTaskMainCantBeAsync", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot embed interop types from assembly '{0}' because it is missing the '{1}' attribute.. /// @@ -6901,6 +6921,15 @@ internal static string ERR_NoPIANestedType { } } + /// + /// Looks up a localized string similar to Do not use refout when using refonly.. + /// + internal static string ERR_NoRefOutWhenRefOnly { + get { + return ResourceManager.GetString("ERR_NoRefOutWhenRefOnly", resourceCulture); + } + } + /// /// Looks up a localized string similar to '{0}': cannot override because '{1}' does not have an overridable set accessor. /// @@ -8675,11 +8704,11 @@ internal static string ERR_SourceFileReferencesNotSupported { } /// - /// Looks up a localized string similar to /sourcelink switch is only supported when emitting Portable PDB (/debug:portable or /debug:embedded must be specified).. + /// Looks up a localized string similar to /sourcelink switch is only supported when emitting PDB.. /// - internal static string ERR_SourceLinkRequiresPortablePdb { + internal static string ERR_SourceLinkRequiresPdb { get { - return ResourceManager.GetString("ERR_SourceLinkRequiresPortablePdb", resourceCulture); + return ResourceManager.GetString("ERR_SourceLinkRequiresPdb", resourceCulture); } } @@ -9870,22 +9899,24 @@ internal static string IDS_Covariantly { return ResourceManager.GetString("IDS_Covariantly", resourceCulture); } } - + /// /// Looks up a localized string similar to /// Visual C# Compiler Options /// /// - OUTPUT FILES - - /// /out:<file> Specify output file name (default: base name of + /// /out:<file> Specify output file name (default: base name of /// file with main class or first file) - /// /target:exe Build a console executable (default) (Short + /// /target:exe Build a console executable (default) (Short /// form: /t:exe) - /// /target:winexe Build a Windows executable (Short form: + /// /target:winexe Build a Windows executable (Short form: /// /t:winexe) - /// /target:library [rest of string was truncated]";. + /// /target:library [rest of string was truncated]";. /// - internal static string IDS_CSCHelp { - get { + internal static string IDS_CSCHelp + { + get + { return ResourceManager.GetString("IDS_CSCHelp", resourceCulture); } } @@ -9944,6 +9975,15 @@ internal static string IDS_FeatureAsync { } } + /// + /// Looks up a localized string similar to async main. + /// + internal static string IDS_FeatureAsyncMain { + get { + return ResourceManager.GetString("IDS_FeatureAsyncMain", resourceCulture); + } + } + /// /// Looks up a localized string similar to automatically implemented properties. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 011fc30ae60ba..2d6e8c4266ed0 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -1,17 +1,17 @@  - @@ -2285,6 +2285,12 @@ If such a class is used as a base class and if the deriving class defines a dest Merge conflict marker encountered + + Do not use refout when using refonly. + + + Cannot compile net modules when using /refout or /refonly. + Overloadable operator expected @@ -3587,9 +3593,6 @@ Give the compiler some way to differentiate the methods. For example, you can gi Async methods are not allowed in an Interface, Class, or Structure which has the 'SecurityCritical' or 'SecuritySafeCritical' attribute. - - '{0}': an entry point cannot be marked with the 'async' modifier - The 'await' operator may only be used in a query expression within the first collection expression of the initial 'from' clause or within the collection expression of a 'join' clause @@ -4379,34 +4382,35 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Visual C# Compiler Options - OUTPUT FILES - - /out:<file> Specify output file name (default: base name of + /out:<file> Specify output file name (default: base name of file with main class or first file) - /target:exe Build a console executable (default) (Short + /target:exe Build a console executable (default) (Short form: /t:exe) - /target:winexe Build a Windows executable (Short form: + /target:winexe Build a Windows executable (Short form: /t:winexe) /target:library Build a library (Short form: /t:library) - /target:module Build a module that can be added to another + /target:module Build a module that can be added to another assembly (Short form: /t:module) - /target:appcontainerexe Build an Appcontainer executable (Short form: + /target:appcontainerexe Build an Appcontainer executable (Short form: /t:appcontainerexe) - /target:winmdobj Build a Windows Runtime intermediate file that + /target:winmdobj Build a Windows Runtime intermediate file that is consumed by WinMDExp (Short form: /t:winmdobj) /doc:<file> XML Documentation file to generate + /refout:<file> Reference assembly output to generate /platform:<string> Limit which platforms this code can run on: x86, - Itanium, x64, arm, anycpu32bitpreferred, or + Itanium, x64, arm, anycpu32bitpreferred, or anycpu. The default is anycpu. - INPUT FILES - - /recurse:<wildcard> Include all files in the current directory and - subdirectories according to the wildcard + /recurse:<wildcard> Include all files in the current directory and + subdirectories according to the wildcard specifications - /reference:<alias>=<file> Reference metadata from the specified assembly + /reference:<alias>=<file> Reference metadata from the specified assembly file using the given alias (Short form: /r) - /reference:<file list> Reference metadata from the specified assembly + /reference:<file list> Reference metadata from the specified assembly files (Short form: /r) /addmodule:<file list> Link the specified modules into this assembly - /link:<file list> Embed metadata from the specified interop + /link:<file list> Embed metadata from the specified interop assembly files (Short form: /l) /analyzer:<file list> Run the analyzers from this assembly (Short form: /a) @@ -4422,23 +4426,24 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ /win32manifest:<file> Specify a Win32 manifest file (.xml) /nowin32manifest Do not include the default Win32 manifest /resource:<resinfo> Embed the specified resource (Short form: /res) - /linkresource:<resinfo> Link the specified resource to this assembly - (Short form: /linkres) Where the resinfo format + /linkresource:<resinfo> Link the specified resource to this assembly + (Short form: /linkres) Where the resinfo format is <file>[,<string name>[,public|private]] - CODE GENERATION - /debug[+|-] Emit debugging information /debug:{full|pdbonly|portable|embedded} - Specify debugging type ('full' is default, + Specify debugging type ('full' is default, 'portable' is a cross-platform format, - 'embedded' is a cross-platform format embedded into + 'embedded' is a cross-platform format embedded into the target .dll or .exe) /optimize[+|-] Enable optimizations (Short form: /o) /deterministic Produce a deterministic assembly (including module version GUID and timestamp) + /refonly Produce a reference assembly in place of the main output /instrument:TestCoverage Produce an assembly instrumented to collect coverage information - /sourcelink:<file> Source link info to embed into Portable PDB. + /sourcelink:<file> Source link info to embed into PDB. - ERRORS AND WARNINGS - /warnaserror[+|-] Report all warnings as errors @@ -4451,17 +4456,17 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ diagnostics. /reportanalyzer Report additional analyzer information, such as execution time. - + - LANGUAGE - /checked[+|-] Generate overflow checks /unsafe[+|-] Allow 'unsafe' code - /define:<symbol list> Define conditional compilation symbol(s) (Short + /define:<symbol list> Define conditional compilation symbol(s) (Short form: /d) - /langversion:<string> Specify language version mode: ISO-1, ISO-2, 3, + /langversion:<string> Specify language version mode: ISO-1, ISO-2, 3, 4, 5, 6, 7, 7.1, Default, or Latest - SECURITY - - /delaysign[+|-] Delay-sign the assembly using only the public + /delaysign[+|-] Delay-sign the assembly using only the public portion of the strong name key /publicsign[+|-] Public-sign the assembly using only the public portion of the strong name key @@ -4479,36 +4484,36 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - ADVANCED - /baseaddress:<address> Base address for the library to be built - /checksumalgorithm:<alg> Specify algorithm for calculating source file + /checksumalgorithm:<alg> Specify algorithm for calculating source file checksum stored in PDB. Supported values are: SHA1 (default) or SHA256. - /codepage:<n> Specify the codepage to use when opening source + /codepage:<n> Specify the codepage to use when opening source files /utf8output Output compiler messages in UTF-8 encoding - /main:<type> Specify the type that contains the entry point - (ignore all other possible entry points) (Short + /main:<type> Specify the type that contains the entry point + (ignore all other possible entry points) (Short form: /m) /fullpaths Compiler generates fully qualified paths - /filealign:<n> Specify the alignment used for output file + /filealign:<n> Specify the alignment used for output file sections /pathmap:<K1>=<V1>,<K2>=<V2>,... Specify a mapping for source path names output by the compiler. - /pdb:<file> Specify debug information file name (default: + /pdb:<file> Specify debug information file name (default: output file name with .pdb extension) - /errorendlocation Output line and column of the end location of + /errorendlocation Output line and column of the end location of each error /preferreduilang Specify the preferred output language name. /nostdlib[+|-] Do not reference standard library (mscorlib.dll) /subsystemversion:<string> Specify subsystem version of this assembly - /lib:<file list> Specify additional directories to search in for + /lib:<file list> Specify additional directories to search in for references - /errorreport:<string> Specify how to handle internal compiler errors: - prompt, send, queue, or none. The default is + /errorreport:<string> Specify how to handle internal compiler errors: + prompt, send, queue, or none. The default is queue. - /appconfig:<file> Specify an application configuration file + /appconfig:<file> Specify an application configuration file containing assembly binding settings - /moduleassemblyname:<string> Name of the assembly which this module will be + /moduleassemblyname:<string> Name of the assembly which this module will be a part of /modulename:<string> Specify the name of the source module @@ -4959,8 +4964,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple conversion. - - /sourcelink switch is only supported when emitting Portable PDB (/debug:portable or /debug:embedded must be specified). + + /sourcelink switch is only supported when emitting PDB. /embed switch is only supported when emitting Portable PDB (/debug:portable or /debug:embedded). @@ -5049,6 +5054,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Compiler version: '{0}'. Language version: {1}. + + async main + Tuple element name '{0}' is inferred. Please use language version {1} or greater to access an element by its inferred name. @@ -5061,6 +5069,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A tuple may not contain a value of type 'void'. + + A void or int returning entry point cannot be async + An expression of type '{0}' cannot be handled by a pattern of type '{1}' in C# {2}. Please use language version {3} or greater. diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index d847d9967a144..84df7969a433a 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -1141,7 +1141,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre Debug.Assert(node.InitializerExpressionOpt == null); return node.Update(constructor, rewrittenArguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, - node.Expanded, node.ArgsToParamsOpt, node.ConstantValue, null, node.Type); + node.Expanded, node.ArgsToParamsOpt, node.ConstantValue, null, node.BinderOpt, node.Type); } public override BoundNode VisitArrayAccess(BoundArrayAccess node) @@ -1963,7 +1963,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre ImmutableArray arguments = this.VisitList(node.Arguments); Debug.Assert(node.InitializerExpressionOpt == null); TypeSymbol type = this.VisitType(node.Type); - return node.Update(node.Constructor, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, null, type); + return node.Update(node.Constructor, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, null, node.BinderOpt, type); } public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) diff --git a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs index c14b627d769c1..1d7378d42fa8d 100644 --- a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs +++ b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs @@ -67,6 +67,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar string outputDirectory = baseDirectory; ImmutableArray> pathMap = ImmutableArray>.Empty; string outputFileName = null; + string outputRefFilePath = null; + bool refOnly = false; string documentationPath = null; string errorLogPath = null; bool parseDocumentationComments = false; //Don't just null check documentationFileName because we want to do this even if the file name is invalid. @@ -381,6 +383,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar } continue; + case "out": if (string.IsNullOrWhiteSpace(value)) { @@ -393,6 +396,26 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; + case "refout": + value = RemoveQuotesAndSlashes(value); + if (string.IsNullOrEmpty(value)) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); + } + else + { + outputRefFilePath = ParseGenericPathToFile(value, diagnostics, baseDirectory); + } + + continue; + + case "refonly": + if (value != null) + break; + + refOnly = true; + continue; + case "t": case "target": if (value == null) @@ -1150,6 +1173,16 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar diagnosticOptions[o.Key] = o.Value; } + if (refOnly && outputRefFilePath != null) + { + AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoRefOutWhenRefOnly); + } + + if (outputKind == OutputKind.NetModule && (refOnly || outputRefFilePath != null)) + { + AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly); + } + if (!IsScriptRunner && !sourceFilesSpecified && (outputKind.IsNetModule() || !resourcesOrModulesSpecified)) { AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.WRN_NoSources); @@ -1193,12 +1226,9 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar keyFileSetting = ParseGenericPathToFile(keyFileSetting, diagnostics, baseDirectory); } - if (sourceLink != null) + if (sourceLink != null && !emitPdb) { - if (!emitPdb || !debugInformationFormat.IsPortable()) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_SourceLinkRequiresPortablePdb); - } + AddDiagnostic(diagnostics, ErrorCode.ERR_SourceLinkRequiresPdb); } if (embedAllSourceFiles) @@ -1264,7 +1294,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar var emitOptions = new EmitOptions ( - metadataOnly: false, + metadataOnly: refOnly, + includePrivateMembers: !refOnly && outputRefFilePath == null, debugInformationFormat: debugInformationFormat, pdbFilePath: null, // to be determined later outputNameOverride: null, // to be determined later @@ -1290,8 +1321,9 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar Utf8Output = utf8output, CompilationName = compilationName, OutputFileName = outputFileName, + OutputRefFilePath = outputRefFilePath, PdbPath = pdbPath, - EmitPdb = emitPdb, + EmitPdb = emitPdb && !refOnly, // silently ignore emitPdb when refOnly is set SourceLink = sourceLink, RuleSetPath = ruleSetPath, OutputDirectory = outputDirectory, diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index e30e81fa20749..fb78b5a1e184c 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; +using static Microsoft.CodeAnalysis.CSharp.Binder; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -40,7 +41,7 @@ public sealed partial class CSharpCompilation : Compilation // version. Do not make any changes to the public interface without making the corresponding // change to the VB version. // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! internal static readonly ParallelOptions DefaultParallelOptions = new ParallelOptions(); @@ -93,9 +94,9 @@ internal Conversions Conversions /// /// Holds onto data related to reference binding. /// The manager is shared among multiple compilations that we expect to have the same result of reference binding. - /// In most cases this can be determined without performing the binding. If the compilation however contains a circular + /// In most cases this can be determined without performing the binding. If the compilation however contains a circular /// metadata reference (a metadata reference that refers back to the compilation) we need to avoid sharing of the binding results. - /// We do so by creating a new reference manager for such compilation. + /// We do so by creating a new reference manager for such compilation. /// private ReferenceManager _referenceManager; @@ -128,7 +129,7 @@ public override bool IsCaseSensitive } /// - /// The options the compilation was created with. + /// The options the compilation was created with. /// public new CSharpCompilationOptions Options { @@ -172,14 +173,14 @@ public LanguageVersion LanguageVersion protected override INamedTypeSymbol CommonCreateErrorTypeSymbol(INamespaceOrTypeSymbol container, string name, int arity) { return new ExtendedErrorTypeSymbol( - container.EnsureCSharpSymbolOrNull(nameof(container)), + container.EnsureCSharpSymbolOrNull(nameof(container)), name, arity, errorInfo: null); } protected override INamespaceSymbol CommonCreateErrorNamespaceSymbol(INamespaceSymbol container, string name) { return new MissingNamespaceSymbol( - container.EnsureCSharpSymbolOrNull(nameof(container)), + container.EnsureCSharpSymbolOrNull(nameof(container)), name); } @@ -408,8 +409,8 @@ private CSharpCompilation Update( /// public new CSharpCompilation WithAssemblyName(string assemblyName) { - // Can't reuse references since the source assembly name changed and the referenced symbols might - // have internals-visible-to relationship with this compilation or they might had a circular reference + // Can't reuse references since the source assembly name changed and the referenced symbols might + // have internals-visible-to relationship with this compilation or they might had a circular reference // to this compilation. return new CSharpCompilation( @@ -429,9 +430,9 @@ private CSharpCompilation Update( /// Creates a new compilation with the specified references. /// /// - /// The new will query the given for the underlying - /// metadata as soon as the are needed. - /// + /// The new will query the given for the underlying + /// metadata as soon as the are needed. + /// /// The new compilation uses whatever metadata is currently being provided by the . /// E.g. if the current compilation references a metadata file that has changed since the creation of the compilation /// the new compilation is going to use the updated version, while the current compilation will be using the previous (it doesn't change). @@ -684,7 +685,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees - /// added later. + /// added later. /// public new CSharpCompilation RemoveSyntaxTrees(params SyntaxTree[] trees) { @@ -693,7 +694,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees - /// added later. + /// added later. /// public new CSharpCompilation RemoveSyntaxTrees(IEnumerable trees) { @@ -746,7 +747,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without any syntax trees. Preserves metadata info - /// from this compilation for use with trees added later. + /// from this compilation for use with trees added later. /// public new CSharpCompilation RemoveAllSyntaxTrees() { @@ -805,7 +806,7 @@ internal override bool HasSubmissionResult() } // TODO(tomat): Consider comparing #r's of the old and the new tree. If they are exactly the same we could still reuse. - // This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke + // This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke // that replaces the tree with a new one. var reuseReferenceManager = !oldTree.HasReferenceOrLoadDirectives() && !newTree.HasReferenceOrLoadDirectives(); syntaxAndDeclarations = syntaxAndDeclarations.ReplaceSyntaxTree(oldTree, newTree); @@ -872,7 +873,7 @@ internal IEnumerable ExternAliases /// /// or corresponding to the given reference or null if there is none. /// - /// Uses object identity when comparing two references. + /// Uses object identity when comparing two references. /// internal new Symbol GetAssemblyOrModuleSymbol(MetadataReference reference) { @@ -976,7 +977,7 @@ public MetadataReference GetDirectiveReference(ReferenceDirectiveTriviaSyntax di /// /// Get all modules in this compilation, including the source module, added modules, and all /// modules of referenced assemblies that do not come from an assembly with an extern alias. - /// Metadata imported from aliased assemblies is not visible at the source level except through + /// Metadata imported from aliased assemblies is not visible at the source level except through /// the use of an extern alias directive. So exclude them from this list which is used to construct /// the global namespace. /// @@ -1017,7 +1018,7 @@ internal void GetUnaliasedReferencedAssemblies(ArrayBuilder asse } /// - /// Gets the that corresponds to the assembly symbol. + /// Gets the that corresponds to the assembly symbol. /// public new MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol) { @@ -1065,7 +1066,7 @@ internal SourceAssemblySymbol SourceAssembly } /// - /// Gets the root namespace that contains all namespaces and types defined in source code or in + /// Gets the root namespace that contains all namespaces and types defined in source code or in /// referenced metadata, merged into a single namespace hierarchy. /// internal new NamespaceSymbol GlobalNamespace @@ -1075,7 +1076,7 @@ internal SourceAssemblySymbol SourceAssembly if ((object)_lazyGlobalNamespace == null) { // Get the root namespace from each module, and merge them all together - // Get all modules in this compilation, ones referenced directly by the compilation + // Get all modules in this compilation, ones referenced directly by the compilation // as well as those referenced by all referenced assemblies. var modules = ArrayBuilder.GetInstance(); @@ -1369,7 +1370,7 @@ internal bool DeclaresTheObjectClass internal new MethodSymbol GetEntryPoint(CancellationToken cancellationToken) { EntryPoint entryPoint = GetEntryPointAndDiagnostics(cancellationToken); - return entryPoint == null ? null : entryPoint.MethodSymbol; + return entryPoint?.MethodSymbol; } internal EntryPoint GetEntryPointAndDiagnostics(CancellationToken cancellationToken) @@ -1452,38 +1453,100 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm } } - DiagnosticBag warnings = DiagnosticBag.GetInstance(); - var viableEntryPoints = ArrayBuilder.GetInstance(); - foreach (var candidate in entryPointCandidates) + // Validity and diagnostics are also tracked because they must be conditionally handled + // if there are not any "traditional" entrypoints found. + var taskEntryPoints = ArrayBuilder<(bool IsValid, MethodSymbol Candidate, DiagnosticBag SpecificDiagnostics)>.GetInstance(); + + // These diagnostics (warning only) are added to the compilation only if + // there were not any main methods found. + DiagnosticBag noMainFoundDiagnostics = DiagnosticBag.GetInstance(); + + bool CheckValid(MethodSymbol candidate, bool isCandidate, DiagnosticBag specificDiagnostics) { - if (!candidate.HasEntryPointSignature()) + if (!isCandidate) { - // a single error for partial methods: - warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); - continue; + noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); + noMainFoundDiagnostics.AddRange(specificDiagnostics); + return false; } if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType) { // a single error for partial methods: - warnings.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); - continue; + noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); + return false; } + return true; + } - if (candidate.IsAsync) + var viableEntryPoints = ArrayBuilder.GetInstance(); + + foreach (var candidate in entryPointCandidates) + { + var perCandidateBag = DiagnosticBag.GetInstance(); + var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag); + + if (IsTaskLike) + { + taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag)); + } + else { - diagnostics.Add(ErrorCode.ERR_MainCantBeAsync, candidate.Locations.First(), candidate); + if (CheckValid(candidate, IsCandidate, perCandidateBag)) + { + if (candidate.IsAsync) + { + diagnostics.Add(ErrorCode.ERR_NonTaskMainCantBeAsync, candidate.Locations.First(), candidate); + } + else + { + diagnostics.AddRange(perCandidateBag); + viableEntryPoints.Add(candidate); + } + } + perCandidateBag.Free(); } + } - viableEntryPoints.Add(candidate); + if (viableEntryPoints.Count == 0) + { + foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints) + { + if (CheckValid(Candidate, IsValid, SpecificDiagnostics) && + CheckFeatureAvailability(Candidate.ExtractReturnTypeSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics)) + { + diagnostics.AddRange(SpecificDiagnostics); + viableEntryPoints.Add(Candidate); + } + } } - if ((object)mainType == null || viableEntryPoints.Count == 0) + foreach (var (_, _, SpecificDiagnostics) in taskEntryPoints) { - diagnostics.AddRange(warnings); + SpecificDiagnostics.Free(); } - warnings.Free(); + if (viableEntryPoints.Count == 0) + { + diagnostics.AddRange(noMainFoundDiagnostics); + } + else if ((object)mainType == null) + { + // Filters out diagnostics so that only InvalidMainSig and MainCant'BeGeneric are left. + // The reason that Error diagnostics can end up in `noMainFoundDiagnostics` is when + // HasEntryPointSignature yields some Error Diagnostics when people implement Task or Task incorrectly. + // + // We can't add those Errors to the general diagnostics bag because it would break previously-working programs. + // The fact that these warnings are not added when csc is invoked with /main is possibly a bug, and is tracked at + // https://github.com/dotnet/roslyn/issues/18964 + foreach (var diagnostic in noMainFoundDiagnostics.AsEnumerable()) + { + if (diagnostic.Code == (int)ErrorCode.WRN_InvalidMainSig || diagnostic.Code == (int)ErrorCode.WRN_MainCantBeGeneric) + { + diagnostics.Add(diagnostic); + } + } + } MethodSymbol entryPoint = null; if (viableEntryPoints.Count == 0) @@ -1513,7 +1576,9 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm entryPoint = viableEntryPoints[0]; } + taskEntryPoints.Free(); viableEntryPoints.Free(); + noMainFoundDiagnostics.Free(); return entryPoint; } finally @@ -1523,6 +1588,92 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm } } + internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag diagnostics) + { + // Common case optimization + if (method.ReturnType.SpecialType == SpecialType.System_Void || method.ReturnType.SpecialType == SpecialType.System_Int32) + { + return false; + } + + if (!(method.ReturnType is NamedTypeSymbol namedType)) + { + return false; + } + + // Early bail so we only even check things that are System.Threading.Tasks.Task() + if (!(namedType.ConstructedFrom == GetWellKnownType(WellKnownType.System_Threading_Tasks_Task) || + namedType.ConstructedFrom == GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T))) + { + return false; + } + + var syntax = method.ExtractReturnTypeSyntax(); + var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); + var binder = GetBinder(syntax); + BoundExpression result; + var success = binder.GetAwaitableExpressionInfo(dumbInstance, out _, out _, out _, out result, syntax, diagnostics); + + return success && + (result.Type.SpecialType == SpecialType.System_Void || result.Type.SpecialType == SpecialType.System_Int32); + } + + /// + /// Checks if the method has an entry point compatible signature, i.e. + /// - the return type is either void, int, or returns a , + /// or where the return type of GetAwaiter().GetResult() + /// is either void or int. + /// - has either no parameter or a single parameter of type string[] + /// + private (bool IsCandidate, bool IsTaskLike) HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) + { + if (method.IsVararg) + { + return (false, false); + } + + TypeSymbol returnType = method.ReturnType; + bool returnsTaskOrTaskOfInt = false; + if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void) + { + // Never look for ReturnsAwaitableToVoidOrInt on int32 or void + returnsTaskOrTaskOfInt = ReturnsAwaitableToVoidOrInt(method, bag); + if (!returnsTaskOrTaskOfInt) + { + return (false, false); + } + } + + if (method.RefKind != RefKind.None) + { + return (false, returnsTaskOrTaskOfInt); + } + + if (method.Parameters.Length == 0) + { + return (true, returnsTaskOrTaskOfInt); + } + + if (method.Parameters.Length > 1) + { + return (false, returnsTaskOrTaskOfInt); + } + + if (!method.ParameterRefKinds.IsDefault) + { + return (false, returnsTaskOrTaskOfInt); + } + + var firstType = method.Parameters[0].Type; + if (firstType.TypeKind != TypeKind.Array) + { + return (false, returnsTaskOrTaskOfInt); + } + + var array = (ArrayTypeSymbol)firstType; + return (array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String, returnsTaskOrTaskOfInt); + } + internal override bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code) => code == (int)ErrorCode.ERR_NoTypeDef; @@ -2017,7 +2168,8 @@ private void GetDiagnosticsForAllMethodBodies(DiagnosticBag diagnostics, Cancell MethodCompiler.CompileMethodBodies( compilation: this, moduleBeingBuiltOpt: null, - generateDebugInfo: false, + emittingPdb: false, + emitTestCoverageData: false, hasDeclarationErrors: false, diagnostics: diagnostics, filterOpt: null, @@ -2059,7 +2211,8 @@ private ImmutableArray GetDiagnosticsForMethodBodiesInTree(SyntaxTre MethodCompiler.CompileMethodBodies( compilation: this, moduleBeingBuiltOpt: null, - generateDebugInfo: false, + emittingPdb: false, + emitTestCoverageData: false, hasDeclarationErrors: false, diagnostics: diagnostics, filterOpt: s => IsDefinedOrImplementedInSourceTree(s, tree, span), @@ -2171,8 +2324,8 @@ internal ImmutableArray GetDiagnosticsForSyntaxTree( { //remove some errors that don't have locations in the tree, like "no suitable main method." //Members in trees other than the one being examined are not compiled. This includes field - //initializers which can result in 'field is never initialized' warnings for fields in partial - //types when the field is in a different source file than the one for which we're getting diagnostics. + //initializers which can result in 'field is never initialized' warnings for fields in partial + //types when the field is in a different source file than the one for which we're getting diagnostics. //For that reason the bag must be also filtered by tree. IEnumerable methodBodyDiagnostics = GetDiagnosticsForMethodBodiesInTree(syntaxTree, filterSpanWithinTree, cancellationToken); @@ -2301,21 +2454,29 @@ internal override CommonPEModuleBuilder CreateModuleBuilder( internal override bool CompileMethods( CommonPEModuleBuilder moduleBuilder, bool emittingPdb, + bool emitMetadataOnly, + bool emitTestCoverageData, DiagnosticBag diagnostics, Predicate filterOpt, CancellationToken cancellationToken) { // The diagnostics should include syntax and declaration errors. We insert these before calling Emitter.Emit, so that the emitter // does not attempt to emit if there are declaration errors (but we do insert all errors from method body binding...) - bool hasDeclarationErrors = !FilterAndAppendDiagnostics(diagnostics, GetDiagnostics(CompilationStage.Declare, true, cancellationToken)); + PooledHashSet excludeDiagnostics = null; + if (emitMetadataOnly) + { + excludeDiagnostics = PooledHashSet.GetInstance(); + excludeDiagnostics.Add((int)ErrorCode.ERR_ConcreteMissingBody); + } + bool hasDeclarationErrors = !FilterAndAppendDiagnostics(diagnostics, GetDiagnostics(CompilationStage.Declare, true, cancellationToken), excludeDiagnostics); + excludeDiagnostics?.Free(); // TODO (tomat): NoPIA: // EmbeddedSymbolManager.MarkAllDeferredSymbolsAsReferenced(this) var moduleBeingBuilt = (PEModuleBuilder)moduleBuilder; - var emitOptions = moduleBeingBuilt.EmitOptions; - if (emitOptions.EmitMetadataOnly) + if (emitMetadataOnly) { if (hasDeclarationErrors) { @@ -2333,7 +2494,7 @@ internal override bool CompileMethods( } else { - if ((emittingPdb || emitOptions.EmitTestCoverageData) && + if ((emittingPdb || emitTestCoverageData) && !CreateDebugDocuments(moduleBeingBuilt.DebugDocumentsBuilder, moduleBeingBuilt.EmbeddedTexts, diagnostics)) { return false; @@ -2349,6 +2510,7 @@ internal override bool CompileMethods( this, moduleBeingBuilt, emittingPdb, + emitTestCoverageData, hasDeclarationErrors, diagnostics: methodBodyDiagnosticBag, filterOpt: filterOpt, @@ -2369,11 +2531,10 @@ internal override bool GenerateResourcesAndDocumentationComments( CommonPEModuleBuilder moduleBuilder, Stream xmlDocStream, Stream win32Resources, + string outputNameOverride, DiagnosticBag diagnostics, CancellationToken cancellationToken) { - Debug.Assert(!moduleBuilder.EmitOptions.EmitMetadataOnly); - // Use a temporary bag so we don't have to refilter pre-existing diagnostics. var resourceDiagnostics = DiagnosticBag.GetInstance(); @@ -2395,7 +2556,7 @@ internal override bool GenerateResourcesAndDocumentationComments( // Use a temporary bag so we don't have to refilter pre-existing diagnostics. var xmlDiagnostics = DiagnosticBag.GetInstance(); - string assemblyName = FileNameUtilities.ChangeExtension(moduleBuilder.EmitOptions.OutputNameOverride, extension: null); + string assemblyName = FileNameUtilities.ChangeExtension(outputNameOverride, extension: null); DocumentationCommentCompiler.WriteDocumentationCommentXml(this, assemblyName, xmlDocStream, xmlDiagnostics, cancellationToken); return FilterAndAppendAndFreeDiagnostics(diagnostics, ref xmlDiagnostics); @@ -2730,15 +2891,15 @@ protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( return TupleTypeSymbol.Create( locationOpt: null, // no location for the type declaration elementTypes: typesBuilder.ToImmutableAndFree(), - elementLocations: elementLocations, - elementNames: elementNames, + elementLocations: elementLocations, + elementNames: elementNames, compilation: this, shouldCheckConstraints: false, errorPositions: default(ImmutableArray)); } protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( - INamedTypeSymbol underlyingType, + INamedTypeSymbol underlyingType, ImmutableArray elementNames, ImmutableArray elementLocations) { @@ -2758,7 +2919,7 @@ protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( } protected override INamedTypeSymbol CommonCreateAnonymousTypeSymbol( - ImmutableArray memberTypes, + ImmutableArray memberTypes, ImmutableArray memberNames, ImmutableArray memberLocations, ImmutableArray memberIsReadOnly) @@ -2856,7 +3017,7 @@ internal override int CompareSourceLocations(Location loc1, Location loc2) #endregion /// - /// Returns if the compilation has all of the members necessary to emit metadata about + /// Returns if the compilation has all of the members necessary to emit metadata about /// dynamic types. /// /// @@ -2912,7 +3073,7 @@ internal bool EnableEnumArrayBlockInitialization return sustainedLowLatency != null && sustainedLowLatency.ContainingAssembly == Assembly.CorLibrary; } } - + internal override bool IsIOperationFeatureEnabled() { var options = (CSharpParseOptions)this.SyntaxTrees.FirstOrDefault()?.Options; diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs index 76cefa256a37c..ef370c3b732c8 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.cs @@ -47,6 +47,7 @@ internal static ImmutableArray ConstructScriptConstructorBody( invokedAsExtensionMethod: false, argsToParamsOpt: ImmutableArray.Empty, resultKind: LookupResultKind.Viable, + binderOpt: null, type: objectType) { WasCompilerGenerated = true }) { WasCompilerGenerated = true }; diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 1ab8e15f9f059..5e67a121110a7 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -23,6 +23,7 @@ internal sealed class MethodCompiler : CSharpSymbolVisitor filterOpt, CancellationToken cancellationToken) { Debug.Assert(compilation != null); @@ -90,16 +91,19 @@ internal MethodCompiler(CSharpCompilation compilation, PEModuleBuilder moduleBei _hasDeclarationErrors = hasDeclarationErrors; SetGlobalErrorIfTrue(hasDeclarationErrors); - if (emittingPdb || moduleBeingBuiltOpt?.EmitOptions.EmitTestCoverageData == true) + if (emittingPdb || emitTestCoverageData) { _debugDocumentProvider = (path, basePath) => moduleBeingBuiltOpt.DebugDocumentsBuilder.GetOrAddDebugDocument(path, basePath, CreateDebugDocumentForFile); } + + _emitTestCoverageData = emitTestCoverageData; } public static void CompileMethodBodies( CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, - bool generateDebugInfo, + bool emittingPdb, + bool emitTestCoverageData, bool hasDeclarationErrors, DiagnosticBag diagnostics, Predicate filterOpt, @@ -122,7 +126,8 @@ public static void CompileMethodBodies( MethodCompiler methodCompiler = new MethodCompiler( compilation, moduleBeingBuiltOpt, - generateDebugInfo, + emittingPdb, + emitTestCoverageData, hasDeclarationErrors, diagnostics, filterOpt, @@ -184,6 +189,8 @@ public static void CompileMethodBodies( } } + // Returns the MethodSymbol for the assembly entrypoint. If the user has a Task returning main, + // this function returns the synthesized Main MethodSymbol. private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuilt, bool hasDeclarationErrors, DiagnosticBag diagnostics, CancellationToken cancellationToken) { var entryPointAndDiagnostics = compilation.GetEntryPointAndDiagnostics(cancellationToken); @@ -194,21 +201,69 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul Debug.Assert(!entryPointAndDiagnostics.Diagnostics.IsDefault); diagnostics.AddRange(entryPointAndDiagnostics.Diagnostics); - var entryPoint = entryPointAndDiagnostics.MethodSymbol; - var synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; + + if ((object)entryPoint == null) + { + Debug.Assert(entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); + return null; + } + + // entryPoint can be a SynthesizedEntryPointSymbol if a script is being compiled. + SynthesizedEntryPointSymbol synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; + if ((object)synthesizedEntryPoint == null && (entryPoint.ReturnType.IsGenericTaskType(compilation) || entryPoint.ReturnType.IsNonGenericTaskType(compilation))) + { + synthesizedEntryPoint = new SynthesizedEntryPointSymbol.AsyncForwardEntryPoint(compilation, entryPoint.ContainingType, entryPoint); + entryPoint = synthesizedEntryPoint; + if ((object)moduleBeingBuilt != null) + { + moduleBeingBuilt.AddSynthesizedDefinition(entryPoint.ContainingType, synthesizedEntryPoint); + } + } + if (((object)synthesizedEntryPoint != null) && (moduleBeingBuilt != null) && !hasDeclarationErrors && !diagnostics.HasAnyErrors()) { - var body = synthesizedEntryPoint.CreateBody(); + BoundStatement body = synthesizedEntryPoint.CreateBody(); + + var dynamicAnalysisSpans = ImmutableArray.Empty; + VariableSlotAllocator lazyVariableSlotAllocator = null; + var lambdaDebugInfoBuilder = ArrayBuilder.GetInstance(); + var closureDebugInfoBuilder = ArrayBuilder.GetInstance(); + StateMachineTypeSymbol stateMachineTypeOpt = null; const int methodOrdinal = -1; + + var loweredBody = LowerBodyOrInitializer( + synthesizedEntryPoint, + methodOrdinal, + body, + null, + new TypeCompilationState(synthesizedEntryPoint.ContainingType, compilation, moduleBeingBuilt), + false, + null, + ref dynamicAnalysisSpans, + diagnostics, + ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + out stateMachineTypeOpt); + + Debug.Assert((object)lazyVariableSlotAllocator == null); + Debug.Assert((object)stateMachineTypeOpt == null); + Debug.Assert(dynamicAnalysisSpans.IsEmpty); + Debug.Assert(lambdaDebugInfoBuilder.IsEmpty()); + Debug.Assert(closureDebugInfoBuilder.IsEmpty()); + + lambdaDebugInfoBuilder.Free(); + closureDebugInfoBuilder.Free(); + var emittedBody = GenerateMethodBody( moduleBeingBuilt, synthesizedEntryPoint, methodOrdinal, - body, + loweredBody, ImmutableArray.Empty, ImmutableArray.Empty, stateMachineTypeOpt: null, @@ -217,11 +272,11 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul debugDocumentProvider: null, importChainOpt: null, emittingPdb: false, + emitTestCoverageData: false, dynamicAnalysisSpans: ImmutableArray.Empty); moduleBeingBuilt.SetMethodBody(synthesizedEntryPoint, emittedBody); } - Debug.Assert((object)entryPoint != null || entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); return entryPoint; } @@ -546,7 +601,7 @@ private void CompileSynthesizedMethods(PrivateImplementationDetails privateImplC Debug.Assert(_moduleBeingBuiltOpt != null); var compilationState = new TypeCompilationState(null, _compilation, _moduleBeingBuiltOpt); - foreach (MethodSymbol method in privateImplClass.GetMethods(new EmitContext(_moduleBeingBuiltOpt, null, diagnostics))) + foreach (MethodSymbol method in privateImplClass.GetMethods(new EmitContext(_moduleBeingBuiltOpt, null, diagnostics, metadataOnly: false, includePrivateMembers: true))) { Debug.Assert(method.SynthesizesLoweredBoundBody); method.GenerateMethodBody(compilationState, diagnostics); @@ -594,74 +649,75 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState) var importChain = methodWithBody.ImportChainOpt; compilationState.CurrentImportChain = importChain; - // We make sure that an asynchronous mutation to the diagnostic bag does not - // confuse the method body generator by making a fresh bag and then loading - // any diagnostics emitted into it back into the main diagnostic bag. - var diagnosticsThisMethod = DiagnosticBag.GetInstance(); + // We make sure that an asynchronous mutation to the diagnostic bag does not + // confuse the method body generator by making a fresh bag and then loading + // any diagnostics emitted into it back into the main diagnostic bag. + var diagnosticsThisMethod = DiagnosticBag.GetInstance(); var method = methodWithBody.Method; - var lambda = method as SynthesizedLambdaMethod; - var variableSlotAllocatorOpt = ((object)lambda != null) ? - _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(lambda, lambda.TopLevelMethod, diagnosticsThisMethod) : - _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(method, method, diagnosticsThisMethod); - - // Synthesized methods have no ordinal stored in custom debug information (only user-defined methods have ordinals). - // In case of async lambdas, which synthesize a state machine type during the following rewrite, the containing method has already been uniquely named, - // so there is no need to produce a unique method ordinal for the corresponding state machine type, whose name includes the (unique) containing method name. - const int methodOrdinal = -1; - MethodBody emittedBody = null; + var lambda = method as SynthesizedLambdaMethod; + var variableSlotAllocatorOpt = ((object)lambda != null) ? + _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(lambda, lambda.TopLevelMethod, diagnosticsThisMethod) : + _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(method, method, diagnosticsThisMethod); - try - { - // Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async - IteratorStateMachine iteratorStateMachine; - BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine); - StateMachineTypeSymbol stateMachine = iteratorStateMachine; + // Synthesized methods have no ordinal stored in custom debug information (only user-defined methods have ordinals). + // In case of async lambdas, which synthesize a state machine type during the following rewrite, the containing method has already been uniquely named, + // so there is no need to produce a unique method ordinal for the corresponding state machine type, whose name includes the (unique) containing method name. + const int methodOrdinal = -1; + MethodBody emittedBody = null; - if (!loweredBody.HasErrors) + try { - AsyncStateMachine asyncStateMachine; - loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine); + // Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async + IteratorStateMachine iteratorStateMachine; + BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine); + StateMachineTypeSymbol stateMachine = iteratorStateMachine; - Debug.Assert(iteratorStateMachine == null || asyncStateMachine == null); - stateMachine = stateMachine ?? asyncStateMachine; - } + if (!loweredBody.HasErrors) + { + AsyncStateMachine asyncStateMachine; + loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine); - if (!diagnosticsThisMethod.HasAnyErrors() && !_globalHasErrors) - { - emittedBody = GenerateMethodBody( - _moduleBeingBuiltOpt, - method, - methodOrdinal, - loweredBody, - ImmutableArray.Empty, - ImmutableArray.Empty, - stateMachine, - variableSlotAllocatorOpt, - diagnosticsThisMethod, - _debugDocumentProvider, + Debug.Assert(iteratorStateMachine == null || asyncStateMachine == null); + stateMachine = stateMachine ?? asyncStateMachine; + } + + if (!diagnosticsThisMethod.HasAnyErrors() && !_globalHasErrors) + { + emittedBody = GenerateMethodBody( + _moduleBeingBuiltOpt, + method, + methodOrdinal, + loweredBody, + ImmutableArray.Empty, + ImmutableArray.Empty, + stateMachine, + variableSlotAllocatorOpt, + diagnosticsThisMethod, + _debugDocumentProvider, method.GenerateDebugInfo ? importChain : null, - emittingPdb: _emittingPdb, - dynamicAnalysisSpans: ImmutableArray.Empty); + emittingPdb: _emittingPdb, + emitTestCoverageData: _emitTestCoverageData, + dynamicAnalysisSpans: ImmutableArray.Empty); + } + } + catch (BoundTreeVisitor.CancelledByStackGuardException ex) + { + ex.AddAnError(_diagnostics); } - } - catch (BoundTreeVisitor.CancelledByStackGuardException ex) - { - ex.AddAnError(_diagnostics); - } - _diagnostics.AddRange(diagnosticsThisMethod); - diagnosticsThisMethod.Free(); + _diagnostics.AddRange(diagnosticsThisMethod); + diagnosticsThisMethod.Free(); - // error while generating IL - if (emittedBody == null) - { - break; - } + // error while generating IL + if (emittedBody == null) + { + break; + } - _moduleBeingBuiltOpt.SetMethodBody(method, emittedBody); + _moduleBeingBuiltOpt.SetMethodBody(method, emittedBody); + } } - } finally { compilationState.CurrentImportChain = oldImportChain; @@ -747,6 +803,7 @@ private void CompileFieldLikeEventAccessor(SourceEventSymbol eventSymbol, bool i debugDocumentProvider: _debugDocumentProvider, importChainOpt: null, emittingPdb: false, + emitTestCoverageData: _emitTestCoverageData, dynamicAnalysisSpans: ImmutableArray.Empty); _moduleBeingBuiltOpt.SetMethodBody(accessor, emittedBody); @@ -818,7 +875,6 @@ private void CompileMethod( } ImportChain oldImportChain = compilationState.CurrentImportChain; - bool instrumentForDynamicAnalysis = _moduleBeingBuiltOpt?.EmitOptions.EmitTestCoverageData == true; // In order to avoid generating code for methods with errors, we create a diagnostic bag just for this method. DiagnosticBag diagsForCurrentMethod = DiagnosticBag.GetInstance(); @@ -887,9 +943,9 @@ private void CompileMethod( analyzedInitializers = InitializerRewriter.RewriteConstructor(processedInitializers.BoundInitializers, methodSymbol); processedInitializers.HasErrors = processedInitializers.HasErrors || analyzedInitializers.HasAnyErrors; - if (body != null && ((methodSymbol.ContainingType.IsStructType() && !methodSymbol.IsImplicitConstructor) || instrumentForDynamicAnalysis)) + if (body != null && ((methodSymbol.ContainingType.IsStructType() && !methodSymbol.IsImplicitConstructor) || _emitTestCoverageData)) { - if (instrumentForDynamicAnalysis && methodSymbol.IsImplicitConstructor) + if (_emitTestCoverageData && methodSymbol.IsImplicitConstructor) { // Flow analysis over the initializers is necessary in order to find assignments to fields. // Bodies of implicit constructors do not get flow analysis later, so the initializers @@ -1004,7 +1060,7 @@ private void CompileMethod( flowAnalyzedBody, previousSubmissionFields, compilationState, - instrumentForDynamicAnalysis, + _emitTestCoverageData, _debugDocumentProvider, ref dynamicAnalysisSpans, diagsForCurrentMethod, @@ -1046,7 +1102,7 @@ private void CompileMethod( { // For dynamic analysis, field initializers are instrumented as part of constructors, // and so are never instrumented here. - Debug.Assert(!instrumentForDynamicAnalysis); + Debug.Assert(!_emitTestCoverageData); StateMachineTypeSymbol initializerStateMachineTypeOpt; BoundStatement lowered = LowerBodyOrInitializer( @@ -1055,7 +1111,7 @@ private void CompileMethod( analyzedInitializers, previousSubmissionFields, compilationState, - instrumentForDynamicAnalysis, + _emitTestCoverageData, _debugDocumentProvider, ref dynamicAnalysisSpans, diagsForCurrentMethod, @@ -1087,7 +1143,7 @@ private void CompileMethod( { if (processedInitializers.LoweredInitializers.Kind == BoundKind.StatementList) { - BoundStatementList lowered = (BoundStatementList) processedInitializers.LoweredInitializers; + BoundStatementList lowered = (BoundStatementList)processedInitializers.LoweredInitializers; boundStatements = boundStatements.Concat(lowered.Statements); } else @@ -1119,6 +1175,7 @@ private void CompileMethod( _debugDocumentProvider, importChain, _emittingPdb, + _emitTestCoverageData, dynamicAnalysisSpans); _moduleBeingBuiltOpt.SetMethodBody(methodSymbol.PartialDefinitionPart ?? methodSymbol, emittedBody); @@ -1178,7 +1235,7 @@ internal static BoundStatement LowerBodyOrInitializer( previousSubmissionFields: previousSubmissionFields, allowOmissionOfConditionalCalls: true, instrumentForDynamicAnalysis: instrumentForDynamicAnalysis, - debugDocumentProvider:debugDocumentProvider, + debugDocumentProvider: debugDocumentProvider, dynamicAnalysisSpans: ref dynamicAnalysisSpans, diagnostics: diagnostics, sawLambdas: out sawLambdas, @@ -1231,7 +1288,7 @@ internal static BoundStatement LowerBodyOrInitializer( lazyVariableSlotAllocator, compilationState, diagnostics, - assignLocals: false); + assignLocals: null); } if (bodyWithoutLambdas.HasErrors) @@ -1275,6 +1332,7 @@ private static MethodBody GenerateMethodBody( DebugDocumentProvider debugDocumentProvider, ImportChain importChainOpt, bool emittingPdb, + bool emitTestCoverageData, ImmutableArray dynamicAnalysisSpans) { // Note: don't call diagnostics.HasAnyErrors() in release; could be expensive if compilation has many warnings. @@ -1301,7 +1359,7 @@ private static MethodBody GenerateMethodBody( bool isAsyncStateMachine; MethodSymbol kickoffMethod; - if (method is SynthesizedStateMachineMethod stateMachineMethod && + if (method is SynthesizedStateMachineMethod stateMachineMethod && method.Name == WellKnownMemberNames.MoveNextMethodName) { kickoffMethod = stateMachineMethod.StateMachineType.KickoffMethod; @@ -1329,9 +1387,9 @@ private static MethodBody GenerateMethodBody( // So it is undesirable to consider these exceptions "user unhandled" since there may well be user code that is awaiting the task. // This is a heuristic since it's possible that there is no user code awaiting the task. moveNextBodyDebugInfoOpt = new AsyncMoveNextBodyDebugInfo( - kickoffMethod, + kickoffMethod, kickoffMethod.ReturnsVoid ? asyncCatchHandlerOffset : -1, - asyncYieldPoints, + asyncYieldPoints, asyncResumePoints); } else @@ -1383,7 +1441,7 @@ private static MethodBody GenerateMethodBody( } DynamicAnalysisMethodBodyData dynamicAnalysisDataOpt = null; - if (moduleBuilder.EmitOptions.EmitTestCoverageData) + if (emitTestCoverageData) { Debug.Assert(debugDocumentProvider != null); dynamicAnalysisDataOpt = new DynamicAnalysisMethodBodyData(dynamicAnalysisSpans); @@ -1808,6 +1866,7 @@ internal static BoundCall GenerateObjectConstructorInitializer(MethodSymbol cons invokedAsExtensionMethod: false, argsToParamsOpt: ImmutableArray.Empty, resultKind: resultKind, + binderOpt: null, type: objectType, hasErrors: hasErrors) { WasCompilerGenerated = true }; diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index 72b8f9303c885..00ed2fbed8133 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -87,6 +87,7 @@ internal static EmitDifferenceResult EmitDifference( updatedMethods, diagnostics, testData?.SymWriterFactory, + emitOptions.PdbFilePath, cancellationToken); } @@ -123,8 +124,8 @@ private static EmitBaseline MapToCompilation( // Mapping from previous compilation to the current. var anonymousTypeMap = moduleBeingBuilt.GetAnonymousTypeMap(); var sourceAssembly = ((CSharpCompilation)previousGeneration.Compilation).SourceAssembly; - var sourceContext = new EmitContext((PEModuleBuilder)previousGeneration.PEModuleBuilder, null, new DiagnosticBag()); - var otherContext = new EmitContext(moduleBeingBuilt, null, new DiagnosticBag()); + var sourceContext = new EmitContext((PEModuleBuilder)previousGeneration.PEModuleBuilder, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); + var otherContext = new EmitContext(moduleBeingBuilt, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); var matcher = new CSharpSymbolMatcher( anonymousTypeMap, diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs index 50fe809e3f4c8..f304335d41170 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs @@ -33,7 +33,7 @@ public PEDeltaAssemblyBuilder( : base(sourceAssembly, emitOptions, outputKind, serializationProperties, manifestResources, additionalTypes: ImmutableArray.Empty) { var initialBaseline = previousGeneration.InitialBaseline; - var context = new EmitContext(this, null, new DiagnosticBag()); + var context = new EmitContext(this, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); // Hydrate symbols from initial metadata. Once we do so it is important to reuse these symbols across all generations, // in order for the symbol matcher to be able to use reference equality once it maps symbols to initial metadata. @@ -47,7 +47,7 @@ public PEDeltaAssemblyBuilder( if (previousGeneration.Ordinal > 0) { var previousAssembly = ((CSharpCompilation)previousGeneration.Compilation).SourceAssembly; - var previousContext = new EmitContext((PEModuleBuilder)previousGeneration.PEModuleBuilder, null, new DiagnosticBag()); + var previousContext = new EmitContext((PEModuleBuilder)previousGeneration.PEModuleBuilder, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); matchToPrevious = new CSharpSymbolMatcher( previousGeneration.AnonymousTypeMap, diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs index c776c270e26a2..20bb10723f950 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/MethodSymbolAdapter.cs @@ -582,10 +582,8 @@ private IEnumerable GetReturnValueCustomAttributesToEmit() { CheckDefinitionInvariant(); - ImmutableArray userDefined; + ImmutableArray userDefined = this.GetReturnTypeAttributes(); ArrayBuilder synthesized = null; - - userDefined = this.GetReturnTypeAttributes(); this.AddSynthesizedReturnTypeAttributes(ref synthesized); // Note that callers of this method (CCI and ReflectionEmitter) have to enumerate diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs index da185b6ee18cf..da2b554d0c6bd 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs @@ -287,12 +287,15 @@ Cci.ITypeReference Cci.ITypeDefinition.GetBaseClass(EmitContext context) diagnostics: context.Diagnostics) : null; } - IEnumerable Cci.ITypeDefinition.Events + IEnumerable Cci.ITypeDefinition.GetEvents(EmitContext context) { - get + CheckDefinitionInvariant(); + foreach (var e in GetEventsToEmit()) { - CheckDefinitionInvariant(); - return GetEventsToEmit(); + if (e.ShouldInclude(context)) + { + yield return e; + } } } @@ -389,9 +392,15 @@ internal virtual IEnumerable GetEventsToEmit() { CheckDefinitionInvariant(); + // All fields in a struct should be emitted + bool isStruct = this.IsStructType(); + foreach (var f in GetFieldsToEmit()) { - yield return f; + if (isStruct || f.ShouldInclude(context)) + { + yield return f; + } } IEnumerable generated = ((PEModuleBuilder)context.Module).GetSynthesizedFields(this); @@ -400,7 +409,10 @@ internal virtual IEnumerable GetEventsToEmit() { foreach (var f in generated) { - yield return f; + if (isStruct || f.ShouldInclude(context)) + { + yield return f; + } } } } @@ -630,7 +642,10 @@ internal virtual bool IsMetadataSealed foreach (var method in this.GetMethodsToEmit()) { Debug.Assert((object)method != null); - yield return method; + if (method.ShouldInclude(context)) + { + yield return method; + } } IEnumerable generated = ((PEModuleBuilder)context.Module).GetSynthesizedMethods(this); @@ -639,7 +654,10 @@ internal virtual bool IsMetadataSealed { foreach (var m in generated) { - yield return m; + if (m.ShouldInclude(context)) + { + yield return m; + } } } } @@ -704,7 +722,10 @@ internal virtual IEnumerable GetMethodsToEmit() foreach (var property in this.GetPropertiesToEmit()) { Debug.Assert((object)property != null); - yield return property; + if (property.ShouldInclude(context)) + { + yield return property; + } } IEnumerable generated = ((PEModuleBuilder)context.Module).GetSynthesizedProperties(this); @@ -713,7 +734,10 @@ internal virtual IEnumerable GetMethodsToEmit() { foreach (var m in generated) { - yield return m; + if (m.ShouldInclude(context)) + { + yield return m; + } } } } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 60b6fb3d1412d..1f6765b2b9526 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -83,9 +83,10 @@ internal sealed override Cci.ICustomAttribute SynthesizeAttribute(WellKnownMembe return Compilation.TrySynthesizeAttribute(attributeConstructor); } - public sealed override IEnumerable GetSourceAssemblyAttributes() + public sealed override IEnumerable GetSourceAssemblyAttributes(bool isRefAssembly) { - return SourceModule.ContainingSourceAssembly.GetCustomAttributesToEmit(this.CompilationState, emittingAssemblyAttributesInNetModule: OutputKind.IsNetModule()); + return SourceModule.ContainingSourceAssembly + .GetCustomAttributesToEmit(this.CompilationState, isRefAssembly, emittingAssemblyAttributesInNetModule: OutputKind.IsNetModule()); } public sealed override IEnumerable GetSourceAssemblySecurityAttributes() @@ -358,9 +359,9 @@ internal virtual bool TryGetAnonymousTypeName(NamedTypeSymbol template, out stri return false; } - internal sealed override ImmutableArray GetAnonymousTypes() + internal sealed override ImmutableArray GetAnonymousTypes(EmitContext context) { - if (EmitOptions.EmitMetadataOnly) + if (context.MetadataOnly) { return ImmutableArray.Empty; } diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs index a9cdc135ae298..217a3dccd7f62 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PropertySymbolAdapter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.Cci; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.Emit; @@ -10,41 +11,38 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal partial class PropertySymbol : - Cci.IPropertyDefinition + IPropertyDefinition { #region IPropertyDefinition Members - IEnumerable Cci.IPropertyDefinition.Accessors + IEnumerable IPropertyDefinition.GetAccessors(EmitContext context) { - get - { - CheckDefinitionInvariant(); + CheckDefinitionInvariant(); - var getMethod = this.GetMethod; - if ((object)getMethod != null) - { - yield return getMethod; - } + MethodSymbol getMethod = this.GetMethod; + if (getMethod != null && getMethod.ShouldInclude(context)) + { + yield return getMethod; + } - var setMethod = this.SetMethod; - if ((object)setMethod != null) - { - yield return setMethod; - } + MethodSymbol setMethod = this.SetMethod; + if (setMethod != null && setMethod.ShouldInclude(context)) + { + yield return setMethod; + } - SourcePropertySymbol sourceProperty = this as SourcePropertySymbol; - if ((object)sourceProperty != null) + SourcePropertySymbol sourceProperty = this as SourcePropertySymbol; + if ((object)sourceProperty != null && sourceProperty.ShouldInclude(context)) + { + SynthesizedSealedPropertyAccessor synthesizedAccessor = sourceProperty.SynthesizedSealedAccessorOpt; + if ((object)synthesizedAccessor != null) { - SynthesizedSealedPropertyAccessor synthesizedAccessor = sourceProperty.SynthesizedSealedAccessorOpt; - if ((object)synthesizedAccessor != null) - { - yield return synthesizedAccessor; - } + yield return synthesizedAccessor; } } } - MetadataConstant Cci.IPropertyDefinition.DefaultValue + MetadataConstant IPropertyDefinition.DefaultValue { get { @@ -53,7 +51,7 @@ MetadataConstant Cci.IPropertyDefinition.DefaultValue } } - Cci.IMethodReference Cci.IPropertyDefinition.Getter + IMethodReference IPropertyDefinition.Getter { get { @@ -68,7 +66,7 @@ Cci.IMethodReference Cci.IPropertyDefinition.Getter } } - bool Cci.IPropertyDefinition.HasDefaultValue + bool IPropertyDefinition.HasDefaultValue { get { @@ -77,7 +75,7 @@ bool Cci.IPropertyDefinition.HasDefaultValue } } - bool Cci.IPropertyDefinition.IsRuntimeSpecial + bool IPropertyDefinition.IsRuntimeSpecial { get { @@ -95,7 +93,7 @@ internal virtual bool HasRuntimeSpecialName } } - bool Cci.IPropertyDefinition.IsSpecialName + bool IPropertyDefinition.IsSpecialName { get { @@ -104,16 +102,16 @@ bool Cci.IPropertyDefinition.IsSpecialName } } - ImmutableArray Cci.IPropertyDefinition.Parameters + ImmutableArray IPropertyDefinition.Parameters { get { CheckDefinitionInvariant(); - return StaticCast.From(this.Parameters); + return StaticCast.From(this.Parameters); } } - Cci.IMethodReference Cci.IPropertyDefinition.Setter + IMethodReference IPropertyDefinition.Setter { get { @@ -142,7 +140,7 @@ private void CheckDefinitionInvariantAllowEmbedded() Debug.Assert(this.ContainingModule is SourceModuleSymbol || this.ContainingAssembly.IsLinked); } - Cci.CallingConvention Cci.ISignature.CallingConvention + CallingConvention ISignature.CallingConvention { get { @@ -151,7 +149,7 @@ Cci.CallingConvention Cci.ISignature.CallingConvention } } - ushort Cci.ISignature.ParameterCount + ushort ISignature.ParameterCount { get { @@ -160,31 +158,31 @@ ushort Cci.ISignature.ParameterCount } } - ImmutableArray Cci.ISignature.GetParameters(EmitContext context) + ImmutableArray ISignature.GetParameters(EmitContext context) { CheckDefinitionInvariant(); - return StaticCast.From(this.Parameters); + return StaticCast.From(this.Parameters); } - ImmutableArray Cci.ISignature.ReturnValueCustomModifiers + ImmutableArray ISignature.ReturnValueCustomModifiers { get { CheckDefinitionInvariantAllowEmbedded(); - return this.TypeCustomModifiers.As(); + return this.TypeCustomModifiers.As(); } } - ImmutableArray Cci.ISignature.RefCustomModifiers + ImmutableArray ISignature.RefCustomModifiers { get { CheckDefinitionInvariantAllowEmbedded(); - return this.RefCustomModifiers.As(); + return this.RefCustomModifiers.As(); } } - bool Cci.ISignature.ReturnValueIsByRef + bool ISignature.ReturnValueIsByRef { get { @@ -193,7 +191,7 @@ bool Cci.ISignature.ReturnValueIsByRef } } - Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) + ITypeReference ISignature.GetType(EmitContext context) { CheckDefinitionInvariantAllowEmbedded(); return ((PEModuleBuilder)context.Module).Translate(this.Type, @@ -205,7 +203,7 @@ Cci.ITypeReference Cci.ISignature.GetType(EmitContext context) #region ITypeDefinitionMember Members - Cci.ITypeDefinition Cci.ITypeDefinitionMember.ContainingTypeDefinition + ITypeDefinition ITypeDefinitionMember.ContainingTypeDefinition { get { @@ -214,7 +212,7 @@ Cci.ITypeDefinition Cci.ITypeDefinitionMember.ContainingTypeDefinition } } - Cci.TypeMemberVisibility Cci.ITypeDefinitionMember.Visibility + TypeMemberVisibility ITypeDefinitionMember.Visibility { get { @@ -227,7 +225,7 @@ Cci.TypeMemberVisibility Cci.ITypeDefinitionMember.Visibility #region ITypeMemberReference Members - Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext context) + ITypeReference ITypeMemberReference.GetContainingType(EmitContext context) { CheckDefinitionInvariant(); return this.ContainingType; @@ -237,13 +235,13 @@ Cci.ITypeReference Cci.ITypeMemberReference.GetContainingType(EmitContext contex #region IReference Members - void Cci.IReference.Dispatch(Cci.MetadataVisitor visitor) + void IReference.Dispatch(MetadataVisitor visitor) { CheckDefinitionInvariant(); - visitor.Visit((Cci.IPropertyDefinition)this); + visitor.Visit((IPropertyDefinition)this); } - Cci.IDefinition Cci.IReference.AsDefinition(EmitContext context) + IDefinition IReference.AsDefinition(EmitContext context) { CheckDefinitionInvariant(); return this; @@ -253,7 +251,7 @@ Cci.IDefinition Cci.IReference.AsDefinition(EmitContext context) #region INamedEntity Members - string Cci.INamedEntity.Name + string INamedEntity.Name { get { @@ -264,7 +262,7 @@ string Cci.INamedEntity.Name #endregion - private Cci.IMethodReference GetSynthesizedSealedAccessor(MethodKind targetMethodKind) + private IMethodReference GetSynthesizedSealedAccessor(MethodKind targetMethodKind) { SourcePropertySymbol sourceProperty = this as SourcePropertySymbol; if ((object)sourceProperty != null) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SourceAssemblySymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SourceAssemblySymbolAdapter.cs new file mode 100644 index 0000000000000..e93fc7cdcc63f --- /dev/null +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SourceAssemblySymbolAdapter.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal partial class SourceAssemblySymbol + { + internal IEnumerable GetCustomAttributesToEmit(ModuleCompilationState compilationState, bool emittingRefAssembly, bool emittingAssemblyAttributesInNetModule) + { + CheckDefinitionInvariant(); + + ImmutableArray userDefined = this.GetAttributes(); + ArrayBuilder synthesized = null; + this.AddSynthesizedAttributes(compilationState, ref synthesized); + + if (emittingRefAssembly && !HasReferenceAssemblyAttribute) + { + var referenceAssemblyAttribute = this.DeclaringCompilation + .TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_ReferenceAssemblyAttribute__ctor, isOptionalUse: true); + Symbol.AddSynthesizedAttribute(ref synthesized, referenceAssemblyAttribute); + } + + // Note that callers of this method (CCI and ReflectionEmitter) have to enumerate + // all items of the returned iterator, otherwise the synthesized ArrayBuilder may leak. + return GetCustomAttributesToEmit(userDefined, synthesized, isReturnType: false, emittingAssemblyAttributesInNetModule: emittingAssemblyAttributesInNetModule); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SymbolAdapter.cs index d1c10b1df43d0..1395f36244021 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/SymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/SymbolAdapter.cs @@ -66,6 +66,7 @@ internal virtual IEnumerable GetCustomAttributesToEmit(Modu internal IEnumerable GetCustomAttributesToEmit(ModuleCompilationState compilationState, bool emittingAssemblyAttributesInNetModule) { CheckDefinitionInvariant(); + Debug.Assert(this.Kind != SymbolKind.Assembly); ImmutableArray userDefined; ArrayBuilder synthesized = null; diff --git a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs index 7d15930176980..2e41a059aaf81 100644 --- a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs +++ b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs @@ -331,7 +331,7 @@ private EmbeddedType EmbedType( // Therefore, the following check can be as simple as: Debug.Assert(!IsFrozen, "Set of embedded types is frozen."); - var noPiaIndexer = new Cci.NoPiaReferenceIndexer(new EmitContext(ModuleBeingBuilt, syntaxNodeOpt, diagnostics)); + var noPiaIndexer = new Cci.NoPiaReferenceIndexer(new EmitContext(ModuleBeingBuilt, syntaxNodeOpt, diagnostics, metadataOnly: false, includePrivateMembers: true)); // Make sure we embed all types referenced by the type declaration: implemented interfaces, etc. noPiaIndexer.VisitTypeDefinitionNoMembers(embedded); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 8343bd6e0f6df..32797ef03e951 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1060,7 +1060,7 @@ internal enum ErrorCode ERR_InvalidOutputName = 2041, ERR_InvalidDebugInformationFormat = 2042, ERR_LegacyObjectIdSyntax = 2043, - ERR_SourceLinkRequiresPortablePdb = 2044, + ERR_SourceLinkRequiresPdb = 2044, ERR_CannotEmbedWithoutPdb = 2045, // unused 2046-2999 WRN_CLS_NoVarArgs = 3000, @@ -1104,7 +1104,7 @@ internal enum ErrorCode ERR_VarargsAsync = 4006, ERR_ByRefTypeAndAwait = 4007, ERR_BadAwaitArgVoidCall = 4008, - ERR_MainCantBeAsync = 4009, + ERR_NonTaskMainCantBeAsync = 4009, ERR_CantConvAsyncAnonFuncReturns = 4010, ERR_BadAwaiterPattern = 4011, ERR_BadSpecialByRefLocal = 4012, @@ -1479,6 +1479,16 @@ internal enum ErrorCode #region diagnostics for C# 7.1 + ERR_NoRefOutWhenRefOnly = 8308, + ERR_NoNetModuleOutputWhenRefOutOrRefOnly = 8309, + // Available = 8310, + ERR_BadDynamicMethodArgDefaultLiteral = 8311, + ERR_DefaultLiteralNotValid = 8312, + WRN_DefaultInSwitch = 8313, + ERR_PatternWrongGenericTypeInVersion = 8314, + + #endregion diagnostics for C# 7.1 + ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation = 8501, ERR_RuntimeDoesNotSupportDefaultInterfaceImplementationForMember = 8502, @@ -1487,11 +1497,5 @@ internal enum ErrorCode ERR_DefaultInterfaceImplementationModifier = 8503, ERR_ImplicitImplementationOfNonPublicInterfaceMember = 8504, - ERR_BadDynamicMethodArgDefaultLiteral = 9000, - ERR_DefaultLiteralNotValid = 9001, - WRN_DefaultInSwitch = 9002, - ERR_PatternWrongGenericTypeInVersion = 9003, - - #endregion diagnostics for C# 7.1 } } diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 10f8705bbe511..7ca1c2467593b 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -130,8 +130,9 @@ internal enum MessageID IDS_FeatureDefaultLiteral = MessageBase + 12718, IDS_FeatureInferredTupleNames = MessageBase + 12719, IDS_FeatureGenericPatternMatching = MessageBase + 12720, + IDS_FeatureAsyncMain = MessageBase + 12721, - IDS_DefaultInterfaceImplementation = MessageBase + 12721, + IDS_DefaultInterfaceImplementation = MessageBase + 12722, } // Message IDs may refer to strings that need to be localized. @@ -189,6 +190,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) switch (feature) { // C# 7.1 features. + case MessageID.IDS_FeatureAsyncMain: case MessageID.IDS_FeatureDefaultLiteral: case MessageID.IDS_FeatureInferredTupleNames: case MessageID.IDS_FeatureGenericPatternMatching: diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index b6e0e6fd5f15e..d624e3ee0bab3 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -4353,7 +4353,7 @@ public BoundPropertyGroup Update(ImmutableArray properties, Boun internal sealed partial class BoundCall : BoundExpression { - public BoundCall(SyntaxNode syntax, BoundExpression receiverOpt, MethodSymbol method, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool isDelegateCall, bool expanded, bool invokedAsExtensionMethod, ImmutableArray argsToParamsOpt, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) + public BoundCall(SyntaxNode syntax, BoundExpression receiverOpt, MethodSymbol method, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool isDelegateCall, bool expanded, bool invokedAsExtensionMethod, ImmutableArray argsToParamsOpt, LookupResultKind resultKind, Binder binderOpt, TypeSymbol type, bool hasErrors = false) : base(BoundKind.Call, syntax, type, hasErrors || receiverOpt.HasErrors() || arguments.HasErrors()) { @@ -4371,6 +4371,7 @@ public BoundCall(SyntaxNode syntax, BoundExpression receiverOpt, MethodSymbol me this.InvokedAsExtensionMethod = invokedAsExtensionMethod; this.ArgsToParamsOpt = argsToParamsOpt; this._ResultKind = resultKind; + this.BinderOpt = binderOpt; } @@ -4395,16 +4396,18 @@ public BoundCall(SyntaxNode syntax, BoundExpression receiverOpt, MethodSymbol me private readonly LookupResultKind _ResultKind; public override LookupResultKind ResultKind { get { return _ResultKind;} } + public Binder BinderOpt { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitCall(this); } - public BoundCall Update(BoundExpression receiverOpt, MethodSymbol method, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool isDelegateCall, bool expanded, bool invokedAsExtensionMethod, ImmutableArray argsToParamsOpt, LookupResultKind resultKind, TypeSymbol type) + public BoundCall Update(BoundExpression receiverOpt, MethodSymbol method, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool isDelegateCall, bool expanded, bool invokedAsExtensionMethod, ImmutableArray argsToParamsOpt, LookupResultKind resultKind, Binder binderOpt, TypeSymbol type) { - if (receiverOpt != this.ReceiverOpt || method != this.Method || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || isDelegateCall != this.IsDelegateCall || expanded != this.Expanded || invokedAsExtensionMethod != this.InvokedAsExtensionMethod || argsToParamsOpt != this.ArgsToParamsOpt || resultKind != this.ResultKind || type != this.Type) + if (receiverOpt != this.ReceiverOpt || method != this.Method || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || isDelegateCall != this.IsDelegateCall || expanded != this.Expanded || invokedAsExtensionMethod != this.InvokedAsExtensionMethod || argsToParamsOpt != this.ArgsToParamsOpt || resultKind != this.ResultKind || binderOpt != this.BinderOpt || type != this.Type) { - var result = new BoundCall(this.Syntax, receiverOpt, method, arguments, argumentNamesOpt, argumentRefKindsOpt, isDelegateCall, expanded, invokedAsExtensionMethod, argsToParamsOpt, resultKind, type, this.HasErrors); + var result = new BoundCall(this.Syntax, receiverOpt, method, arguments, argumentNamesOpt, argumentRefKindsOpt, isDelegateCall, expanded, invokedAsExtensionMethod, argsToParamsOpt, resultKind, binderOpt, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -4505,7 +4508,7 @@ public BoundAttribute Update(MethodSymbol constructor, ImmutableArray constructorsGroup, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, TypeSymbol type, bool hasErrors = false) + public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor, ImmutableArray constructorsGroup, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, Binder binderOpt, TypeSymbol type, bool hasErrors = false) : base(BoundKind.ObjectCreationExpression, syntax, type, hasErrors || arguments.HasErrors() || initializerExpressionOpt.HasErrors()) { @@ -4523,6 +4526,7 @@ public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor this.ArgsToParamsOpt = argsToParamsOpt; this.ConstantValueOpt = constantValueOpt; this.InitializerExpressionOpt = initializerExpressionOpt; + this.BinderOpt = binderOpt; } @@ -4544,16 +4548,18 @@ public BoundObjectCreationExpression(SyntaxNode syntax, MethodSymbol constructor public BoundExpression InitializerExpressionOpt { get; } + public Binder BinderOpt { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitObjectCreationExpression(this); } - public BoundObjectCreationExpression Update(MethodSymbol constructor, ImmutableArray constructorsGroup, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, TypeSymbol type) + public BoundObjectCreationExpression Update(MethodSymbol constructor, ImmutableArray constructorsGroup, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, ConstantValue constantValueOpt, BoundExpression initializerExpressionOpt, Binder binderOpt, TypeSymbol type) { - if (constructor != this.Constructor || constructorsGroup != this.ConstructorsGroup || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || expanded != this.Expanded || argsToParamsOpt != this.ArgsToParamsOpt || constantValueOpt != this.ConstantValueOpt || initializerExpressionOpt != this.InitializerExpressionOpt || type != this.Type) + if (constructor != this.Constructor || constructorsGroup != this.ConstructorsGroup || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || expanded != this.Expanded || argsToParamsOpt != this.ArgsToParamsOpt || constantValueOpt != this.ConstantValueOpt || initializerExpressionOpt != this.InitializerExpressionOpt || binderOpt != this.BinderOpt || type != this.Type) { - var result = new BoundObjectCreationExpression(this.Syntax, constructor, constructorsGroup, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, type, this.HasErrors); + var result = new BoundObjectCreationExpression(this.Syntax, constructor, constructorsGroup, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, constantValueOpt, initializerExpressionOpt, binderOpt, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -5418,7 +5424,7 @@ public BoundEventAccess Update(BoundExpression receiverOpt, EventSymbol eventSym internal sealed partial class BoundIndexerAccess : BoundExpression { - public BoundIndexerAccess(SyntaxNode syntax, BoundExpression receiverOpt, PropertySymbol indexer, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, TypeSymbol type, bool hasErrors = false) + public BoundIndexerAccess(SyntaxNode syntax, BoundExpression receiverOpt, PropertySymbol indexer, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, Binder binderOpt, bool useSetterForDefaultArgumentGeneration, TypeSymbol type, bool hasErrors = false) : base(BoundKind.IndexerAccess, syntax, type, hasErrors || receiverOpt.HasErrors() || arguments.HasErrors()) { @@ -5433,6 +5439,8 @@ public BoundIndexerAccess(SyntaxNode syntax, BoundExpression receiverOpt, Proper this.ArgumentRefKindsOpt = argumentRefKindsOpt; this.Expanded = expanded; this.ArgsToParamsOpt = argsToParamsOpt; + this.BinderOpt = binderOpt; + this.UseSetterForDefaultArgumentGeneration = useSetterForDefaultArgumentGeneration; } @@ -5450,16 +5458,20 @@ public BoundIndexerAccess(SyntaxNode syntax, BoundExpression receiverOpt, Proper public ImmutableArray ArgsToParamsOpt { get; } + public Binder BinderOpt { get; } + + public bool UseSetterForDefaultArgumentGeneration { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitIndexerAccess(this); } - public BoundIndexerAccess Update(BoundExpression receiverOpt, PropertySymbol indexer, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, TypeSymbol type) + public BoundIndexerAccess Update(BoundExpression receiverOpt, PropertySymbol indexer, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, bool expanded, ImmutableArray argsToParamsOpt, Binder binderOpt, bool useSetterForDefaultArgumentGeneration, TypeSymbol type) { - if (receiverOpt != this.ReceiverOpt || indexer != this.Indexer || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || expanded != this.Expanded || argsToParamsOpt != this.ArgsToParamsOpt || type != this.Type) + if (receiverOpt != this.ReceiverOpt || indexer != this.Indexer || arguments != this.Arguments || argumentNamesOpt != this.ArgumentNamesOpt || argumentRefKindsOpt != this.ArgumentRefKindsOpt || expanded != this.Expanded || argsToParamsOpt != this.ArgsToParamsOpt || binderOpt != this.BinderOpt || useSetterForDefaultArgumentGeneration != this.UseSetterForDefaultArgumentGeneration || type != this.Type) { - var result = new BoundIndexerAccess(this.Syntax, receiverOpt, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, type, this.HasErrors); + var result = new BoundIndexerAccess(this.Syntax, receiverOpt, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, binderOpt, useSetterForDefaultArgumentGeneration, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -8964,7 +8976,7 @@ public override BoundNode VisitCall(BoundCall node) BoundExpression receiverOpt = (BoundExpression)this.Visit(node.ReceiverOpt); ImmutableArray arguments = (ImmutableArray)this.VisitList(node.Arguments); TypeSymbol type = this.VisitType(node.Type); - return node.Update(receiverOpt, node.Method, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.IsDelegateCall, node.Expanded, node.InvokedAsExtensionMethod, node.ArgsToParamsOpt, node.ResultKind, type); + return node.Update(receiverOpt, node.Method, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.IsDelegateCall, node.Expanded, node.InvokedAsExtensionMethod, node.ArgsToParamsOpt, node.ResultKind, node.BinderOpt, type); } public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOperator node) { @@ -8985,7 +8997,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre ImmutableArray arguments = (ImmutableArray)this.VisitList(node.Arguments); BoundExpression initializerExpressionOpt = (BoundExpression)this.Visit(node.InitializerExpressionOpt); TypeSymbol type = this.VisitType(node.Type); - return node.Update(node.Constructor, node.ConstructorsGroup, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, initializerExpressionOpt, type); + return node.Update(node.Constructor, node.ConstructorsGroup, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, initializerExpressionOpt, node.BinderOpt, type); } public override BoundNode VisitTupleLiteral(BoundTupleLiteral node) { @@ -9124,7 +9136,7 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) BoundExpression receiverOpt = (BoundExpression)this.Visit(node.ReceiverOpt); ImmutableArray arguments = (ImmutableArray)this.VisitList(node.Arguments); TypeSymbol type = this.VisitType(node.Type); - return node.Update(receiverOpt, node.Indexer, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, type); + return node.Update(receiverOpt, node.Indexer, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.BinderOpt, node.UseSetterForDefaultArgumentGeneration, type); } public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node) { @@ -10313,6 +10325,7 @@ public override TreeDumperNode VisitCall(BoundCall node, object arg) new TreeDumperNode("invokedAsExtensionMethod", node.InvokedAsExtensionMethod, null), new TreeDumperNode("argsToParamsOpt", node.ArgsToParamsOpt, null), new TreeDumperNode("resultKind", node.ResultKind, null), + new TreeDumperNode("binderOpt", node.BinderOpt, null), new TreeDumperNode("type", node.Type, null) } ); @@ -10356,6 +10369,7 @@ public override TreeDumperNode VisitObjectCreationExpression(BoundObjectCreation new TreeDumperNode("argsToParamsOpt", node.ArgsToParamsOpt, null), new TreeDumperNode("constantValueOpt", node.ConstantValueOpt, null), new TreeDumperNode("initializerExpressionOpt", null, new TreeDumperNode[] { Visit(node.InitializerExpressionOpt, null) }), + new TreeDumperNode("binderOpt", node.BinderOpt, null), new TreeDumperNode("type", node.Type, null) } ); @@ -10602,6 +10616,8 @@ public override TreeDumperNode VisitIndexerAccess(BoundIndexerAccess node, objec new TreeDumperNode("argumentRefKindsOpt", node.ArgumentRefKindsOpt, null), new TreeDumperNode("expanded", node.Expanded, null), new TreeDumperNode("argsToParamsOpt", node.ArgsToParamsOpt, null), + new TreeDumperNode("binderOpt", node.BinderOpt, null), + new TreeDumperNode("useSetterForDefaultArgumentGeneration", node.UseSetterForDefaultArgumentGeneration, null), new TreeDumperNode("type", node.Type, null) } ); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AwaitExpressionSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AwaitExpressionSpiller.cs index 5b9c0b56eafd2..dc56bc050b763 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AwaitExpressionSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AwaitExpressionSpiller.cs @@ -1041,7 +1041,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre Debug.Assert(node.InitializerExpressionOpt == null); BoundSpillSequenceBuilder builder = null; var arguments = this.VisitExpressionList(ref builder, node.Arguments, node.ArgumentRefKindsOpt); - return UpdateExpression(builder, node.Update(node.Constructor, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, node.InitializerExpressionOpt, node.Type)); + return UpdateExpression(builder, node.Update(node.Constructor, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.ConstantValueOpt, node.InitializerExpressionOpt, node.BinderOpt, node.Type)); } public override BoundNode VisitPointerElementAccess(BoundPointerElementAccess node) diff --git a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DynamicAnalysisInjector.cs b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DynamicAnalysisInjector.cs index e1c54b14d9174..c2c73ba4c85fa 100644 --- a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DynamicAnalysisInjector.cs +++ b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DynamicAnalysisInjector.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.CodeAnalysis.CodeGen; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Roslyn.Utilities; using System.Collections.Immutable; using System.Diagnostics; -using System; +using System.Linq; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp { @@ -18,7 +19,8 @@ internal sealed class DynamicAnalysisInjector : CompoundInstrumenter { private readonly MethodSymbol _method; private readonly BoundStatement _methodBody; - private readonly MethodSymbol _createPayload; + private readonly MethodSymbol _createPayloadForMethodsSpanningSingleFile; + private readonly MethodSymbol _createPayloadForMethodsSpanningMultipleFiles; private readonly ArrayBuilder _spansBuilder; private ImmutableArray _dynamicAnalysisSpans = ImmutableArray.Empty; private readonly BoundStatement _methodEntryInstrumentation; @@ -28,7 +30,13 @@ internal sealed class DynamicAnalysisInjector : CompoundInstrumenter private readonly DebugDocumentProvider _debugDocumentProvider; private readonly SyntheticBoundNodeFactory _methodBodyFactory; - public static DynamicAnalysisInjector TryCreate(MethodSymbol method, BoundStatement methodBody, SyntheticBoundNodeFactory methodBodyFactory, DiagnosticBag diagnostics, DebugDocumentProvider debugDocumentProvider, Instrumenter previous) + public static DynamicAnalysisInjector TryCreate( + MethodSymbol method, + BoundStatement methodBody, + SyntheticBoundNodeFactory methodBodyFactory, + DiagnosticBag diagnostics, + DebugDocumentProvider debugDocumentProvider, + Instrumenter previous) { // Do not instrument implicitly-declared methods, except for constructors. // Instrument implicit constructors in order to instrument member initializers. @@ -43,28 +51,54 @@ public static DynamicAnalysisInjector TryCreate(MethodSymbol method, BoundStatem return null; } - MethodSymbol createPayload = GetCreatePayload(methodBodyFactory.Compilation, methodBody.Syntax, diagnostics); + MethodSymbol createPayloadForMethodsSpanningSingleFile = GetCreatePayloadOverload( + methodBodyFactory.Compilation, + WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, + methodBody.Syntax, + diagnostics); + + MethodSymbol createPayloadForMethodsSpanningMultipleFiles = GetCreatePayloadOverload( + methodBodyFactory.Compilation, + WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, + methodBody.Syntax, + diagnostics); // Do not instrument any methods if CreatePayload is not present. - if ((object)createPayload == null) + if ((object)createPayloadForMethodsSpanningSingleFile == null || (object)createPayloadForMethodsSpanningMultipleFiles == null) { return null; } // Do not instrument CreatePayload if it is part of the current compilation (which occurs only during testing). // CreatePayload will fail at run time with an infinite recursion if it is instrumented. - if (method.Equals(createPayload)) + if (method.Equals(createPayloadForMethodsSpanningSingleFile) || method.Equals(createPayloadForMethodsSpanningMultipleFiles)) { return null; } - return new DynamicAnalysisInjector(method, methodBody, methodBodyFactory, createPayload, diagnostics, debugDocumentProvider, previous); - } - - private DynamicAnalysisInjector(MethodSymbol method, BoundStatement methodBody, SyntheticBoundNodeFactory methodBodyFactory, MethodSymbol createPayload, DiagnosticBag diagnostics, DebugDocumentProvider debugDocumentProvider, Instrumenter previous) - : base(previous) - { - _createPayload = createPayload; + return new DynamicAnalysisInjector( + method, + methodBody, + methodBodyFactory, + createPayloadForMethodsSpanningSingleFile, + createPayloadForMethodsSpanningMultipleFiles, + diagnostics, + debugDocumentProvider, + previous); + } + + private DynamicAnalysisInjector( + MethodSymbol method, + BoundStatement methodBody, + SyntheticBoundNodeFactory methodBodyFactory, + MethodSymbol createPayloadForMethodsSpanningSingleFile, + MethodSymbol createPayloadForMethodsSpanningMultipleFiles, + DiagnosticBag diagnostics, + DebugDocumentProvider debugDocumentProvider, + Instrumenter previous) : base(previous) + { + _createPayloadForMethodsSpanningSingleFile = createPayloadForMethodsSpanningSingleFile; + _createPayloadForMethodsSpanningMultipleFiles = createPayloadForMethodsSpanningMultipleFiles; _method = method; _methodBody = methodBody; _spansBuilder = ArrayBuilder.GetInstance(); @@ -129,6 +163,78 @@ private static bool IsExcludedFromCodeCoverage(MethodSymbol method) return false; } + private static BoundExpressionStatement GetCreatePayloadStatement( + ImmutableArray dynamicAnalysisSpans, + SyntaxNode methodBodySyntax, + LocalSymbol methodPayload, + MethodSymbol createPayloadForMethodsSpanningSingleFile, + MethodSymbol createPayloadForMethodsSpanningMultipleFiles, + BoundExpression mvid, + BoundExpression methodToken, + BoundExpression payloadSlot, + SyntheticBoundNodeFactory methodBodyFactory, + DebugDocumentProvider debugDocumentProvider) + { + MethodSymbol createPayloadOverload; + BoundExpression fileIndexOrIndicesArgument; + + if (dynamicAnalysisSpans.IsEmpty) + { + createPayloadOverload = createPayloadForMethodsSpanningSingleFile; + + // For a compiler generated method that has no 'real' spans, we emit the index for + // the document corresponding to the syntax node that is associated with its bound node. + var document = GetSourceDocument(debugDocumentProvider, methodBodySyntax); + fileIndexOrIndicesArgument = methodBodyFactory.SourceDocumentIndex(document); + } + else + { + var documents = PooledHashSet.GetInstance(); + var fileIndices = ArrayBuilder.GetInstance(); + + foreach (var span in dynamicAnalysisSpans) + { + var document = span.Document; + if (documents.Add(document)) + { + fileIndices.Add(methodBodyFactory.SourceDocumentIndex(document)); + } + } + + documents.Free(); + + // At this point, we should have at least one document since we have already + // handled the case where method has no 'real' spans (and therefore no documents) above. + if (fileIndices.Count == 1) + { + createPayloadOverload = createPayloadForMethodsSpanningSingleFile; + fileIndexOrIndicesArgument = fileIndices.Single(); + } + else + { + createPayloadOverload = createPayloadForMethodsSpanningMultipleFiles; + + // Order of elements in fileIndices should be deterministic because these + // elements were added based on order of spans in dynamicAnalysisSpans above. + fileIndexOrIndicesArgument = methodBodyFactory.Array( + methodBodyFactory.SpecialType(SpecialType.System_Int32), fileIndices.ToImmutable()); + } + + fileIndices.Free(); + } + + return methodBodyFactory.Assignment( + methodBodyFactory.Local(methodPayload), + methodBodyFactory.Call( + null, + createPayloadOverload, + mvid, + methodToken, + fileIndexOrIndicesArgument, + payloadSlot, + methodBodyFactory.Literal(dynamicAnalysisSpans.Length))); + } + public override BoundStatement CreateBlockPrologue(BoundBlock original, out LocalSymbol synthesizedLocal) { BoundStatement previousPrologue = base.CreateBlockPrologue(original, out synthesizedLocal); @@ -138,22 +244,50 @@ public override BoundStatement CreateBlockPrologue(BoundBlock original, out Loca // In the future there will be multiple analysis kinds. const int analysisKind = 0; - ArrayTypeSymbol modulePayloadType = ArrayTypeSymbol.CreateCSharpArray(_methodBodyFactory.Compilation.Assembly, _payloadType); + ArrayTypeSymbol modulePayloadType = + ArrayTypeSymbol.CreateCSharpArray(_methodBodyFactory.Compilation.Assembly, _payloadType); // Synthesize the initialization of the instrumentation payload array, using concurrency-safe code: // // var payload = PID.PayloadRootField[methodIndex]; // if (payload == null) - // payload = Instrumentation.CreatePayload(mvid, methodIndex, fileIndex, ref PID.PayloadRootField[methodIndex], payloadLength); + // payload = Instrumentation.CreatePayload(mvid, methodIndex, fileIndexOrIndices, ref PID.PayloadRootField[methodIndex], payloadLength); + + BoundStatement payloadInitialization = + _methodBodyFactory.Assignment( + _methodBodyFactory.Local(_methodPayload), + _methodBodyFactory.ArrayAccess( + _methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType), + ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method)))); - BoundStatement payloadInitialization = _methodBodyFactory.Assignment(_methodBodyFactory.Local(_methodPayload), _methodBodyFactory.ArrayAccess(_methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType), ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method)))); BoundExpression mvid = _methodBodyFactory.ModuleVersionId(); BoundExpression methodToken = _methodBodyFactory.MethodDefIndex(_method); - BoundExpression fileIndex = _methodBodyFactory.SourceDocumentIndex(GetSourceDocument(_methodBody.Syntax)); - BoundExpression payloadSlot = _methodBodyFactory.ArrayAccess(_methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType), ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method))); - BoundStatement createPayloadCall = _methodBodyFactory.Assignment(_methodBodyFactory.Local(_methodPayload), _methodBodyFactory.Call(null, _createPayload, mvid, methodToken, fileIndex, payloadSlot, _methodBodyFactory.Literal(_dynamicAnalysisSpans.Length))); - BoundExpression payloadNullTest = _methodBodyFactory.Binary(BinaryOperatorKind.ObjectEqual, _methodBodyFactory.SpecialType(SpecialType.System_Boolean), _methodBodyFactory.Local(_methodPayload), _methodBodyFactory.Null(_payloadType)); + BoundExpression payloadSlot = + _methodBodyFactory.ArrayAccess( + _methodBodyFactory.InstrumentationPayloadRoot(analysisKind, modulePayloadType), + ImmutableArray.Create(_methodBodyFactory.MethodDefIndex(_method))); + + BoundStatement createPayloadCall = + GetCreatePayloadStatement( + _dynamicAnalysisSpans, + _methodBody.Syntax, + _methodPayload, + _createPayloadForMethodsSpanningSingleFile, + _createPayloadForMethodsSpanningMultipleFiles, + mvid, + methodToken, + payloadSlot, + _methodBodyFactory, + _debugDocumentProvider); + + BoundExpression payloadNullTest = + _methodBodyFactory.Binary( + BinaryOperatorKind.ObjectEqual, + _methodBodyFactory.SpecialType(SpecialType.System_Boolean), + _methodBodyFactory.Local(_methodPayload), + _methodBodyFactory.Null(_payloadType)); + BoundStatement payloadIf = _methodBodyFactory.If(payloadNullTest, createPayloadCall); Debug.Assert(synthesizedLocal == null); @@ -271,7 +405,7 @@ public override BoundStatement InstrumentReturnStatement(BoundReturnStatement or return AddDynamicAnalysis(original, rewritten); } - + private static bool ReturnsValueWithinExpressionBodiedConstruct(BoundReturnStatement returnStatement) { if (returnStatement.WasCompilerGenerated && @@ -340,12 +474,12 @@ private BoundStatement CollectDynamicAnalysis(BoundStatement original, BoundStat return statementFactory.StatementList(AddAnalysisPoint(SyntaxForSpan(original), statementFactory), rewritten); } - private Cci.DebugSourceDocument GetSourceDocument(SyntaxNode syntax) + private static Cci.DebugSourceDocument GetSourceDocument(DebugDocumentProvider debugDocumentProvider, SyntaxNode syntax) { - return GetSourceDocument(syntax, syntax.GetLocation().GetMappedLineSpan()); + return GetSourceDocument(debugDocumentProvider, syntax, syntax.GetLocation().GetMappedLineSpan()); } - private Cci.DebugSourceDocument GetSourceDocument(SyntaxNode syntax, FileLinePositionSpan span) + private static Cci.DebugSourceDocument GetSourceDocument(DebugDocumentProvider debugDocumentProvider, SyntaxNode syntax, FileLinePositionSpan span) { string path = span.Path; // If the path for the syntax node is empty, try the path for the entire syntax tree. @@ -354,7 +488,7 @@ private Cci.DebugSourceDocument GetSourceDocument(SyntaxNode syntax, FileLinePos path = syntax.SyntaxTree.FilePath; } - return _debugDocumentProvider.Invoke(path, basePath: ""); + return debugDocumentProvider.Invoke(path, basePath: ""); } private BoundStatement AddAnalysisPoint(SyntaxNode syntaxForSpan, Text.TextSpan alternateSpan, SyntheticBoundNodeFactory statementFactory) @@ -371,10 +505,19 @@ private BoundStatement AddAnalysisPoint(SyntaxNode syntaxForSpan, FileLinePositi { // Add an entry in the spans array. int spansIndex = _spansBuilder.Count; - _spansBuilder.Add(new SourceSpan(GetSourceDocument(syntaxForSpan, span), span.StartLinePosition.Line, span.StartLinePosition.Character, span.EndLinePosition.Line, span.EndLinePosition.Character)); + _spansBuilder.Add(new SourceSpan( + GetSourceDocument(_debugDocumentProvider, syntaxForSpan, span), + span.StartLinePosition.Line, + span.StartLinePosition.Character, + span.EndLinePosition.Line, + span.EndLinePosition.Character)); // Generate "_payload[pointIndex] = true". - BoundArrayAccess payloadCell = statementFactory.ArrayAccess(statementFactory.Local(_methodPayload), statementFactory.Literal(spansIndex)); + BoundArrayAccess payloadCell = + statementFactory.ArrayAccess( + statementFactory.Local(_methodPayload), + statementFactory.Literal(spansIndex)); + return statementFactory.Assignment(payloadCell, statementFactory.Literal(true)); } @@ -421,10 +564,10 @@ private static SyntaxNode SyntaxForSpan(BoundStatement statement) return syntaxForSpan; } - - private static MethodSymbol GetCreatePayload(CSharpCompilation compilation, SyntaxNode syntax, DiagnosticBag diagnostics) + + private static MethodSymbol GetCreatePayloadOverload(CSharpCompilation compilation, WellKnownMember overload, SyntaxNode syntax, DiagnosticBag diagnostics) { - return (MethodSymbol)Binder.GetWellKnownTypeMember(compilation, WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayload, diagnostics, syntax: syntax); + return (MethodSymbol)Binder.GetWellKnownTypeMember(compilation, overload, diagnostics, syntax: syntax); } private static SyntaxNode MethodDeclarationIfAvailable(SyntaxNode body) @@ -478,7 +621,7 @@ private static Text.TextSpan SkipAttributes(SyntaxNode syntax) return syntax.Span; } - + private static Text.TextSpan SkipAttributes(SyntaxNode syntax, SyntaxList attributes, SyntaxTokenList modifiers, SyntaxToken keyword, TypeSyntax type) { Text.TextSpan originalSpan = syntax.Span; diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs index 9830f0705377c..a592b8e77bb1d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs @@ -98,10 +98,10 @@ public MappedLocalFunction(SynthesizedLambdaMethod symbol, ClosureKind closureKi // or the "this" parameter when at the top level. Keys in this map are never constructed types. private readonly Dictionary _framePointers = new Dictionary(); - // True if the rewritten tree should include assignments of the - // original locals to the lifted proxies. This is only useful for the - // expression evaluator where the original locals are left as is. - private readonly bool _assignLocals; + // The set of original locals that should be assigned to proxies + // if lifted. This is useful for the expression evaluator where + // the original locals are left as is. + private readonly HashSet _assignLocals; // The current method or lambda being processed. private MethodSymbol _currentMethod; @@ -159,7 +159,7 @@ private LambdaRewriter( VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, DiagnosticBag diagnostics, - bool assignLocals) + HashSet assignLocals) : base(slotAllocatorOpt, compilationState, diagnostics) { Debug.Assert(analysis != null); @@ -207,7 +207,7 @@ protected override bool NeedsProxy(Symbol localOrParameter) /// Slot allocator. /// The caller's buffer into which we produce additional methods to be emitted by the caller /// Diagnostic bag for diagnostics - /// The rewritten tree should include assignments of the original locals to the lifted proxies + /// The set of original locals that should be assigned to proxies if lifted public static BoundStatement Rewrite( BoundStatement loweredBody, NamedTypeSymbol thisType, @@ -220,7 +220,7 @@ public static BoundStatement Rewrite( VariableSlotAllocator slotAllocatorOpt, TypeCompilationState compilationState, DiagnosticBag diagnostics, - bool assignLocals) + HashSet assignLocals) { Debug.Assert((object)thisType != null); Debug.Assert(((object)thisParameter == null) || (thisParameter.Type == thisType)); @@ -642,7 +642,7 @@ private BoundNode IntroduceFrame(BoundNode node, LambdaFrame frame, Func(SyntaxNode syntax, WellKnownMemb /// /// This function provides a false sense of security, it is likely going to surprise you when the requested member is missing. - /// Recommendation: Do not use, use instead! + /// Recommendation: Do not use, use instead! /// If used, a unit-test with a missing member is absolutely a must have. /// private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember) + { + return UnsafeGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics); + } + + /// + /// This function provides a false sense of security, it is likely going to surprise you when the requested member is missing. + /// Recommendation: Do not use, use instead! + /// If used, a unit-test with a missing member is absolutely a must have. + /// + private static MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, DiagnosticBag diagnostics) { MethodSymbol method; - if (TryGetSpecialTypeMethod(syntax, specialMember, out method)) + if (TryGetSpecialTypeMethod(syntax, specialMember, compilation, diagnostics, out method)) { return method; } @@ -329,15 +338,20 @@ private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember { MemberDescriptor descriptor = SpecialMembers.GetDescriptor(specialMember); SpecialType type = (SpecialType)descriptor.DeclaringTypeId; - TypeSymbol container = _compilation.Assembly.GetSpecialType(type); - TypeSymbol returnType = new ExtendedErrorTypeSymbol(compilation: _compilation, name: descriptor.Name, errorInfo: null, arity: descriptor.Arity); + TypeSymbol container = compilation.Assembly.GetSpecialType(type); + TypeSymbol returnType = new ExtendedErrorTypeSymbol(compilation: compilation, name: descriptor.Name, errorInfo: null, arity: descriptor.Arity); return new ErrorMethodSymbol(container, returnType, "Missing"); } } private bool TryGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, out MethodSymbol method) { - return Binder.TryGetSpecialTypeMember(_compilation, specialMember, syntax, _diagnostics, out method); + return TryGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics, out method); + } + + private static bool TryGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, DiagnosticBag diagnostics, out MethodSymbol method) + { + return Binder.TryGetSpecialTypeMember(compilation, specialMember, syntax, diagnostics, out method); } public override BoundNode VisitTypeOfOperator(BoundTypeOfOperator node) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AnonymousObjectCreation.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AnonymousObjectCreation.cs index 03c4f0f2cb53b..58039e05a3874 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AnonymousObjectCreation.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AnonymousObjectCreation.cs @@ -25,6 +25,7 @@ public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousO argsToParamsOpt: default(ImmutableArray), constantValueOpt: null, initializerExpressionOpt: null, + binderOpt: null, type: node.Type); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs index 79f3e1e825424..8ec81d79de3d8 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs @@ -1195,6 +1195,7 @@ private BoundExpression MakeLiftedBinaryOperatorConsequence( return new BoundObjectCreationExpression( syntax, UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), + null, unliftedOp); } @@ -1484,6 +1485,7 @@ private BoundExpression MakeNewNullableBoolean(SyntaxNode syntax, bool? value) return new BoundObjectCreationExpression( syntax, UnsafeGetNullableMethod(syntax, nullableBoolType, SpecialMember.System_Nullable_T__ctor), + null, MakeBooleanConstant(syntax, value.GetValueOrDefault())); } @@ -1719,10 +1721,20 @@ private BoundExpression LowerLiftedBooleanOperator( /// If used, a unit-test with a missing member is absolutely a must have. /// private MethodSymbol UnsafeGetNullableMethod(SyntaxNode syntax, TypeSymbol nullableType, SpecialMember member) + { + return UnsafeGetNullableMethod(syntax, nullableType, member, _compilation, _diagnostics); + } + + /// + /// This function provides a false sense of security, it is likely going to surprise you when the requested member is missing. + /// Recommendation: Do not use, use instead! + /// If used, a unit-test with a missing member is absolutely a must have. + /// + private static MethodSymbol UnsafeGetNullableMethod(SyntaxNode syntax, TypeSymbol nullableType, SpecialMember member, CSharpCompilation compilation, DiagnosticBag diagnostics) { var nullableType2 = nullableType as NamedTypeSymbol; Debug.Assert((object)nullableType2 != null); - return UnsafeGetSpecialTypeMethod(syntax, member).AsMember(nullableType2); + return UnsafeGetSpecialTypeMethod(syntax, member, compilation, diagnostics).AsMember(nullableType2); } private bool TryGetNullableMethod(SyntaxNode syntax, TypeSymbol nullableType, SpecialMember member, out MethodSymbol result) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 3ee96a545797e..38463182dbd62 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Semantics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -223,6 +224,7 @@ private BoundExpression MakeCall( invokedAsExtensionMethod: invokedAsExtensionMethod, argsToParamsOpt: default(ImmutableArray), resultKind: resultKind, + binderOpt: null, type: type); } else @@ -238,6 +240,7 @@ private BoundExpression MakeCall( node.InvokedAsExtensionMethod, default(ImmutableArray), node.ResultKind, + node.BinderOpt, node.Type); } @@ -385,39 +388,15 @@ private ImmutableArray MakeArguments( // // If none of those are the case then we can just take an early out. - // An applicable "vararg" method could not possibly be applicable in its expanded - // form, and cannot possibly have named arguments or used optional parameters, - // because the __arglist() argument has to be positional and in the last position. - - - if (methodOrIndexer.GetIsVararg()) - { - Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); - Debug.Assert(argsToParamsOpt.IsDefault); - Debug.Assert(!expanded); - temps = default(ImmutableArray); - return rewrittenArguments; - } - - var receiverNamedType = invokedAsExtensionMethod ? - ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : - methodOrIndexer.ContainingType; - - bool isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; - ArrayBuilder temporariesBuilder = ArrayBuilder.GetInstance(); rewrittenArguments = _factory.MakeTempsForDiscardArguments(rewrittenArguments, temporariesBuilder); - if (rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && - argsToParamsOpt.IsDefault && - !expanded && - !isComReceiver) + if (CanSkipRewriting(rewrittenArguments, methodOrIndexer, expanded, argsToParamsOpt, invokedAsExtensionMethod, out var isComReceiver)) { temps = temporariesBuilder.ToImmutableAndFree(); return rewrittenArguments; } - // We have: // * a list of arguments, already converted to their proper types, // in source code order. Some optional arguments might be missing. @@ -512,6 +491,81 @@ private ImmutableArray MakeArguments( return actualArguments.AsImmutableOrNull(); } + internal static ImmutableArray MakeArgumentsInEvaluationOrder( + Binder binder, + SyntaxNode syntax, + ImmutableArray arguments, + Symbol methodOrIndexer, + MethodSymbol optionalParametersMethod, + bool expanded, + ImmutableArray argsToParamsOpt, + bool invokedAsExtensionMethod) + { + Debug.Assert(binder != null); + + // Either the methodOrIndexer is a property, in which case the method used + // for optional parameters is an accessor of that property (or an overridden + // property), or the methodOrIndexer is used for optional parameters directly. + Debug.Assert(((methodOrIndexer.Kind == SymbolKind.Property) && optionalParametersMethod.IsAccessor()) || + (object)methodOrIndexer == optionalParametersMethod); + + // We need to do a fancy rewrite under the following circumstances: + // (1) a params array is being used; we need to generate the array. + // (2) there were optional parameters that had no corresponding arguments. + // + // If neither of those are the case then we can just take an early out. + + + if (CanSkipRewriting(arguments, methodOrIndexer, expanded, argsToParamsOpt, invokedAsExtensionMethod, out var isComReceiver)) + { + // In this case, the invocation is not in expanded form and there's no named argument provided. + // So we just return list of arguments as is. + return arguments.ZipAsArray(methodOrIndexer.GetParameters(), (a, p) => BoundCall.CreateArgumentOperation(ArgumentKind.Explicit, p, a)); + } + + return BuildArgumentsInEvaluationOrder(syntax, + methodOrIndexer, + optionalParametersMethod, + expanded, + argsToParamsOpt, + arguments, + binder); + } + + // temporariesBuilder will be null when factory is null. + private static bool CanSkipRewriting( + ImmutableArray rewrittenArguments, + Symbol methodOrIndexer, + bool expanded, + ImmutableArray argsToParamsOpt, + bool invokedAsExtensionMethod, + out bool isComReceiver) + { + // An applicable "vararg" method could not possibly be applicable in its expanded + // form, and cannot possibly have named arguments or used optional parameters, + // because the __arglist() argument has to be positional and in the last position. + + if (methodOrIndexer.GetIsVararg()) + { + Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); + Debug.Assert(argsToParamsOpt.IsDefault); + Debug.Assert(!expanded); + isComReceiver = false; + return true; + } + + var receiverNamedType = invokedAsExtensionMethod ? + ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : + methodOrIndexer.ContainingType; + + isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; + + return rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && + argsToParamsOpt.IsDefault && + !expanded && + !isComReceiver; + } + private static ImmutableArray GetRefKindsOrNull(ArrayBuilder refKinds) { foreach (var refKind in refKinds) @@ -544,44 +598,32 @@ private void BuildStoresToTemps( RefKind refKind = argumentRefKinds.RefKinds(a); Debug.Assert(arguments[p] == null); - if (expanded && p == arguments.Length - 1) + // Unfortunately, we violate the specification and allow: + // M(int q, params int[] x) ... M(x : X(), q : Q()); + // which means that we cannot bail out just because + // an argument of an expanded-form call corresponds to + // the parameter array. We need to make sure that the + // side effects of X() and Q() continue to happen in the right + // order here. + // + // Fortunately, we do disallow M(x : 123, x : 345, x : 456). + // + // Here's what we'll do. If all the remaining arguments + // correspond to elements in the parameter array then + // we can bail out here without creating any temporaries. + // The next step in the call rewriter will deal with gathering + // up the elements. + // + // However, if there are other elements after this one + // that do not correspond to elements in the parameter array + // then we need to create a temporary as usual. The step that + // produces the parameter array will need to deal with that + // eventuality. + + if (IsBeginningOfParamArray(p, a, expanded, arguments.Length, rewrittenArguments, argsToParamsOpt, out int paramArrayArgumentCount) + && a + paramArrayArgumentCount == rewrittenArguments.Length) { - // Unfortunately, we violate the specification and allow: - // M(int q, params int[] x) ... M(x : X(), q : Q()); - // which means that we cannot bail out just because - // an argument of an expanded-form call corresponds to - // the parameter array. We need to make sure that the - // side effects of X() and Q() continue to happen in the right - // order here. - // - // Fortunately, we do disallow M(x : 123, x : 345, x : 456). - // - // Here's what we'll do. If all the remaining arguments - // correspond to elements in the parameter array then - // we can bail out here without creating any temporaries. - // The next step in the call rewriter will deal with gathering - // up the elements. - // - // However, if there are other elements after this one - // that do not correspond to elements in the parameter array - // then we need to create a temporary as usual. The step that - // produces the parameter array will need to deal with that - // eventuality. - - bool canBail = true; - for (int remainingArgument = a + 1; remainingArgument < rewrittenArguments.Length; ++remainingArgument) - { - int remainingParameter = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[remainingArgument] : remainingArgument; - if (remainingParameter != arguments.Length - 1) - { - canBail = false; - break; - } - } - if (canBail) - { - return; - } + return; } if (IsSafeForReordering(argument, refKind)) @@ -599,6 +641,111 @@ private void BuildStoresToTemps( } } + // This fills in the arguments and parameters arrays in evaluation order. + private static ImmutableArray BuildArgumentsInEvaluationOrder( + SyntaxNode syntax, + Symbol methodOrIndexer, + MethodSymbol optionalParametersMethod, + bool expanded, + ImmutableArray argsToParamsOpt, + ImmutableArray arguments, + Binder binder) + { + ImmutableArray parameters = methodOrIndexer.GetParameters(); + + ArrayBuilder argumentsInEvaluationBuilder = ArrayBuilder.GetInstance(parameters.Length); + + PooledHashSet processedParameters = PooledHashSet.GetInstance(); + + // First, fill in all the explicitly provided arguments. + for (int a = 0; a < arguments.Length; ++a) + { + BoundExpression argument = arguments[a]; + + int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a; + var parameter = parameters[p]; + + Debug.Assert(!processedParameters.Contains(p)); + + processedParameters.Add(p); + + ArgumentKind kind = ArgumentKind.Explicit; + + if (IsBeginningOfParamArray(p, a, expanded, parameters.Length, arguments, argsToParamsOpt, out int paramArrayArgumentCount)) + { + int firstNonParamArrayArgumentIndex = a + paramArrayArgumentCount; + Debug.Assert(firstNonParamArrayArgumentIndex <= arguments.Length); + + kind = ArgumentKind.ParamArray; + ArrayBuilder paramArray = ArrayBuilder.GetInstance(paramArrayArgumentCount); + + for (int i = a; i < firstNonParamArrayArgumentIndex; ++i) + { + paramArray.Add(arguments[i]); + } + + // Set loop variable so the value for next iteration will be the index of the first non param-array argument after param-array argument(s). + a = firstNonParamArrayArgumentIndex - 1; + + argument = CreateParamArrayArgument(syntax, parameter.Type, paramArray.ToImmutableAndFree(), null, binder); + } + + argumentsInEvaluationBuilder.Add(BoundCall.CreateArgumentOperation(kind, parameter, argument)); + } + + // Collect parameters with missing arguments. + ArrayBuilder missingParametersBuilder = ArrayBuilder.GetInstance(parameters.Length); + for (int i = 0; i < parameters.Length; ++i) + { + if (!processedParameters.Contains(i)) + { + missingParametersBuilder.Add(parameters[i]); + } + } + + processedParameters.Free(); + + // Finally, append default value as arguments. + AppendMissingOptionalArguments(syntax, methodOrIndexer, optionalParametersMethod, expanded, binder, missingParametersBuilder, argumentsInEvaluationBuilder); + + missingParametersBuilder.Free(); + + return argumentsInEvaluationBuilder.ToImmutableAndFree(); + } + + /// + /// Returns true if the given argument is the begining of a list of param array arguments (could be empty), otherwise returns false. + /// When returns true, numberOfParamArrayArguments is set to the number of param array arguments. + /// + private static bool IsBeginningOfParamArray( + int parameterIndex, + int argumentIndex, + bool expanded, + int parameterCount, + ImmutableArray arguments, + ImmutableArray argsToParamsOpt, + out int numberOfParamArrayArguments) + { + numberOfParamArrayArguments = 0; + + if (expanded && parameterIndex == parameterCount - 1) + { + int remainingArgument = argumentIndex + 1; + for (; remainingArgument < arguments.Length; ++remainingArgument) + { + int remainingParameter = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[remainingArgument] : remainingArgument; + if (remainingParameter != parameterCount - 1) + { + break; + } + } + numberOfParamArrayArguments = remainingArgument - argumentIndex; + return true; + } + + return false; + } + private BoundExpression BuildParamsArray( SyntaxNode syntax, Symbol methodOrIndexer, @@ -659,21 +806,48 @@ private BoundExpression BuildParamsArray( invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray), resultKind: LookupResultKind.Viable, + binderOpt: null, type: arrayEmpty.ReturnType); } } } - var int32Type = methodOrIndexer.ContainingAssembly.GetPrimitiveType(Microsoft.Cci.PrimitiveTypeCode.Int32); + return CreateParamArrayArgument(syntax, paramArrayType, arrayArgs, this, null); + } + + private static BoundExpression CreateParamArrayArgument(SyntaxNode syntax, + TypeSymbol paramArrayType, + ImmutableArray arrayArgs, + LocalRewriter localRewriter, + Binder binder) + { + Debug.Assert(localRewriter == null ^ binder == null); + + TypeSymbol int32Type = (localRewriter != null ? localRewriter._compilation : binder.Compilation).GetSpecialType(SpecialType.System_Int32); + BoundExpression arraySize = MakeLiteral(syntax, ConstantValue.Create(arrayArgs.Length), int32Type, localRewriter); return new BoundArrayCreation( syntax, - ImmutableArray.Create( - MakeLiteral(syntax, ConstantValue.Create(arrayArgs.Length), int32Type)), + ImmutableArray.Create(arraySize), new BoundArrayInitialization(syntax, arrayArgs), paramArrayType); } + /// + /// To create literal expression for IOperation, set localRewriter to null. + /// + private static BoundExpression MakeLiteral(SyntaxNode syntax, ConstantValue constantValue, TypeSymbol type, LocalRewriter localRewriter) + { + if (localRewriter != null) + { + return localRewriter.MakeLiteral(syntax, constantValue, type); + } + else + { + return new BoundLiteral(syntax, constantValue, type, constantValue.IsBad); + } + } + private static void OptimizeTemporaries( BoundExpression[] arguments, ArrayBuilder refKinds, @@ -820,6 +994,56 @@ private void InsertMissingOptionalArguments(SyntaxNode syntax, } } + private static void AppendMissingOptionalArguments(SyntaxNode syntax, + Symbol methodOrIndexer, + MethodSymbol optionalParametersMethod, + bool expanded, + Binder binder, + ArrayBuilder missingParameters, + ArrayBuilder argumentsBuilder) + { + ImmutableArray parameters = methodOrIndexer.GetParameters(); + ImmutableArray parametersOfOptionalParametersMethod = optionalParametersMethod.Parameters; + + foreach (ParameterSymbol parameter in missingParameters) + { + BoundExpression argument; + ArgumentKind kind; + + // In case of indexer access, missing parameters are corresponding to the indexer symbol, we need to + // get default values based on actual accessor method parameter symbols (but still want to tie resulted IArgument + // to the indexer parameter.) + ParameterSymbol parameterOfOptionalParametersMethod = parametersOfOptionalParametersMethod[parameter.Ordinal]; + + if (expanded && parameterOfOptionalParametersMethod.Ordinal == parameters.Length - 1) + { + Debug.Assert(parameterOfOptionalParametersMethod.IsParams); + + // Create an empty array for omitted param array argument. + argument = CreateParamArrayArgument(syntax, parameterOfOptionalParametersMethod.Type, ImmutableArray.Empty, null, binder); + kind = ArgumentKind.ParamArray; + } + else + { + Debug.Assert(parameterOfOptionalParametersMethod.IsOptional); + + var unusedDiagnostics = DiagnosticBag.GetInstance(); + + argument = GetDefaultParameterValue(syntax, + parameterOfOptionalParametersMethod, + enableCallerInfo: ThreeState.Unknown, + localRewriter: null, + binder: binder, + diagnostics: unusedDiagnostics); + kind = ArgumentKind.DefaultValue; + + unusedDiagnostics.Free(); + } + + argumentsBuilder.Add(BoundCall.CreateArgumentOperation(kind, parameter, argument)); + } + } + private static SourceLocation GetCallerLocation(SyntaxNode syntax, ThreeState enableCallerInfo) { switch (enableCallerInfo) @@ -878,9 +1102,43 @@ private static SourceLocation GetCallerLocation(SyntaxNode syntax, ThreeState en /// we will provide caller information as a value of this parameter. /// This is done to match the native compiler behavior and user requests (see http://roslyn.codeplex.com/workitem/171). This behavior /// does not match the C# spec that currently requires to provide caller information only in explicit invocations and query expressions. - /// + /// private BoundExpression GetDefaultParameterValue(SyntaxNode syntax, ParameterSymbol parameter, ThreeState enableCallerInfo) { + return GetDefaultParameterValue(syntax, parameter, enableCallerInfo, this, null, this._diagnostics); + } + + /// + /// This helper is used by both LocalRewriter and IOperation. + /// - For lowering, 'localRewriter' must be passed in as an argument, and set 'binder' and 'diagnostics' to null. + /// - For deriving argument expression for IArgument operation, 'localRewriter' must be null, and 'compilation', 'diagnostics' + /// must be passed in, where 'callerMemberName' must not be null if 'parameter.IsCallerMemberName' is 'true'. + /// + private static BoundExpression GetDefaultParameterValue( + SyntaxNode syntax, + ParameterSymbol parameter, + ThreeState enableCallerInfo, + LocalRewriter localRewriter, + Binder binder, + DiagnosticBag diagnostics) + { + Debug.Assert(localRewriter == null ^ binder == null); + Debug.Assert(diagnostics != null); + + bool isLowering; + CSharpCompilation compilation; + + if (localRewriter != null) + { + isLowering = true; + compilation = localRewriter._compilation; + } + else + { + isLowering = false; + compilation = binder.Compilation; + } + // TODO: Ideally, the enableCallerInfo parameter would be of just bool type with only 'true' and 'false' values, and all callers // explicitly provided one of those values, so that we do not rely on shape of syntax nodes in the rewriter. There are not many immediate callers, // but often the immediate caller does not have the required information, so all possible call chains should be analyzed and possibly updated @@ -893,8 +1151,9 @@ private BoundExpression GetDefaultParameterValue(SyntaxNode syntax, ParameterSym SourceLocation callerSourceLocation; // For compatibility with the native compiler we treat all bad imported constant - // values as default(T). - if (defaultConstantValue != null && defaultConstantValue.IsBad) + // values as default(T). However, we don't do this for IOperation purpose, in which case + // we will expose the bad node. + if (defaultConstantValue != null && defaultConstantValue.IsBad && isLowering) { defaultConstantValue = ConstantValue.Null; } @@ -902,94 +1161,107 @@ private BoundExpression GetDefaultParameterValue(SyntaxNode syntax, ParameterSym if (parameter.IsCallerLineNumber && ((callerSourceLocation = GetCallerLocation(syntax, enableCallerInfo)) != null)) { int line = callerSourceLocation.SourceTree.GetDisplayLineNumber(callerSourceLocation.SourceSpan); - BoundExpression lineLiteral = MakeLiteral(syntax, ConstantValue.Create(line), _compilation.GetSpecialType(SpecialType.System_Int32)); + + BoundExpression lineLiteral = MakeLiteral(syntax, ConstantValue.Create(line), compilation.GetSpecialType(SpecialType.System_Int32), localRewriter); if (parameterType.IsNullableType()) { - defaultValue = MakeConversionNode(lineLiteral, parameterType.GetNullableUnderlyingType(), false); + TypeSymbol nullableType = parameterType.GetNullableUnderlyingType(); + defaultValue = MakeConversionNode(lineLiteral, nullableType, @checked: false); // wrap it in a nullable ctor. defaultValue = new BoundObjectCreationExpression( syntax, - UnsafeGetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor), + UnsafeGetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor, compilation, diagnostics), + null, defaultValue); } else { - defaultValue = MakeConversionNode(lineLiteral, parameterType, false); - } + defaultValue = MakeConversionNode(lineLiteral, parameterType, @checked: false); + } } else if (parameter.IsCallerFilePath && ((callerSourceLocation = GetCallerLocation(syntax, enableCallerInfo)) != null)) { - string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, _compilation.Options.SourceReferenceResolver); - BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(path), _compilation.GetSpecialType(SpecialType.System_String)); - defaultValue = MakeConversionNode(memberNameLiteral, parameterType, false); + string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, compilation.Options.SourceReferenceResolver); + BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(path), compilation.GetSpecialType(SpecialType.System_String), localRewriter); + defaultValue = MakeConversionNode(memberNameLiteral, parameterType, @checked: false); } else if (parameter.IsCallerMemberName && ((callerSourceLocation = GetCallerLocation(syntax, enableCallerInfo)) != null)) { string memberName; - switch (_factory.TopLevelMethod.MethodKind) + if (isLowering) { - case MethodKind.Constructor: - case MethodKind.StaticConstructor: - // See if the code is actually part of a field, field-like event or property initializer and return the name of the corresponding member. - var memberDecl = syntax.Ancestors().OfType().FirstOrDefault(); - - if (memberDecl != null) - { - BaseFieldDeclarationSyntax fieldDecl; - - if (memberDecl.Kind() == SyntaxKind.PropertyDeclaration) - { - var propDecl = (PropertyDeclarationSyntax)memberDecl; - EqualsValueClauseSyntax initializer = propDecl.Initializer; + MethodSymbol topLevelMethod = localRewriter._factory.TopLevelMethod; + switch (topLevelMethod.MethodKind) + { + case MethodKind.Constructor: + case MethodKind.StaticConstructor: + // See if the code is actually part of a field, field-like event or property initializer and return the name of the corresponding member. + var memberDecl = syntax.Ancestors().OfType().FirstOrDefault(); - if (initializer != null && initializer.Span.Contains(syntax.Span)) - { - memberName = propDecl.Identifier.ValueText; - break; - } - } - else if ((fieldDecl = memberDecl as BaseFieldDeclarationSyntax) != null) + if (memberDecl != null) { - memberName = null; + BaseFieldDeclarationSyntax fieldDecl; - foreach (VariableDeclaratorSyntax varDecl in fieldDecl.Declaration.Variables) + if (memberDecl.Kind() == SyntaxKind.PropertyDeclaration) { - EqualsValueClauseSyntax initializer = varDecl.Initializer; + var propDecl = (PropertyDeclarationSyntax)memberDecl; + EqualsValueClauseSyntax initializer = propDecl.Initializer; if (initializer != null && initializer.Span.Contains(syntax.Span)) { - memberName = varDecl.Identifier.ValueText; + memberName = propDecl.Identifier.ValueText; break; } } - - if (memberName != null) + else if ((fieldDecl = memberDecl as BaseFieldDeclarationSyntax) != null) { - break; + memberName = null; + + foreach (VariableDeclaratorSyntax varDecl in fieldDecl.Declaration.Variables) + { + EqualsValueClauseSyntax initializer = varDecl.Initializer; + + if (initializer != null && initializer.Span.Contains(syntax.Span)) + { + memberName = varDecl.Identifier.ValueText; + break; + } + } + + if (memberName != null) + { + break; + } } } - } - goto default; + goto default; - default: - memberName = _factory.TopLevelMethod.GetMemberCallerName(); - break; + default: + memberName = topLevelMethod.GetMemberCallerName(); + break; + } + } + else + { + memberName = binder.ContainingMember().GetMemberCallerName(); } - BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(memberName), _compilation.GetSpecialType(SpecialType.System_String)); - defaultValue = MakeConversionNode(memberNameLiteral, parameterType, false); + BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(memberName), compilation.GetSpecialType(SpecialType.System_String), localRewriter); + defaultValue = MakeConversionNode(memberNameLiteral, parameterType, @checked: false); } else if (defaultConstantValue == ConstantValue.NotAvailable) - { + { // There is no constant value given for the parameter in source/metadata. if (parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object) { // We have something like M([Optional] object x). We have special handling for such situations. - defaultValue = GetDefaultParameterSpecial(syntax, parameter); + defaultValue = isLowering + ? localRewriter.GetDefaultParameterSpecial(syntax, parameter) + : GetDefaultParameterSpecialForIOperation(syntax, parameter, compilation, diagnostics); } else { @@ -1008,8 +1280,8 @@ private BoundExpression GetDefaultParameterValue(SyntaxNode syntax, ParameterSym // We have something like M(double? x = 1.23), so replace the argument // with new double?(1.23). - TypeSymbol constantType = _compilation.GetSpecialType(defaultConstantValue.SpecialType); - defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType); + TypeSymbol constantType = compilation.GetSpecialType(defaultConstantValue.SpecialType); + defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType, localRewriter); // The parameter's underlying type might not match the constant type. For example, we might have // a default value of 5 (an integer) but a parameter type of decimal?. @@ -1019,27 +1291,52 @@ private BoundExpression GetDefaultParameterValue(SyntaxNode syntax, ParameterSym // Finally, wrap it in a nullable ctor. defaultValue = new BoundObjectCreationExpression( syntax, - UnsafeGetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor), - defaultValue); + UnsafeGetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor, compilation, diagnostics), + null, + defaultValue); } else if (defaultConstantValue.IsNull || defaultConstantValue.IsBad) { - defaultValue = MakeLiteral(syntax, defaultConstantValue, parameterType); + defaultValue = MakeLiteral(syntax, defaultConstantValue, parameterType, localRewriter); } else { // We have something like M(double x = 1.23), so replace the argument with 1.23. - TypeSymbol constantType = _compilation.GetSpecialType(defaultConstantValue.SpecialType); - defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType); - // The parameter type might not match the constant type. + TypeSymbol constantType = compilation.GetSpecialType(defaultConstantValue.SpecialType); + defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType, localRewriter); + // The parameter type might not match the constant type. defaultValue = MakeConversionNode(defaultValue, parameterType, @checked: false, acceptFailingConversion: true); } return defaultValue; - } + + BoundExpression MakeConversionNode(BoundExpression operand, TypeSymbol type, bool @checked, bool acceptFailingConversion = false) + { + if (isLowering) + { + return localRewriter.MakeConversionNode(operand, type, @checked, acceptFailingConversion); + } + else + { + return MakeConversionForIOperation(operand, type, syntax, compilation, diagnostics, @checked, acceptFailingConversion); + } + } + } private BoundExpression GetDefaultParameterSpecial(SyntaxNode syntax, ParameterSymbol parameter) + { + BoundExpression defaultValue = GetDefaultParameterSpecialNoConversion(syntax, parameter, this._compilation); + return MakeConversionNode(defaultValue, parameter.Type, @checked: false); + } + + private static BoundExpression GetDefaultParameterSpecialForIOperation(SyntaxNode syntax, ParameterSymbol parameter, CSharpCompilation compilation, DiagnosticBag diagnostics) + { + BoundExpression defaultValue = GetDefaultParameterSpecialNoConversion(syntax, parameter, compilation); + return MakeConversionForIOperation(defaultValue, parameter.Type, syntax, compilation, diagnostics, @checked: false); + } + + private static BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax, ParameterSymbol parameter, CSharpCompilation compilation) { // We have a call to a method M([Optional] object x) which omits the argument. The value we generate // for the argument depends on the presence or absence of other attributes. The rules are: @@ -1062,25 +1359,23 @@ private BoundExpression GetDefaultParameterSpecial(SyntaxNode syntax, ParameterS else if (parameter.IsIUnknownConstant) { // new UnknownWrapper(default(object)) - var methodSymbol = (MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_UnknownWrapper__ctor); + var methodSymbol = (MethodSymbol)compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_UnknownWrapper__ctor); var argument = new BoundDefaultExpression(syntax, parameter.Type); - defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, argument); + defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, null, argument); } else if (parameter.IsIDispatchConstant) { // new DispatchWrapper(default(object)) - var methodSymbol = (MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_DispatchWrapper__ctor); + var methodSymbol = (MethodSymbol)compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_DispatchWrapper__ctor); var argument = new BoundDefaultExpression(syntax, parameter.Type); - defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, argument); + defaultValue = new BoundObjectCreationExpression(syntax, methodSymbol, null, argument); } else { // Type.Missing - var fieldSymbol = (FieldSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Type__Missing); + var fieldSymbol = (FieldSymbol)compilation.GetWellKnownTypeMember(WellKnownMember.System_Type__Missing); defaultValue = new BoundFieldAccess(syntax, null, fieldSymbol, ConstantValue.NotAvailable); - } - - defaultValue = MakeConversionNode(defaultValue, parameter.Type, @checked: false); + } return defaultValue; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index 1e8f69755e997..274599cdc9533 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -328,6 +328,8 @@ private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAcce argumentRefKinds, false, default(ImmutableArray), + null, + indexerAccess.UseSetterForDefaultArgumentGeneration, indexerAccess.Type); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 3ff46b9d2e426..0b545e0c4daa7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -108,9 +108,11 @@ private BoundExpression MakeConversionNode( { Debug.Assert(oldNode == null || oldNode.Syntax == syntax); Debug.Assert((object)rewrittenType != null); - @checked = @checked && - (_inExpressionLambda && (explicitCastInCode || DistinctSpecialTypes(rewrittenOperand.Type, rewrittenType)) || - NeedsChecked(rewrittenOperand.Type, rewrittenType)); + + if (_inExpressionLambda) + { + @checked = @checked && NeedsCheckedConversionInExpressionTree(rewrittenOperand.Type, rewrittenType, explicitCastInCode); + } switch (conversion.Kind) { @@ -373,53 +375,8 @@ private BoundExpression MakeConversionNode( type: rewrittenType); } - private static bool DistinctSpecialTypes(TypeSymbol source, TypeSymbol target) - { - Debug.Assert((object)target != null); - - if ((object)source == null) - { - return false; - } - - SpecialType sourceST = source.StrippedType().EnumUnderlyingType().SpecialType; - SpecialType targetST = target.StrippedType().EnumUnderlyingType().SpecialType; - return sourceST != targetST; - } - - private const bool y = true; - private const bool n = false; - - private static readonly bool[,] s_needsChecked = - { // chri08u08i16u16i32u32i64u64 - /* chr */ - { n, y, y, y, n, n, n, n, n }, - /* i08 */ - { y, n, y, n, y, n, y, n, y }, - /* u08 */ - { n, y, n, n, n, n, n, n, n }, - /* i16 */ - { y, y, y, n, y, n, y, n, y }, - /* u16 */ - { n, y, y, y, n, n, n, n, n }, - /* i32 */ - { y, y, y, y, y, n, y, n, y }, - /* u32 */ - { y, y, y, y, y, y, n, n, n }, - /* i64 */ - { y, y, y, y, y, y, y, n, y }, - /* u64 */ - { y, y, y, y, y, y, y, y, n }, - /* dec */ - { n, n, n, n, n, n, n, n, n }, - /* r32 */ - { y, y, y, y, y, y, y, y, y }, - /* r64 */ - { y, y, y, y, y, y, y, y, y }, - }; - // Determine if the conversion can actually overflow at runtime. If not, no need to generate a checked instruction. - private static bool NeedsChecked(TypeSymbol source, TypeSymbol target) + private static bool NeedsCheckedConversionInExpressionTree(TypeSymbol source, TypeSymbol target, bool explicitCastInCode) { Debug.Assert((object)target != null); @@ -428,22 +385,20 @@ private static bool NeedsChecked(TypeSymbol source, TypeSymbol target) return false; } - if (source.IsDynamic()) - { - return true; - } + SpecialType GetUnderlyingSpecialType(TypeSymbol type) => + type.StrippedType().EnumUnderlyingType().SpecialType; - SpecialType sourceST = source.StrippedType().EnumUnderlyingType().SpecialType; - SpecialType targetST = target.StrippedType().EnumUnderlyingType().SpecialType; + bool IsInRange(SpecialType type, SpecialType low, SpecialType high) => + low <= type && type <= high; + + SpecialType sourceST = GetUnderlyingSpecialType(source); + SpecialType targetST = GetUnderlyingSpecialType(target); // integral to double or float is never checked, but float/double to integral // may be checked. - bool sourceIsNumeric = SpecialType.System_Char <= sourceST && sourceST <= SpecialType.System_Double; - bool targetIsNumeric = SpecialType.System_Char <= targetST && targetST <= SpecialType.System_UInt64; - return - sourceIsNumeric && target.IsPointerType() || - targetIsNumeric && source.IsPointerType() || - sourceIsNumeric && targetIsNumeric && s_needsChecked[sourceST - SpecialType.System_Char, targetST - SpecialType.System_Char]; + return (explicitCastInCode || sourceST != targetST) && + IsInRange(sourceST, SpecialType.System_Char, SpecialType.System_Double) && + IsInRange(targetST, SpecialType.System_Char, SpecialType.System_UInt64); } /// @@ -456,10 +411,26 @@ private static bool NeedsChecked(TypeSymbol source, TypeSymbol target) /// The error will be suppressed only for conversions from or . /// private BoundExpression MakeConversionNode(BoundExpression rewrittenOperand, TypeSymbol rewrittenType, bool @checked, bool acceptFailingConversion = false) + { + Conversion conversion = MakeConversion(rewrittenOperand, rewrittenType, _compilation, _diagnostics, acceptFailingConversion); + if (!conversion.IsValid) + { + return _factory.NullOrDefault(rewrittenType); + } + + return MakeConversionNode(rewrittenOperand.Syntax, rewrittenOperand, conversion, rewrittenType, @checked); + } + + private static Conversion MakeConversion( + BoundExpression rewrittenOperand, + TypeSymbol rewrittenType, + CSharpCompilation compilation, + DiagnosticBag diagnostics, + bool acceptFailingConversion) { HashSet useSiteDiagnostics = null; - Conversion conversion = _compilation.Conversions.ClassifyConversionFromType(rewrittenOperand.Type, rewrittenType, ref useSiteDiagnostics); - _diagnostics.Add(rewrittenOperand.Syntax, useSiteDiagnostics); + Conversion conversion = compilation.Conversions.ClassifyConversionFromType(rewrittenOperand.Type, rewrittenType, ref useSiteDiagnostics); + diagnostics.Add(rewrittenOperand.Syntax, useSiteDiagnostics); if (!conversion.IsValid) { @@ -468,17 +439,44 @@ private BoundExpression MakeConversionNode(BoundExpression rewrittenOperand, Typ rewrittenOperand.Type.SpecialType != SpecialType.System_DateTime) { // error CS0029: Cannot implicitly convert type '{0}' to '{1}' - _diagnostics.Add( + diagnostics.Add( ErrorCode.ERR_NoImplicitConv, rewrittenOperand.Syntax.Location, rewrittenOperand.Type, rewrittenType); } + } - return _factory.NullOrDefault(rewrittenType); + return conversion; + } + + private static BoundExpression MakeConversionForIOperation( + BoundExpression operand, + TypeSymbol type, + SyntaxNode syntax, + CSharpCompilation compilation, + DiagnosticBag diagnostics, + bool @checked, + bool acceptFailingConversion = false) + { + Conversion conversion = MakeConversion(operand, type, compilation, diagnostics, acceptFailingConversion); + + if (conversion.IsIdentity) + { + return operand; } - return MakeConversionNode(rewrittenOperand.Syntax, rewrittenOperand, conversion, rewrittenType, @checked); + // TODO: Consider doing constant folding for default parameter value conversion. + // https://github.com/dotnet/roslyn/issues/19591 + return new BoundConversion( + syntax, + operand, + conversion, + @checked: @checked, + explicitCastInCode: false, + constantValueOpt: default(ConstantValue), + type: type, + hasErrors: !conversion.IsValid); } /// @@ -745,7 +743,7 @@ private BoundExpression RewriteNullableConversion( BoundExpression rewrittenConversion = MakeConversionNode(syntax, rewrittenOperand, conversion.UnderlyingConversions[0], rewrittenType.GetNullableUnderlyingType(), @checked); MethodSymbol ctor = UnsafeGetNullableMethod(syntax, rewrittenType, SpecialMember.System_Nullable_T__ctor); - return new BoundObjectCreationExpression(syntax, ctor, rewrittenConversion); + return new BoundObjectCreationExpression(syntax, ctor, null, rewrittenConversion); } else { @@ -851,6 +849,7 @@ private BoundExpression RewriteFullyLiftedBuiltInConversion( BoundExpression consequence = new BoundObjectCreationExpression( syntax, UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), + null, MakeConversionNode( syntax, BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault), @@ -926,6 +925,7 @@ private BoundExpression OptimizeLiftedBuiltInConversion( return new BoundObjectCreationExpression( syntax, UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), + null, MakeConversionNode( syntax, nonNullValue, @@ -1031,7 +1031,7 @@ private BoundExpression MakeLiftedUserDefinedConversionConsequence(BoundCall cal { Debug.Assert(resultType.IsNullableType() && resultType.GetNullableUnderlyingType() == call.Method.ReturnType); MethodSymbol ctor = UnsafeGetNullableMethod(call.Syntax, resultType, SpecialMember.System_Nullable_T__ctor); - return new BoundObjectCreationExpression(call.Syntax, ctor, call); + return new BoundObjectCreationExpression(call.Syntax, ctor, null, call); } return call; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IfStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IfStatement.cs index 0c807fc1072f1..2305c3e63d718 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IfStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IfStatement.cs @@ -59,6 +59,7 @@ private static BoundStatement RewriteIfStatement( builder.Add(new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, afterif)); builder.Add(rewrittenConsequence); + builder.Add(new BoundSequencePoint(null, null)); builder.Add(new BoundLabelStatement(syntax, afterif)); var statements = builder.ToImmutableAndFree(); return new BoundStatementList(syntax, statements, hasErrors); @@ -86,6 +87,7 @@ private static BoundStatement RewriteIfStatement( builder.Add(new BoundGotoStatement(syntax, afterif)); builder.Add(new BoundLabelStatement(syntax, alt)); builder.Add(rewrittenAlternativeOpt); + builder.Add(new BoundSequencePoint(null, null)); builder.Add(new BoundLabelStatement(syntax, afterif)); return new BoundStatementList(syntax, builder.ToImmutableAndFree(), hasErrors); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index d2ae090a187da..888ca6650eb3b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -105,8 +105,8 @@ private BoundExpression MakeIndexerAccess( // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, type) : - new BoundIndexerAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, type); + oldNodeOpt.Update(rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, null, isLeftOfAssignment, type) : + new BoundIndexerAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, null, isLeftOfAssignment, type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Literal.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Literal.cs index 264d08a50d1e9..d8ba8ef9e36a3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Literal.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Literal.cs @@ -135,7 +135,7 @@ private BoundExpression MakeDecimalLiteral(SyntaxNode syntax, ConstantValue cons return new BoundObjectCreationExpression( syntax, ctor, arguments.ToImmutableAndFree(), default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), - constantValue, null, ctor.ContainingType); + constantValue, null, null, ctor.ContainingType); } private BoundExpression MakeDateTimeLiteral(SyntaxNode syntax, ConstantValue constantValue) @@ -154,7 +154,7 @@ private BoundExpression MakeDateTimeLiteral(SyntaxNode syntax, ConstantValue con return new BoundObjectCreationExpression( syntax, ctor, arguments.ToImmutableAndFree(), default(ImmutableArray), default(ImmutableArray), false, default(ImmutableArray), - ConstantValue.NotAvailable, null, ctor.ContainingType); + ConstantValue.NotAvailable, null, null, ctor.ContainingType); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs index a0bb56b340dde..38b8b5d4c20f3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs @@ -203,6 +203,7 @@ private BoundExpression MakeNewT(SyntaxNode syntax, TypeParameterSymbol typePara invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray), resultKind: LookupResultKind.Viable, + binderOpt: null, type: typeParameter); return createInstanceCall; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs index f54cbd709d3bb..c8f353222bbab 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs @@ -38,7 +38,7 @@ private class PatternSwitchLocalRewriter : DecisionTreeBuilder private ArrayBuilder _loweredDecisionTree = ArrayBuilder.GetInstance(); private PatternSwitchLocalRewriter(LocalRewriter localRewriter, BoundPatternSwitchStatement node) - : base(localRewriter._factory.CurrentMethod, localRewriter._factory.Compilation.Conversions) + : base(localRewriter._factory.CurrentMethod, node.Syntax, localRewriter._factory.Compilation.Conversions) { this._localRewriter = localRewriter; this._factory = localRewriter._factory; @@ -59,15 +59,9 @@ private BoundStatement MakeLoweredForm(BoundPatternSwitchStatement node) var expression = _localRewriter.VisitExpression(node.Expression); var result = ArrayBuilder.GetInstance(); - // if the expression is "too complex", we copy it to a temp. - LocalSymbol initialTemp = null; if (expression.ConstantValue == null) { - initialTemp = _factory.SynthesizedLocal(expression.Type, expression.Syntax); - result.Add(_factory.Assignment(_factory.Local(initialTemp), expression)); - expression = _factory.Local(initialTemp); - - // EnC: We need to insert a hidden sequence point to handle function remapping in case + // EnC: We need to insert a hidden sequence point to handle function remapping in case // the containing method is edited while methods invoked in the expression are being executed. if (!node.WasCompilerGenerated && _localRewriter.Instrument) { @@ -85,7 +79,6 @@ private BoundStatement MakeLoweredForm(BoundPatternSwitchStatement node) } // at this point the end of result is unreachable. - _declaredTemps.AddOptional(initialTemp); _declaredTemps.AddRange(node.InnerLocals); // output the sections of code @@ -148,7 +141,7 @@ private DecisionTree LowerToDecisionTree( BoundExpression loweredExpression, BoundPatternSwitchStatement node) { - var loweredDecisionTree = DecisionTree.Create(loweredExpression, loweredExpression.Type, _enclosingSymbol); + var loweredDecisionTree = CreateEmptyDecisionTree(loweredExpression); BoundPatternSwitchLabel defaultLabel = null; SyntaxNode defaultSection = null; foreach (var section in node.SwitchSections) @@ -223,15 +216,11 @@ private void LowerDecisionTree(BoundExpression expression, DecisionTree decision _loweredDecisionTree.Add(_factory.Assignment(decisionTree.Expression, convertedExpression)); } + // If the temp is not yet in the declared temp set, add it now if (_declaredTempSet.Add(decisionTree.Temp)) { _declaredTemps.Add(decisionTree.Temp); } - else - { - // we should only attempt to declare each temp once. - throw ExceptionUtilities.Unreachable; - } } switch (decisionTree.Kind) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ReturnStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ReturnStatement.cs index a611dffde4a0a..18b2442dbe4a7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ReturnStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ReturnStatement.cs @@ -28,10 +28,6 @@ public override BoundNode VisitReturnStatement(BoundReturnStatement node) { rewritten = _instrumenter.InstrumentReturnStatement(node, rewritten); } - else if (node.WasCompilerGenerated && _factory.CurrentMethod?.IsAsync == true) - { - rewritten = new BoundSequencePoint(null, rewritten); - } return rewritten; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TryStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TryStatement.cs index be1e749b9f2c2..c9b43e9e35a11 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TryStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TryStatement.cs @@ -79,6 +79,11 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node) return base.VisitCatchBlock(node); } + if (node.ExceptionFilterOpt.ConstantValue?.BooleanValue == false) + { + return null; + } + BoundExpression rewrittenExceptionSourceOpt = (BoundExpression)this.Visit(node.ExceptionSourceOpt); BoundExpression rewrittenFilter = (BoundExpression)this.Visit(node.ExceptionFilterOpt); BoundBlock rewrittenBody = (BoundBlock)this.Visit(node.Body); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleCreationExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleCreationExpression.cs index ad637c2120f2b..8dbab708121b9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleCreationExpression.cs @@ -60,7 +60,7 @@ private BoundExpression MakeTupleCreationExpression(SyntaxNode syntax, NamedType } MethodSymbol smallestConstructor = smallestCtor.AsMember(smallestType); - BoundObjectCreationExpression currentCreation = new BoundObjectCreationExpression(syntax, smallestConstructor, smallestCtorArguments); + BoundObjectCreationExpression currentCreation = new BoundObjectCreationExpression(syntax, smallestConstructor, null, smallestCtorArguments); if (underlyingTupleTypeChain.Count > 0) { @@ -83,7 +83,7 @@ private BoundExpression MakeTupleCreationExpression(SyntaxNode syntax, NamedType .Add(currentCreation); MethodSymbol constructor = tuple8Ctor.AsMember(underlyingTupleTypeChain.Pop()); - currentCreation = new BoundObjectCreationExpression(syntax, constructor, ctorArguments); + currentCreation = new BoundObjectCreationExpression(syntax, constructor, null, ctorArguments); } while (underlyingTupleTypeChain.Count > 0); } @@ -96,7 +96,8 @@ private BoundExpression MakeTupleCreationExpression(SyntaxNode syntax, NamedType currentCreation.Expanded, currentCreation.ArgsToParamsOpt, currentCreation.ConstantValue, - currentCreation.InitializerExpressionOpt, + currentCreation.InitializerExpressionOpt, + currentCreation.BinderOpt, type); return currentCreation; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index b3c64901f9383..6d19d2eb3b737 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -348,6 +348,7 @@ private BoundExpression GetLiftedUnaryOperatorConsequence(UnaryOperatorKind kind BoundExpression consequence = new BoundObjectCreationExpression( syntax, ctor, + null, unliftedOp); return consequence; } @@ -633,7 +634,7 @@ private BoundExpression MakeUserDefinedIncrementOperator(BoundIncrementOperator BoundExpression userDefinedCall = BoundCall.Synthesized(syntax, null, node.MethodOpt, call_GetValueOrDefault); // new S?(op_Increment(temp.GetValueOrDefault())) - BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, userDefinedCall); + BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, null, userDefinedCall); // default(S?) BoundExpression alternative = new BoundDefaultExpression(syntax, null, type); @@ -700,7 +701,7 @@ private BoundExpression MakeBuiltInIncrementOperator(BoundIncrementOperator node { binaryOperandType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(binaryOperandType); MethodSymbol ctor = UnsafeGetNullableMethod(node.Syntax, binaryOperandType, SpecialMember.System_Nullable_T__ctor); - boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, boundOne); + boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, null, boundOne); } // Now we construct the other operand to the binary addition. We start with just plain "x". @@ -800,7 +801,7 @@ private BoundExpression MakeLiftedDecimalIncDecOperator(SyntaxNode syntax, Binar // op_Inc(x.GetValueOrDefault()) BoundExpression methodCall = BoundCall.Synthesized(syntax, null, method, getValueCall); // new decimal?(op_Inc(x.GetValueOrDefault())) - BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, methodCall); + BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, null, methodCall); // default(decimal?) BoundExpression alternative = new BoundDefaultExpression(syntax, null, operand.Type); diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index 4b8e2d4becf59..4005e66d8c8fb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -255,6 +255,7 @@ public override BoundNode VisitCall(BoundCall node) node.InvokedAsExtensionMethod, node.ArgsToParamsOpt, node.ResultKind, + node.BinderOpt, rewrittenType); } @@ -471,6 +472,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre rewritten.ArgsToParamsOpt, rewritten.ConstantValueOpt, rewritten.InitializerExpressionOpt, + rewritten.BinderOpt, rewritten.Type); } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 3f4fe16167ae4..1529d4c6f2c74 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -6,12 +6,9 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Symbols; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -174,12 +171,13 @@ private void CheckCurrentType() { Debug.Assert((object)TopLevelMethod == null || TopLevelMethod.ContainingType == CurrentType); - // In EE scenarios, lambdas are considered to be contained by the user-defined methods, - // rather than the EE-defined methods for which we are generating bound nodes. This is - // because the containing symbols are used to determine the type of the "this" parameter, - // which we need to the user-defined types. + // In EE scenarios, lambdas and local functions are considered to be contained by the + // user-defined methods, rather than the EE-defined methods for which we are generating + // bound nodes. This is because the containing symbols are used to determine the type + // of the "this" parameter, which we need to be the user-defined types. Debug.Assert((object)CurrentMethod == null || CurrentMethod.MethodKind == MethodKind.AnonymousFunction || + CurrentMethod.MethodKind == MethodKind.LocalFunction || CurrentMethod.ContainingType == CurrentType); } } @@ -570,7 +568,7 @@ public BoundObjectCreationExpression New(NamedTypeSymbol type, params BoundExpre public BoundObjectCreationExpression New(MethodSymbol ctor, params BoundExpression[] args) { // TODO: add diagnostics for when things fall apart - return new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true }; + return new BoundObjectCreationExpression(Syntax, ctor, null, args) { WasCompilerGenerated = true }; } public BoundExpression InstanceCall(BoundExpression receiver, string name, BoundExpression arg) @@ -637,7 +635,7 @@ public BoundCall Call(BoundExpression receiver, MethodSymbol method, ImmutableAr return new BoundCall( Syntax, receiver, method, args, ImmutableArray.Empty, ImmutableArray.Empty, false, false, false, - default(ImmutableArray), LookupResultKind.Viable, method.ReturnType, + default(ImmutableArray), LookupResultKind.Viable, null, method.ReturnType, hasErrors:method.OriginalDefinition is ErrorMethodSymbol) { WasCompilerGenerated = true }; } @@ -648,7 +646,7 @@ public BoundCall Call(BoundExpression receiver, MethodSymbol method, ImmutableAr return new BoundCall( Syntax, receiver, method, args, ImmutableArray.Empty, refKinds, false, false, false, - ImmutableArray.Empty, LookupResultKind.Viable, method.ReturnType) + ImmutableArray.Empty, LookupResultKind.Viable, null, method.ReturnType) { WasCompilerGenerated = true }; } @@ -956,9 +954,14 @@ public BoundExpression ThrowExpression(BoundExpression thrown, TypeSymbol type) public BoundExpression Null(TypeSymbol type) { - BoundExpression nullLiteral = new BoundLiteral(Syntax, ConstantValue.Null, type) { WasCompilerGenerated = true }; + return Null(type, Syntax); + } + + public static BoundExpression Null(TypeSymbol type, SyntaxNode syntax) + { + BoundExpression nullLiteral = new BoundLiteral(syntax, ConstantValue.Null, type) { WasCompilerGenerated = true }; return type.IsPointerType() - ? BoundConversion.SynthesizedNonUserDefined(Syntax, nullLiteral, Conversion.NullToPointer, type) + ? BoundConversion.SynthesizedNonUserDefined(syntax, nullLiteral, Conversion.NullToPointer, type) : nullLiteral; } @@ -1182,7 +1185,12 @@ public BoundExpression Array(TypeSymbol elementType, BoundExpression length) internal BoundExpression Default(TypeSymbol type) { - return new BoundDefaultExpression(Syntax, type) { WasCompilerGenerated = true }; + return Default(type, Syntax); + } + + internal static BoundExpression Default(TypeSymbol type, SyntaxNode syntax) + { + return new BoundDefaultExpression(syntax, type) { WasCompilerGenerated = true }; } internal BoundStatement Try( @@ -1221,7 +1229,12 @@ internal BoundTryStatement Fault(BoundBlock tryBlock, BoundBlock faultBlock) internal BoundExpression NullOrDefault(TypeSymbol typeSymbol) { - return typeSymbol.IsValueType ? Default(typeSymbol) : Null(typeSymbol); + return NullOrDefault(typeSymbol, this.Syntax); + } + + internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode syntax) + { + return typeSymbol.IsValueType ? Default(typeSymbol, syntax) : Null(typeSymbol, syntax); } internal BoundExpression Not( diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index d4b9e5ef6aa03..c5f37ce315d96 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -84,7 +85,7 @@ public virtual bool IsGenericMethod internal virtual bool IsDirectlyExcludedFromCodeCoverage { get => false; } /// - /// Returns true if this method is an extension method. + /// Returns true if this method is an extension method. /// public abstract bool IsExtensionMethod { get; } @@ -114,7 +115,7 @@ public virtual bool IsGenericMethod internal abstract IEnumerable GetSecurityInformation(); /// - /// Marshalling information for return value (FieldMarshal in metadata). + /// Marshalling information for return value (FieldMarshal in metadata). /// internal abstract MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get; } @@ -133,7 +134,7 @@ public virtual bool IsGenericMethod /// /// Returns true if this method hides base methods by name. This cannot be specified directly /// in the C# language, but can be true for methods defined in other languages imported from - /// metadata. The equivalent of the "hidebyname" flag in metadata. + /// metadata. The equivalent of the "hidebyname" flag in metadata. /// public abstract bool HidesBaseMethodsByName { get; } @@ -184,7 +185,7 @@ public virtual bool IsCheckedBuiltin public abstract TypeSymbol ReturnType { get; } /// - /// Returns the type arguments that have been substituted for the type parameters. + /// Returns the type arguments that have been substituted for the type parameters. /// If nothing has been substituted for a given type parameter, /// then the type parameter itself is consider the type argument. /// @@ -275,13 +276,13 @@ internal virtual bool IsExplicitInterfaceImplementation /// Returns interface methods explicitly implemented by this method. /// /// - /// Methods imported from metadata can explicitly implement more than one method, + /// Methods imported from metadata can explicitly implement more than one method, /// that is why return type is ImmutableArray. /// public abstract ImmutableArray ExplicitInterfaceImplementations { get; } /// - /// Returns the list of custom modifiers, if any, associated with the return type. + /// Returns the list of custom modifiers, if any, associated with the return type. /// public abstract ImmutableArray ReturnTypeCustomModifiers { get; } @@ -309,7 +310,7 @@ public virtual ImmutableArray GetReturnTypeAttributes() /// returns the property that this method is the getter or setter for. /// If this method has MethodKind of MethodKind.EventAdd or MethodKind.EventRemove, /// returns the event that this method is the adder or remover for. - /// Note, the set of possible associated symbols might be expanded in the future to + /// Note, the set of possible associated symbols might be expanded in the future to /// reflect changes in the languages. /// public abstract Symbol AssociatedSymbol { get; } @@ -327,19 +328,19 @@ internal MethodSymbol GetLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt) while (m.IsOverride && !m.HidesBaseMethodsByName) { // We might not be able to access the overridden method. For example, - // + // // .assembly A // { // InternalsVisibleTo("B") // public class A { internal virtual void M() { } } // } - // + // // .assembly B // { // InternalsVisibleTo("C") // public class B : A { internal override void M() { } } // } - // + // // .assembly C // { // public class C : B { ... new B().M ... } // A.M is not accessible from here @@ -374,9 +375,9 @@ internal MethodSymbol GetConstructedLeastOverriddenMethod(NamedTypeSymbol access /// /// If this method overrides another method (because it both had the override modifier /// and there correctly was a method to override), returns the overridden method. - /// Note that if an overriding method D.M overrides C.M, which in turn overrides + /// Note that if an overriding method D.M overrides C.M, which in turn overrides /// virtual method A.M, the "overridden method" of D.M is C.M, not the original virtual - /// method A.M. Note also that constructed generic methods are not considered to + /// method A.M. Note also that constructed generic methods are not considered to /// override anything. /// public MethodSymbol OverriddenMethod @@ -587,7 +588,7 @@ internal bool IsSubmissionInitializer } /// - /// Determines whether this method is a candidate for a default assembly entry point + /// Determines whether this method is a candidate for a default assembly entry point /// (i.e. it is a static method called "Main"). /// internal bool IsEntryPointCandidate @@ -595,53 +596,6 @@ internal bool IsEntryPointCandidate get { return IsStatic && Name == WellKnownMemberNames.EntryPointMethodName; } } - /// - /// Checks if the method has an entry point compatible signature, i.e. - /// - the return type is either void or int - /// - has either no parameter or a single parameter of type string[] - /// - internal bool HasEntryPointSignature() - { - if (this.IsVararg) - { - return false; - } - - TypeSymbol returnType = ReturnType; - if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void) - { - return false; - } - - if (RefKind != RefKind.None) - { - return false; - } - - if (Parameters.Length == 0) - { - return true; - } - - if (Parameters.Length > 1) - { - return false; - } - - if (!ParameterRefKinds.IsDefault) - { - return false; - } - - var firstType = Parameters[0].Type; - if (firstType.TypeKind != TypeKind.Array) - { - return false; - } - - var array = (ArrayTypeSymbol)firstType; - return array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String; - } internal override TResult Accept(CSharpSymbolVisitor visitor, TArgument argument) { @@ -738,7 +692,7 @@ public virtual TypeSymbol ReceiverType } /// - /// If this method is a reduced extension method, returns a type inferred during reduction process for the type parameter. + /// If this method is a reduced extension method, returns a type inferred during reduction process for the type parameter. /// /// Type parameter of the corresponding method. /// Inferred type or Nothing if nothing was inferred. @@ -888,7 +842,7 @@ internal bool CalculateUseSiteDiagnostic(ref DiagnosticInfo result) return true; } - // If the member is in an assembly with unified references, + // If the member is in an assembly with unified references, // we check if its definition depends on a type from a unified reference. if (this.ContainingModule.HasUnifiedReferences) { @@ -908,7 +862,7 @@ internal bool CalculateUseSiteDiagnostic(ref DiagnosticInfo result) } /// - /// Return error code that has highest priority while calculating use site error for this symbol. + /// Return error code that has highest priority while calculating use site error for this symbol. /// protected override int HighestPriorityUseSiteError { @@ -949,7 +903,7 @@ internal virtual TypeSymbol IteratorElementType /// /// Generates bound block representing method's body for methods in lowered form and adds it to - /// a collection of method bodies of the current module. This method is supposed to only be + /// a collection of method bodies of the current module. This method is supposed to only be /// called for method symbols which return SynthesizesLoweredBoundBody == true. /// internal virtual void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) @@ -980,7 +934,7 @@ internal virtual bool SynthesizesLoweredBoundBody /// /// Syntax offset is a unique identifier for the local within the emitted method body. /// It's based on position of the local declarator. In single-part method bodies it's simply the distance - /// from the start of the method body syntax span. If a method body has multiple parts (such as a constructor + /// from the start of the method body syntax span. If a method body has multiple parts (such as a constructor /// comprising of code for member initializers and constructor initializer calls) the offset is calculated /// as if all source these parts were concatenated together and prepended to the constructor body. /// The resulting syntax offset is then negative for locals defined outside of the constructor body. @@ -1232,7 +1186,7 @@ public virtual bool IsTupleMethod /// /// If this is a method of a tuple type, return corresponding underlying method from the - /// tuple underlying type. Otherwise, null. + /// tuple underlying type. Otherwise, null. /// public virtual MethodSymbol TupleUnderlyingMethod { diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs index a651f6f1b3a9e..8924222d9878d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -300,5 +301,19 @@ public static bool IsGenericTaskReturningAsync(this MethodSymbol method, CSharpC return method.IsAsync && method.ReturnType.IsGenericTaskType(compilation); } + + internal static CSharpSyntaxNode ExtractReturnTypeSyntax(this MethodSymbol method) + { + method = method.PartialDefinitionPart ?? method; + foreach (var reference in method.DeclaringSyntaxReferences) + { + if (reference.GetSyntax() is MethodDeclarationSyntax methodDeclaration) + { + return methodDeclaration.ReturnType; + } + } + + return (CSharpSyntaxNode)CSharpSyntaxTree.Dummy.GetRoot(); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs index 4cc0f8a9bea23..48f86ecbd1c2e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -20,7 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// /// Represents an assembly built by compiler. /// - internal sealed class SourceAssemblySymbol : MetadataOrSourceAssemblySymbol, ISourceAssemblySymbolInternal, IAttributeTargetSymbol + internal sealed partial class SourceAssemblySymbol : MetadataOrSourceAssemblySymbol, ISourceAssemblySymbolInternal, IAttributeTargetSymbol { /// /// A Compilation the assembly is created for. @@ -730,7 +729,7 @@ private void ValidateIVTPublicKeys(DiagnosticBag diagnostics) /// Forces binding and decoding of attributes. /// This property shouldn't be accessed during binding as it can lead to attribute binding cycle. /// - private bool InternalsAreVisible + public bool InternalsAreVisible { get { @@ -1696,6 +1695,15 @@ private bool HasDebuggableAttribute } } + private bool HasReferenceAssemblyAttribute + { + get + { + CommonAssemblyWellKnownAttributeData assemblyData = this.GetSourceDecodedWellKnownAttributeData(); + return assemblyData != null && assemblyData.HasReferenceAssemblyAttribute; + } + } + internal override void AddSynthesizedAttributes(ModuleCompilationState compilationState, ref ArrayBuilder attributes) { base.AddSynthesizedAttributes(compilationState, ref attributes); @@ -2264,6 +2272,10 @@ private void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArguments().HasCompilationRelaxationsAttribute = true; } + else if (attribute.IsTargetAttribute(this, AttributeDescription.ReferenceAssemblyAttribute)) + { + arguments.GetOrCreateData().HasReferenceAssemblyAttribute = true; + } else if (attribute.IsTargetAttribute(this, AttributeDescription.RuntimeCompatibilityAttribute)) { bool wrapNonExceptionThrows = true; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 1d45cc848a95f..889173b25084b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -17,7 +19,6 @@ internal abstract class SynthesizedEntryPointSymbol : MethodSymbol internal const string FactoryName = ""; private readonly NamedTypeSymbol _containingType; - private readonly TypeSymbol _returnType; internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitializerMethod initializerMethod, DiagnosticBag diagnostics) { @@ -54,13 +55,11 @@ internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitial } } - private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType, TypeSymbol returnType) + private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType) { Debug.Assert((object)containingType != null); - Debug.Assert((object)returnType != null); _containingType = containingType; - _returnType = returnType; } internal override bool GenerateDebugInfo @@ -128,11 +127,6 @@ internal override RefKind RefKind get { return RefKind.None; } } - public override TypeSymbol ReturnType - { - get { return _returnType; } - } - public override ImmutableArray ReturnTypeCustomModifiers { get { return ImmutableArray.Empty; } @@ -160,7 +154,7 @@ public override int Arity public override bool ReturnsVoid { - get { return _returnType.SpecialType == SpecialType.System_Void; } + get { return ReturnType.SpecialType == SpecialType.System_Void; } } public override MethodKind MethodKind @@ -286,7 +280,7 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l throw ExceptionUtilities.Unreachable; } - private CSharpSyntaxNode GetSyntax() + private static CSharpSyntaxNode DummySyntax() { var syntaxTree = CSharpSyntaxTree.Dummy; return (CSharpSyntaxNode)syntaxTree.GetRoot(); @@ -325,34 +319,133 @@ private static BoundCall CreateParameterlessCall(CSharpSyntaxNode syntax, BoundE invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray), resultKind: LookupResultKind.Viable, + binderOpt: null, type: method.ReturnType) { WasCompilerGenerated = true }; } + /// A synthesized entrypoint that forwards all calls to an async Main Method + internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol + { + /// The user-defined asynchronous main method. + private readonly CSharpSyntaxNode _userMainReturnTypeSyntax; + + private readonly BoundExpression _getAwaiterGetResultCall; + + private readonly ImmutableArray _parameters; + + internal AsyncForwardEntryPoint(CSharpCompilation compilation, NamedTypeSymbol containingType, MethodSymbol userMain) : + base(containingType) + { + // There should be no way for a userMain to be passed in unless it already passed the + // parameter checks for determining entrypoint validity. + Debug.Assert(userMain.ParameterCount == 0 || userMain.ParameterCount == 1); + + _userMainReturnTypeSyntax = userMain.ExtractReturnTypeSyntax(); + var binder = compilation.GetBinder(_userMainReturnTypeSyntax); + _parameters = SynthesizedParameterSymbol.DeriveParameters(userMain, this); + + var arguments = Parameters.SelectAsArray((p, s) => (BoundExpression)new BoundParameter(s, p, p.Type), _userMainReturnTypeSyntax); + + // Main(args) or Main() + BoundCall userMainInvocation = new BoundCall( + syntax: _userMainReturnTypeSyntax, + receiverOpt: null, + method: userMain, + arguments: arguments, + argumentNamesOpt: default(ImmutableArray), + argumentRefKindsOpt: default(ImmutableArray), + isDelegateCall: false, + expanded: false, + invokedAsExtensionMethod: false, + argsToParamsOpt: default(ImmutableArray), + resultKind: LookupResultKind.Viable, + binderOpt: binder, + type: userMain.ReturnType) + { WasCompilerGenerated = true }; + + // The diagnostics that would be produced here will already have been captured and returned. + var droppedBag = DiagnosticBag.GetInstance(); + var success = binder.GetAwaitableExpressionInfo(userMainInvocation, out _, out _, out _, out _getAwaiterGetResultCall, _userMainReturnTypeSyntax, droppedBag); + droppedBag.Free(); + + Debug.Assert( + ReturnType.SpecialType == SpecialType.System_Void || + ReturnType.SpecialType == SpecialType.System_Int32); + } + + public override string Name => MainName; + + public override ImmutableArray Parameters => _parameters; + + public override TypeSymbol ReturnType => _getAwaiterGetResultCall.Type; + + internal override BoundBlock CreateBody() + { + var syntax = _userMainReturnTypeSyntax; + + if (ReturnsVoid) + { + return new BoundBlock( + syntax: syntax, + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + new BoundExpressionStatement( + syntax: syntax, + expression: _getAwaiterGetResultCall + ) + { WasCompilerGenerated = true }, + new BoundReturnStatement( + syntax: syntax, + refKind: RefKind.None, + expressionOpt: null + ) + { WasCompilerGenerated = true } + ) + ) + { WasCompilerGenerated = true }; + + } + else + { + return new BoundBlock( + syntax: syntax, + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + new BoundReturnStatement( + syntax: syntax, + refKind: RefKind.None, + expressionOpt: _getAwaiterGetResultCall + ) + ) + ) + { WasCompilerGenerated = true }; + } + } + } + private sealed class ScriptEntryPoint : SynthesizedEntryPointSymbol { private readonly MethodSymbol _getAwaiterMethod; private readonly MethodSymbol _getResultMethod; + private readonly TypeSymbol _returnType; internal ScriptEntryPoint(NamedTypeSymbol containingType, TypeSymbol returnType, MethodSymbol getAwaiterMethod, MethodSymbol getResultMethod) : - base(containingType, returnType) + base(containingType) { Debug.Assert(containingType.IsScriptClass); Debug.Assert(returnType.SpecialType == SpecialType.System_Void); _getAwaiterMethod = getAwaiterMethod; _getResultMethod = getResultMethod; + _returnType = returnType; } - public override string Name - { - get { return MainName; } - } + public override string Name => MainName; - public override ImmutableArray Parameters - { - get { return ImmutableArray.Empty; } - } + public override ImmutableArray Parameters => ImmutableArray.Empty; + + public override TypeSymbol ReturnType => _returnType; // private static void
() // { @@ -365,7 +458,7 @@ internal override BoundBlock CreateBody() Debug.Assert((object)_getAwaiterMethod != null); Debug.Assert((object)_getResultMethod != null); - var syntax = this.GetSyntax(); + var syntax = DummySyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 0); @@ -391,7 +484,8 @@ internal override BoundBlock CreateBody() scriptLocal, new BoundObjectCreationExpression( syntax, - ctor) + ctor, + null) { WasCompilerGenerated = true }, _containingType) { WasCompilerGenerated = true }) @@ -423,13 +517,15 @@ internal override BoundBlock CreateBody() private sealed class SubmissionEntryPoint : SynthesizedEntryPointSymbol { private readonly ImmutableArray _parameters; + private readonly TypeSymbol _returnType; internal SubmissionEntryPoint(NamedTypeSymbol containingType, TypeSymbol returnType, TypeSymbol submissionArrayType) : - base(containingType, returnType) + base(containingType) { Debug.Assert(containingType.IsSubmissionClass); Debug.Assert(returnType.SpecialType != SpecialType.System_Void); _parameters = ImmutableArray.Create(SynthesizedParameterSymbol.Create(this, submissionArrayType, 0, RefKind.None, "submissionArray")); + _returnType = returnType; } public override string Name @@ -442,6 +538,8 @@ public override ImmutableArray Parameters get { return _parameters; } } + public override TypeSymbol ReturnType => _returnType; + // private static T (object[] submissionArray) // { // var submission = new Submission#N(submissionArray); @@ -449,7 +547,7 @@ public override ImmutableArray Parameters // } internal override BoundBlock CreateBody() { - var syntax = this.GetSyntax(); + var syntax = DummySyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 1); @@ -481,6 +579,7 @@ internal override BoundBlock CreateBody() default(ImmutableArray), null, null, + null, _containingType) { WasCompilerGenerated = true }, _containingType) diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index b63e0120146af..a3d3b87784e2e 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -25,9 +25,8 @@ using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; - -using static Roslyn.Test.Utilities.SharedResourceHelpers; using static Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers; +using static Roslyn.Test.Utilities.SharedResourceHelpers; namespace Microsoft.CodeAnalysis.CSharp.CommandLine.UnitTests { @@ -37,7 +36,7 @@ public class CommandLineTests : CSharpTestBase private static readonly string s_defaultSdkDirectory = RuntimeEnvironment.GetRuntimeDirectory(); private static readonly string s_compilerVersion = typeof(Csc).GetTypeInfo().Assembly.GetCustomAttribute().Version; private static readonly string s_compilerShortCommitHash = - CommonCompiler.ExtractShortCommitHash(typeof(CSharpCompiler).GetTypeInfo().Assembly.GetCustomAttribute().Hash); + CommonCompiler.ExtractShortCommitHash(typeof(CSharpCompiler).GetTypeInfo().Assembly.GetCustomAttribute()?.Hash); private readonly string _baseDirectory = TempRoot.Root; @@ -1725,19 +1724,19 @@ public void SourceLink() Assert.Equal(Path.Combine(_baseDirectory, "s l.json"), parsedArgs.SourceLink); parsedArgs = DefaultParse(new[] { "/sourcelink:sl.json", "/debug:full", "a.cs" }, _baseDirectory); - parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPortablePdb)); + parsedArgs.Errors.Verify(); parsedArgs = DefaultParse(new[] { "/sourcelink:sl.json", "/debug:pdbonly", "a.cs" }, _baseDirectory); - parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPortablePdb)); + parsedArgs.Errors.Verify(); parsedArgs = DefaultParse(new[] { "/sourcelink:sl.json", "/debug-", "a.cs" }, _baseDirectory); - parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPortablePdb)); + parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPdb)); parsedArgs = DefaultParse(new[] { "/sourcelink:sl.json", "/debug+", "a.cs" }, _baseDirectory); - parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPortablePdb)); + parsedArgs.Errors.Verify(); parsedArgs = DefaultParse(new[] { "/sourcelink:sl.json", "a.cs" }, _baseDirectory); - parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPortablePdb)); + parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SourceLinkRequiresPdb)); } [Fact] @@ -1800,6 +1799,31 @@ public void SourceLink_EndToEnd_Portable() CleanupAllGeneratedFiles(src.Path); } + [Fact] + public void SourceLink_EndToEnd_Windows() + { + var dir = Temp.CreateDirectory(); + + var src = dir.CreateFile("a.cs"); + src.WriteAllText(@"class C { public static void Main() {} }"); + + var sl = dir.CreateFile("sl.json"); + byte[] slContent = Encoding.UTF8.GetBytes(@"{ ""documents"" : {} }"); + sl.WriteAllBytes(slContent); + + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var csc = new MockCSharpCompiler(null, dir.Path, new[] { "/nologo", "/debug:full", "/sourcelink:sl.json", "a.cs" }); + int exitCode = csc.Run(outWriter); + Assert.Equal(0, exitCode); + + var pdbStream = File.OpenRead(Path.Combine(dir.Path, "a.pdb")); + var actualData = PdbValidation.GetSourceLinkData(pdbStream); + AssertEx.Equal(slContent, actualData); + + // Clean up temp files + CleanupAllGeneratedFiles(src.Path); + } + [Fact] public void Embed() { @@ -2876,6 +2900,40 @@ public void ParseOut() // error CS2005: Missing file specification for '/out:' option Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/out:")); + parsedArgs = DefaultParse(new[] { @"/refout:", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify( + // error CS2005: Missing file specification for '/refout:' option + Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/refout:")); + + parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/refonly", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify( + // error CS8301: Do not use refout when using refonly. + Diagnostic(ErrorCode.ERR_NoRefOutWhenRefOnly).WithLocation(1, 1)); + + parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/link:b", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify(); + + parsedArgs = DefaultParse(new[] { "/refonly", "/link:b", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify(); + + parsedArgs = DefaultParse(new[] { "/refonly:incorrect", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify( + // error CS2007: Unrecognized option: '/refonly:incorrect' + Diagnostic(ErrorCode.ERR_BadSwitch).WithArguments("/refonly:incorrect").WithLocation(1, 1) + ); + + parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/target:module", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify( + // error CS8302: Cannot compile net modules when using /refout or /refonly. + Diagnostic(ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1) + ); + + parsedArgs = DefaultParse(new[] { @"/refonly", "/target:module", "a.cs" }, baseDirectory); + parsedArgs.Errors.Verify( + // error CS8302: Cannot compile net modules when using /refout or /refonly. + Diagnostic(ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1) + ); + // Dev11 reports CS2007: Unrecognized option: '/out' parsedArgs = DefaultParse(new[] { @"/out", "a.cs" }, baseDirectory); parsedArgs.Errors.Verify( @@ -7275,12 +7333,14 @@ public void IOFailure_DisposeXmlFile() Assert.Equal($"error CS0016: Could not write to output file '{xmlPath}' -- 'Fake IOException'{Environment.NewLine}", outWriter.ToString()); } - [Fact] - public void IOFailure_DisposeSourceLinkFile() + [Theory] + [InlineData("portable")] + [InlineData("full")] + public void IOFailure_DisposeSourceLinkFile(string format) { var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path); var sourceLinkPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.json"); - var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug:portable", $"/sourcelink:{sourceLinkPath}", srcPath }); + var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug:" + format, $"/sourcelink:{sourceLinkPath}", srcPath }); csc.FileOpen = (file, mode, access, share) => { if (file == sourceLinkPath) @@ -8970,6 +9030,7 @@ public void ParseSeparatedPaths_QuotedComma() paths); } + [CompilerTrait(CompilerFeature.Determinism)] [Fact] public void PathMapParser() { @@ -9003,6 +9064,82 @@ public void PathMapParser() Assert.Equal((int)ErrorCode.ERR_InvalidPathMap, parsedArgs.Errors[0].Code); } + [Fact] + [CompilerTrait(CompilerFeature.Determinism)] + public void PathMapPdbParser() + { + var dir = Path.Combine(_baseDirectory, "a"); + var parsedArgs = DefaultParse(new[] { $@"/pathmap:{dir}=b:\", "a.cs", @"/pdb:a\data.pdb", "/debug:full" }, _baseDirectory); + parsedArgs.Errors.Verify(); + Assert.Equal(Path.Combine(dir, @"data.pdb"), parsedArgs.PdbPath); + + // This value is calculate during Emit phases and should be null even in the face of a pathmap targeting it. + Assert.Null(parsedArgs.EmitOptions.PdbFilePath); + } + + [Fact] + [CompilerTrait(CompilerFeature.Determinism)] + public void PathMapPdbEmit() + { + void AssertPdbEmit(TempDirectory dir, string pdbPath, string pePdbPath, params string[] extraArgs) + { + var source = @"class Program { static void Main() { } }"; + var src = dir.CreateFile("a.cs").WriteAllText(source); + var defaultArgs = new[] { "/nologo", "a.cs", "/out:a.exe", "/debug:full", $"/pdb:{pdbPath}" }; + var isDeterministic = extraArgs.Contains("/deterministic"); + var args = defaultArgs.Concat(extraArgs).ToArray(); + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + + var csc = new MockCSharpCompiler(null, dir.Path, args); + int exitCode = csc.Run(outWriter); + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(dir.Path, "a.exe"); + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + using (var peStream = File.OpenRead(exePath)) + { + PdbValidation.ValidateDebugDirectory(peStream, null, pePdbPath, isDeterministic); + } + } + + // Case with no mappings + using (var dir = new DisposableDirectory(Temp)) + { + var pdbPath = Path.Combine(dir.Path, "a.pdb"); + AssertPdbEmit(dir, pdbPath, pdbPath); + } + + // Simple mapping + using (var dir = new DisposableDirectory(Temp)) + { + var pdbPath = Path.Combine(dir.Path, "a.pdb"); + AssertPdbEmit(dir, pdbPath, @"q:\a.pdb", $@"/pathmap:{dir.Path}=q:\"); + } + + // Simple mapping deterministic + using (var dir = new DisposableDirectory(Temp)) + { + var pdbPath = Path.Combine(dir.Path, "a.pdb"); + AssertPdbEmit(dir, pdbPath, @"q:\a.pdb", $@"/pathmap:{dir.Path}=q:\", "/deterministic"); + } + + // Partial mapping + using (var dir = new DisposableDirectory(Temp)) + { + dir.CreateDirectory("pdb"); + var pdbPath = Path.Combine(dir.Path, @"pdb\a.pdb"); + AssertPdbEmit(dir, pdbPath, @"q:\pdb\a.pdb", $@"/pathmap:{dir.Path}=q:\"); + } + + // Legacy feature flag + using (var dir = new DisposableDirectory(Temp)) + { + var pdbPath = Path.Combine(dir.Path, "a.pdb"); + AssertPdbEmit(dir, pdbPath, @"a.pdb", $@"/features:pdb-path-determinism"); + } + } + [WorkItem(7588, "https://github.com/dotnet/roslyn/issues/7588")] [Fact] public void Version() @@ -9024,6 +9161,197 @@ public void Version() } } + [Fact] + public void RefOut() + { + var dir = Temp.CreateDirectory(); + var refDir = dir.CreateDirectory("ref"); + + var src = dir.CreateFile("a.cs"); + src.WriteAllText(@" +public class C +{ + /// Main method + public static void Main() + { + System.Console.Write(""Hello""); + } + /// Private method + private static void PrivateMethod() + { + System.Console.Write(""Private""); + } +}"); + + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var csc = new MockCSharpCompiler(null, dir.Path, + new[] { "/nologo", "/out:a.exe", "/refout:ref/a.dll", "/doc:doc.xml", "/deterministic", "a.cs" }); + + int exitCode = csc.Run(outWriter); + Assert.Equal(0, exitCode); + + var exe = Path.Combine(dir.Path, "a.exe"); + Assert.True(File.Exists(exe)); + + MetadataReaderUtils.VerifyPEMetadata(exe, + new[] { "TypeDefinition:", "TypeDefinition:C" }, + new[] { "MethodDefinition:Void Main()", "MethodDefinition:Void PrivateMethod()", "MethodDefinition:Void .ctor()" }, + new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute" } + ); + + var doc = Path.Combine(dir.Path, "doc.xml"); + Assert.True(File.Exists(doc)); + + var content = File.ReadAllText(doc); + var expectedDoc = +@" + + + a + + + + Main method + + + Private method + + +"; + Assert.Equal(expectedDoc, content.Trim()); + + var output = ProcessUtilities.RunAndGetOutput(exe, startFolder: dir.Path); + Assert.Equal("Hello", output.Trim()); + + var refDll = Path.Combine(refDir.Path, "a.dll"); + Assert.True(File.Exists(refDll)); + + // The types and members that are included needs further refinement. + // See issue https://github.com/dotnet/roslyn/issues/17612 + MetadataReaderUtils.VerifyPEMetadata(refDll, + new[] { "TypeDefinition:", "TypeDefinition:C" }, + new[] { "MethodDefinition:Void Main()", "MethodDefinition:Void .ctor()" }, + new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute", "ReferenceAssemblyAttribute" } + ); + + // Clean up temp files + CleanupAllGeneratedFiles(dir.Path); + CleanupAllGeneratedFiles(refDir.Path); + } + + [Fact] + public void RefOutWithError() + { + var dir = Temp.CreateDirectory(); + dir.CreateDirectory("ref"); + + var src = dir.CreateFile("a.cs"); + src.WriteAllText(@"class C { public static void Main() { error(); } }"); + + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var csc = new MockCSharpCompiler(null, dir.Path, + new[] { "/nologo", "/out:a.dll", "/refout:ref/a.dll", "/deterministic", "a.cs" }); + int exitCode = csc.Run(outWriter); + Assert.Equal(1, exitCode); + + var dll = Path.Combine(dir.Path, "a.dll"); + Assert.False(File.Exists(dll)); + + var refDll = Path.Combine(dir.Path, Path.Combine("ref", "a.dll")); + Assert.False(File.Exists(refDll)); + + Assert.Equal("a.cs(1,39): error CS0103: The name 'error' does not exist in the current context", outWriter.ToString().Trim()); + + // Clean up temp files + CleanupAllGeneratedFiles(dir.Path); + } + + [Fact] + public void RefOnly() + { + var dir = Temp.CreateDirectory(); + + var src = dir.CreateFile("a.cs"); + src.WriteAllText(@" +using System; +class C +{ + /// Main method + public static void Main() + { + error(); // semantic error in method body + } + private event Action E1 + { + add { } + remove { } + } + private event Action E2; + + /// Private Class Field + private int field; + + /// Private Struct + private struct S + { + /// Private Struct Field + private int field; + } +}"); + + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var csc = new MockCSharpCompiler(null, dir.Path, + new[] { "/nologo", "/out:a.dll", "/refonly", "/debug", "/deterministic", "/doc:doc.xml", "a.cs" }); + int exitCode = csc.Run(outWriter); + Assert.Equal("", outWriter.ToString()); + Assert.Equal(0, exitCode); + + var refDll = Path.Combine(dir.Path, "a.dll"); + Assert.True(File.Exists(refDll)); + + // The types and members that are included needs further refinement. + // See issue https://github.com/dotnet/roslyn/issues/17612 + MetadataReaderUtils.VerifyPEMetadata(refDll, + new[] { "TypeDefinition:", "TypeDefinition:C", "TypeDefinition:S" }, + new[] { "MethodDefinition:Void Main()", "MethodDefinition:Void .ctor()" }, + new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute", "ReferenceAssemblyAttribute" } + ); + + var pdb = Path.Combine(dir.Path, "a.pdb"); + Assert.False(File.Exists(pdb)); + + var doc = Path.Combine(dir.Path, "doc.xml"); + Assert.True(File.Exists(doc)); + + var content = File.ReadAllText(doc); + var expectedDoc = +@" + + + a + + + + Main method + + + Private Class Field + + + Private Struct + + + Private Struct Field + + +"; + Assert.Equal(expectedDoc, content.Trim()); + + + // Clean up temp files + CleanupAllGeneratedFiles(dir.Path); + } + [Fact] public void CompilingCodeWithInvalidPreProcessorSymbolsShouldProvideDiagnostics() { diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Assembly.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Assembly.cs index a5ed0b9969357..5798e61b98e8d 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Assembly.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Assembly.cs @@ -954,7 +954,8 @@ private static void TestDuplicateAssemblyAttributesNotEmitted(AssemblySymbol ass // We should get only unique netmodule/assembly attributes here, duplicate ones should not be emitted. int expectedEmittedAttrsCount = expectedSrcAttrCount - expectedDuplicateAttrCount; - var allEmittedAttrs = assembly.GetCustomAttributesToEmit(new ModuleCompilationState(), emittingAssemblyAttributesInNetModule: false); + var allEmittedAttrs = ((SourceAssemblySymbol)assembly).GetCustomAttributesToEmit(new ModuleCompilationState(), + emittingRefAssembly: false, emittingAssemblyAttributesInNetModule: false); var emittedAttrs = allEmittedAttrs.Where(a => string.Equals(a.AttributeClass.Name, attrTypeName, StringComparison.Ordinal)).AsImmutable(); Assert.Equal(expectedEmittedAttrsCount, emittedAttrs.Length); @@ -1243,7 +1244,8 @@ static void Main(string[] args) { } expectedDuplicateAttrCount: 1, attrTypeName: "AssemblyTitleAttribute"); - var attrs = consoleappCompilation.Assembly.GetCustomAttributesToEmit(new ModuleCompilationState(), emittingAssemblyAttributesInNetModule: false); + var attrs = ((SourceAssemblySymbol)consoleappCompilation.Assembly).GetCustomAttributesToEmit(new ModuleCompilationState(), + emittingRefAssembly: false, emittingAssemblyAttributesInNetModule: false); foreach (var a in attrs) { switch (a.AttributeClass.Name) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/WellKnownAttributesTestBase.cs b/src/Compilers/CSharp/Test/Emit/Attributes/WellKnownAttributesTestBase.cs index 10d44a7cf2c09..332fe419da9ad 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/WellKnownAttributesTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/WellKnownAttributesTestBase.cs @@ -87,7 +87,7 @@ internal static void VerifyParamArrayAttribute(ParameterSymbol parameter, Source var paramArrayAttributeCtor = (MethodSymbol)emitModule.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ParamArrayAttribute__ctor); bool found = false; - var context = new EmitContext(emitModule, null, new DiagnosticBag()); + var context = new EmitContext(emitModule, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); foreach (Microsoft.Cci.ICustomAttribute attr in parameter.GetSynthesizedAttributes()) { diff --git a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj index 0d8951495e9e2..e4d06c7c65ba6 100644 --- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj +++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj @@ -57,6 +57,9 @@ + + Emit\MvidReader.cs + @@ -70,6 +73,7 @@ + @@ -142,14 +146,13 @@ - + - @@ -159,6 +162,7 @@ + diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs new file mode 100644 index 0000000000000..34bd9f103093a --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs @@ -0,0 +1,1462 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Xunit; +using System.Threading; +using Microsoft.CodeAnalysis.Test.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen +{ + [CompilerTrait(CompilerFeature.AsyncMain)] + public class CodeGenAsyncMainTests : EmitMetadataTestBase + { + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp71_WithMainType() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe.WithMainTypeName("Program"), parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12), + // (11,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(11, 22)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp7_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe.WithMainTypeName("Program"), parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12), + // (11,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(11, 22)); + } + + [Fact] + public void GetResultReturnsSomethingElse_CSharp7() + { + var source = @" +using System.Threading.Tasks; +using System; + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public double GetResult() { return 0.0; } + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + } + public class Task { + public Awaiter GetAwaiter() { + return new Awaiter(); + } + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (25,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 12), + // (25,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(25, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void GetResultReturnsSomethingElse_CSharp71() + { + var source = @" +using System.Threading.Tasks; +using System; + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public double GetResult() { return 0.0; } + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + } + public class Task { + public Awaiter GetAwaiter() { + return new Awaiter(); + } + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (25,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 12), + // (25,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(25, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskOfTGetAwaiterReturnsVoid_CSharp7() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), + // (2,1): hidden CS8019: Unnecessary using directive. + // using System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); + } + + [Fact] + public void TaskOfTGetAwaiterReturnsVoid_CSharp71() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), + // (2,1): hidden CS8019: Unnecessary using directive. + // using System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); + } + + [Fact] + public void TaskGetAwaiterReturnsVoid() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MissingMethodsOnTask() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task {} +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (10,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(10, 12), + // (10,12): error CS1061: 'Task' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'Task' could be found (are you missing a using directive or an assembly reference?) + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(10, 12), + // (10,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(10, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void EmitTaskOfIntReturningMainWithoutInt() + { + var corAssembly = @" +namespace System { + public class Object {} + public abstract class ValueType{} + public struct Int32{} +}"; + var corCompilation = CreateCompilation(corAssembly, options: TestOptions.DebugDll); + corCompilation.VerifyDiagnostics(); + + var taskAssembly = @" +namespace System.Threading.Tasks { + public class Task{} +}"; + var taskCompilation = CreateCompilationWithMscorlib45(taskAssembly, options: TestOptions.DebugDll); + taskCompilation.VerifyDiagnostics(); + + var source = @" +using System; +using System.Threading.Tasks; + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateCompilation(source, new[] { corCompilation.ToMetadataReference(), taskCompilation.ToMetadataReference() }, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1), + // (6,17): error CS0518: Predefined type 'System.Int32' is not defined or imported + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(6, 12), + // (6,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void EmitTaskReturningMainWithoutVoid() + { + var corAssembly = @" +namespace System { + public class Object {} +}"; + var corCompilation = CreateCompilation(corAssembly, options: TestOptions.DebugDll); + corCompilation.VerifyDiagnostics(); + + var taskAssembly = @" +namespace System.Threading.Tasks { + public class Task{} +}"; + var taskCompilation = CreateCompilationWithMscorlib45(taskAssembly, options: TestOptions.DebugDll); + taskCompilation.VerifyDiagnostics(); + + var source = @" +using System; +using System.Threading.Tasks; + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateCompilation(source, new[] { corCompilation.ToMetadataReference(), taskCompilation.ToMetadataReference() }, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1), + // (6,12): error CS1061: 'Task' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'Task' could be found (are you missing a using directive or an assembly reference?) + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(6, 12), + // (6,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(6, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void AsyncEmitMainTest() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main() { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncMainTestCodegenWithErrors() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main() { + Console.WriteLine(""hello""); + await Task.Factory.StartNew(() => 5); + Console.WriteLine(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + c.VerifyEmitDiagnostics( + // (6,28): error CS0161: 'Program.Main()': not all code paths return a value + // static async Task Main() { + Diagnostic(ErrorCode.ERR_ReturnExpected, "Main").WithArguments("Program.Main()").WithLocation(6, 28)); + } + + + [Fact] + public void AsyncEmitMainOfIntTest_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10); + } + + [Fact] + public void AsyncEmitMainOfIntTest_ParamsStringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(params string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10); + } + + [Fact] + public void AsyncEmitMainTest_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncEmitMainTestCodegenWithErrors_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.WriteLine(""hello""); + await Task.Factory.StartNew(() => 5); + Console.WriteLine(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + c.VerifyEmitDiagnostics( + // (6,28): error CS0161: 'Program.Main()': not all code paths return a value + // static async Task Main() { + Diagnostic(ErrorCode.ERR_ReturnExpected, "Main").WithArguments("Program.Main(string[])").WithLocation(6, 28)); + } + + [Fact] + public void AsyncEmitMainOfIntTest_StringArgs_WithArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(args[0]); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10, args: new string[] { "async main" }); + } + + [Fact] + public void AsyncEmitMainTest_StringArgs_WithArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(args[0]); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0, args: new string[] { "async main" }); + } + + [Fact] + public void MainCanBeAsyncWithArgs() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + + CompileAndVerify(compilation, expectedReturnCode: 0); + } + + [Fact] + public void MainCanReturnTaskWithArgs_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + + CompileAndVerify(compilation, expectedReturnCode: 0); + } + + + [Fact] + public void MainCantBeAsyncWithRefTask() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static ref Task Main(string[] args) + { + throw new System.Exception(); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,21): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // static ref Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(6, 21), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncWithArgs_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncWithArgs_CSharp7_NoAwait() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTask() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + [Fact] + public void MainCanReturnTask_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncVoid_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,23): error CS9003: Async Main methods must return Task or Task + // async static void Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncInt() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static int Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(6, 22), + // (6,22): error CS4009: A void or int returning entry point cannot be async + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.Null(entry); + } + + [Fact] + public void MainCantBeAsyncInt_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static int Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(6, 22), + // (6,22): error CS9003: Async Main methods must return Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_WithArgs() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_WithArgs_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_WithArgs_Csharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_WithArgs_Csharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_CSharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOverFloats() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + await Task.Factory.StartNew(() => { }); + return 0; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,30): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static Task Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main()").WithLocation(6, 30), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndGeneric() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( + // (6,23): warning CS0402: 'A.Main()': an entry point cannot be generic or in a generic type + // async static void Main() + Diagnostic(ErrorCode.WRN_MainCantBeGeneric, "Main").WithArguments("A.Main()").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndBadSig() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(bool truth) + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point + // async static void Main(bool truth) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndGeneric_AndBadSig() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(bool truth) + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point + // async static void Main(bool truth) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskMainAndNonTaskMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskMainAndNonTaskMain_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncVoidMain_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + } + async static int Main() + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + return 1; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (11,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(11, 22), + // (11,22): error CS4009: A void or int returning entry point cannot be async + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(11, 22), + // (6,23): error CS4009: A void or int returning entry point cannot be async + // async static void Main(string[] args) + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main(string[])").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void AsyncVoidMain_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static async void Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( + // (6,23): error CS4009: A void or int returning entry point cannot be async + // static async void Main(string[] args) + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main(string[])").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskMainAndNonTaskMain_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe.WithMainTypeName("A")).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskIntAndTaskFloat_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + System.Console.WriteLine(""Task""); + return 0; + } + + async static Task Main(string[] args) + { + System.Console.WriteLine(""Task""); + return 0.0F; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseDebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( + // (6,28): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async static Task Main() + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(6, 28), + // (12,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(12, 30), + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // (12,30): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // async static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(12, 30), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskIntAndTaskFloat_CSharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + System.Console.WriteLine(""Task""); + return Task.FromResult(0); + } + + static Task Main(string[] args) + { + System.Console.WriteLine(""Task""); + return Task.FromResult(0.0F); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseDebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( + // (6,12): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // (12,24): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(12, 24), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskOfFloatMainAndNonTaskMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + return 0.0f; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (10,30): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // async static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(11, 30)); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskOfFloatMainAndNonTaskMain_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + return 0.0f; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe.WithMainTypeName("A")).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() { + System.Console.Write(""GetResult called""); + } + } + public class Task {} +} + +public static class MyExtensions { + public static Awaiter GetAwaiter(this Task task) { + System.Console.Write(""GetAwaiter called | ""); + return new Awaiter(); + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (26,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(26, 43), + // (33,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(33, 12), + // (34,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(34, 20)); + CompileAndVerify(sourceCompilation, expectedOutput: "GetAwaiter called | GetResult called"); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods_Obsolete() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() { + System.Console.Write(""GetResult called""); + } + } + public class Task {} +} + +public static class MyExtensions { + [System.Obsolete(""test"")] + public static Awaiter GetAwaiter(this Task task) { + System.Console.Write(""GetAwaiter called | ""); + return new Awaiter(); + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (34,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(34, 12), + // (27,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(27, 43), + // (35,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(35, 20), + // (34,12): warning CS0618: 'MyExtensions.GetAwaiter(Task)' is obsolete: 'test' + // static Task Main() { + Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "Task").WithArguments("MyExtensions.GetAwaiter(System.Threading.Tasks.Task)", "test").WithLocation(34, 12)); + + CompileAndVerify(sourceCompilation, expectedOutput: "GetAwaiter called | GetResult called"); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods_ObsoleteFailing() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() {} + } + public class Task {} +} + +public static class MyExtensions { + [System.Obsolete(""test"", true)] + public static Awaiter GetAwaiter(this Task task) { + return null; + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (25,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 43), + // (31,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(31, 12), + // (32,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(32, 20), + // (31,12): warning CS0618: 'MyExtensions.GetAwaiter(Task)' is obsolete: 'test' + // static Task Main() { + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Task").WithArguments("MyExtensions.GetAwaiter(System.Threading.Tasks.Task)", "test").WithLocation(31, 12)); + } + } +} diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenCheckedTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenCheckedTests.cs index b6ad41829bbd3..1886c8c03e445 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenCheckedTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenCheckedTests.cs @@ -2367,5 +2367,416 @@ .locals init (int V_0) } "); } + + [Fact] + public void CheckedConversionsInExpressionTrees_Implicit() + { + // char + CheckedConversionInExpressionTree_Implicit("char", "char", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("char", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("char", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("char", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("char", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("char", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("char", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("char", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("char", "double", ConvertMethod.Convert); + + // sbyte + CheckedConversionInExpressionTree_Implicit("sbyte", "sbyte", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("sbyte", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("sbyte", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("sbyte", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("sbyte", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("sbyte", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("sbyte", "double", ConvertMethod.Convert); + + // byte + CheckedConversionInExpressionTree_Implicit("byte", "byte", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("byte", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("byte", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("byte", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("byte", "double", ConvertMethod.Convert); + + // short + CheckedConversionInExpressionTree_Implicit("short", "short", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("short", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("short", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("short", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("short", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("short", "double", ConvertMethod.Convert); + + // ushort + CheckedConversionInExpressionTree_Implicit("ushort", "ushort", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("ushort", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("ushort", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("ushort", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("ushort", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("ushort", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("ushort", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("ushort", "double", ConvertMethod.Convert); + + // int + CheckedConversionInExpressionTree_Implicit("int", "int", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("int", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("int", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("int", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("int", "double", ConvertMethod.Convert); + + // uint + CheckedConversionInExpressionTree_Implicit("uint", "uint", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("uint", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("uint", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Implicit("uint", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("uint", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("uint", "double", ConvertMethod.Convert); + + // long + CheckedConversionInExpressionTree_Implicit("long", "long", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("long", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("long", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("long", "double", ConvertMethod.Convert); + + // ulong + CheckedConversionInExpressionTree_Implicit("ulong", "ulong", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("ulong", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("ulong", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("ulong", "double", ConvertMethod.Convert); + + // decimal + CheckedConversionInExpressionTree_Implicit("decimal", "decimal", ConvertMethod.None); + + // float + CheckedConversionInExpressionTree_Implicit("float", "float", ConvertMethod.None); + CheckedConversionInExpressionTree_Implicit("float", "double", ConvertMethod.Convert); + + // double + CheckedConversionInExpressionTree_Implicit("double", "double", ConvertMethod.None); + + // object + CheckedConversionInExpressionTree_Implicit("int", "object", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Implicit("string", "object", ConvertMethod.None); + + // Nullable<> + CheckedConversionInExpressionTree_Implicit("int", "int?", "arg => F(Convert(arg))"); + CheckedConversionInExpressionTree_Implicit("int", "long?", "arg => F(ConvertChecked(ConvertChecked(arg)))"); + } + + [Fact] + [WorkItem(18459, "https://github.com/dotnet/roslyn/issues/18459")] + public void CheckedConversionsInExpressionTrees_ImplicitTuple() + { + CheckedConversionInExpressionTree_Implicit("(int, int)", "(int, int)?", ConvertMethod.Convert); + } + + [Fact] + public void CheckedConversionsInExpressionTrees_Explicit() + { + // char + CheckedConversionInExpressionTree_Explicit("char", "char", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("char", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("char", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("char", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("char", "double", ConvertMethod.Convert); + + // sbyte + CheckedConversionInExpressionTree_Explicit("sbyte", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "sbyte", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("sbyte", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("sbyte", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("sbyte", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("sbyte", "double", ConvertMethod.Convert); + + // byte + CheckedConversionInExpressionTree_Explicit("byte", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "byte", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("byte", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("byte", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("byte", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("byte", "double", ConvertMethod.Convert); + + // short + CheckedConversionInExpressionTree_Explicit("short", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "short", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("short", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("short", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("short", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("short", "double", ConvertMethod.Convert); + + // ushort + CheckedConversionInExpressionTree_Explicit("ushort", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "ushort", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ushort", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ushort", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ushort", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ushort", "double", ConvertMethod.Convert); + + // int + CheckedConversionInExpressionTree_Explicit("int", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "int", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("int", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("int", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("int", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("int", "double", ConvertMethod.Convert); + + // uint + CheckedConversionInExpressionTree_Explicit("uint", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "uint", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("uint", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("uint", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("uint", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("uint", "double", ConvertMethod.Convert); + + // long + CheckedConversionInExpressionTree_Explicit("long", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "long", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("long", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("long", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("long", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("long", "double", ConvertMethod.Convert); + + // ulong + CheckedConversionInExpressionTree_Explicit("ulong", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("ulong", "ulong", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ulong", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ulong", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("ulong", "double", ConvertMethod.Convert); + + // decimal + CheckedConversionInExpressionTree_Explicit("decimal", "char", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "sbyte", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "byte", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "short", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "ushort", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "int", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "uint", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "long", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "ulong", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("decimal", "double", ConvertMethod.Convert); + + // float + CheckedConversionInExpressionTree_Explicit("float", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("float", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("float", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("float", "double", ConvertMethod.Convert); + + // double + CheckedConversionInExpressionTree_Explicit("double", "char", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "sbyte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "byte", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "short", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "ushort", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "int", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "uint", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "long", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "ulong", ConvertMethod.ConvertChecked); + CheckedConversionInExpressionTree_Explicit("double", "decimal", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("double", "float", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("double", "double", ConvertMethod.Convert); + + // enum + CheckedConversionInExpressionTree_Explicit("E", "int", ConvertMethod.ConvertChecked, "enum E { }"); + CheckedConversionInExpressionTree_Explicit("int", "E", ConvertMethod.ConvertChecked, "enum E { }"); + CheckedConversionInExpressionTree_Explicit("E", "int", ConvertMethod.ConvertChecked, "enum E : short { }"); + CheckedConversionInExpressionTree_Explicit("int", "E", ConvertMethod.ConvertChecked, "enum E : short { }"); + + // object + CheckedConversionInExpressionTree_Explicit("int", "object", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("object", "int", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("string", "object", ConvertMethod.Convert); + CheckedConversionInExpressionTree_Explicit("object", "string", ConvertMethod.Convert); + + // Nullable<> + CheckedConversionInExpressionTree_Explicit("int", "byte?", "arg => ConvertChecked(arg)"); + CheckedConversionInExpressionTree_Explicit("int", "int?", "arg => ConvertChecked(arg)"); + CheckedConversionInExpressionTree_Explicit("int", "long?", "arg => ConvertChecked(ConvertChecked(arg))"); + } + + [Fact] + public void CheckedConversionsInExpressionTrees_ExplicitTuple() + { + CheckedConversionInExpressionTree_Explicit("(int, int)", "(int, int)?", ConvertMethod.Convert); + } + + private enum ConvertMethod + { + None, + Convert, + ConvertChecked, + } + + private void CheckedConversionInExpressionTree_Implicit(string fromType, string toType, ConvertMethod expectedMethod, string additionalTypes = "") + { + var source = CheckedConversionInExpressionTree_ImplicitSource(fromType, toType, additionalTypes); + var compilation = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + string expectedOutput; + switch (expectedMethod) + { + default: + expectedOutput = "arg => F(arg)"; + break; + case ConvertMethod.Convert: + expectedOutput = "arg => F(Convert(arg))"; + break; + case ConvertMethod.ConvertChecked: + expectedOutput = "arg => F(ConvertChecked(arg))"; + break; + } + var verifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + // Since Expression.ConvertChecked can generate a Checked result + // (rather than ConvertChecked), verify the correct method was called. + VerifyConversionInExpressionTreeIL(verifier.TestData.GetMethodData("C.Main").GetMethodIL(), expectedMethod); + } + + private void CheckedConversionInExpressionTree_Implicit(string fromType, string toType, string expectedOutput) + { + var source = CheckedConversionInExpressionTree_ImplicitSource(fromType, toType, additionalTypes: ""); + var compilation = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe); + CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + private static string CheckedConversionInExpressionTree_ImplicitSource(string fromType, string toType, string additionalTypes) + { + return +$@"using System; +using System.Linq.Expressions; +{additionalTypes} +class C +{{ + static {toType} F({toType} arg) => arg; + static void Main() + {{ + Expression> e = arg => checked(F(arg)); + Console.WriteLine(e); + }} +}}"; + } + + private void CheckedConversionInExpressionTree_Explicit(string fromType, string toType, ConvertMethod expectedMethod, string additionalTypes = "") + { + var source = CheckedConversionInExpressionTree_ExplicitSource(fromType, toType, additionalTypes); + var compilation = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + string expectedOutput; + switch (expectedMethod) + { + default: + expectedOutput = "arg => arg"; + break; + case ConvertMethod.Convert: + expectedOutput = "arg => Convert(arg)"; + break; + case ConvertMethod.ConvertChecked: + expectedOutput = "arg => ConvertChecked(arg)"; + break; + } + var verifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + // Since Expression.ConvertChecked can generate a Checked result + // (rather than ConvertChecked), verify the correct method was called. + VerifyConversionInExpressionTreeIL(verifier.TestData.GetMethodData("C.Main").GetMethodIL(), expectedMethod); + } + + private void CheckedConversionInExpressionTree_Explicit(string fromType, string toType, string expectedOutput) + { + var source = CheckedConversionInExpressionTree_ExplicitSource(fromType, toType, additionalTypes: ""); + var compilation = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe); + CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + private static string CheckedConversionInExpressionTree_ExplicitSource(string fromType, string toType, string additionalTypes) + { + return +$@"using System; +using System.Linq.Expressions; +{additionalTypes} +class C +{{ + static void Main() + {{ + Expression> e = arg => checked(({toType})arg); + Console.WriteLine(e); + }} +}}"; + } + + private static void VerifyConversionInExpressionTreeIL(string actualIL, ConvertMethod expectedMethod) + { + Assert.Equal( + actualIL.Contains($"System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression, "), + expectedMethod == ConvertMethod.Convert); + Assert.Equal( + actualIL.Contains($"System.Linq.Expressions.Expression.ConvertChecked(System.Linq.Expressions.Expression, "), + expectedMethod == ConvertMethod.ConvertChecked); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs index 75a50e471aa80..0f1e7cddca389 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { public static class LocalFunctionTestsUtil { - public static IMethodSymbol FindLocalFunction(this CommonTestBase.CompilationVerifier verifier, string localFunctionName) + public static IMethodSymbol FindLocalFunction(this CompilationVerifier verifier, string localFunctionName) { localFunctionName = (char)GeneratedNameKind.LocalFunction + "__" + localFunctionName; var methods = verifier.TestData.GetMethodsByName(); @@ -4038,7 +4038,7 @@ .locals init (Program.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 } "); } - + internal CompilationVerifier VerifyOutput(string source, string output, CSharpCompilationOptions options) { var comp = CreateCompilationWithMscorlib45AndCSruntime(source, options: options); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLockTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLockTests.cs index 6a0f3d1eeba35..7bca136e11e92 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLockTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLockTests.cs @@ -57,7 +57,7 @@ .locals init (object V_0, IL_0023: brfalse.s IL_002b IL_0025: ldloc.0 IL_0026: call ""void System.Threading.Monitor.Exit(object)"" - IL_002b: endfinally + ~IL_002b: endfinally } -IL_002c: ldstr ""After"" IL_0031: call ""void System.Console.WriteLine(string)"" @@ -112,7 +112,7 @@ .locals init (object V_0, IL_0027: brfalse.s IL_002f IL_0029: ldloc.0 IL_002a: call ""void System.Threading.Monitor.Exit(object)"" - IL_002f: endfinally + ~IL_002f: endfinally } -IL_0030: ldstr ""After"" IL_0035: call ""void System.Console.WriteLine(string)"" @@ -387,7 +387,7 @@ .locals init (object V_0, IL_0027: brfalse.s IL_002f IL_0029: ldloc.0 IL_002a: call ""void System.Threading.Monitor.Exit(object)"" - IL_002f: endfinally + ~IL_002f: endfinally } -IL_0030: ldstr ""After"" IL_0035: call ""void System.Console.WriteLine(string)"" diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs index 31b15e6ec6269..b8eac7aeae28b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs @@ -12826,6 +12826,7 @@ static System.Action M() compilation: compilation, moduleBeingBuiltOpt: module, emittingPdb: false, + emitTestCoverageData: false, hasDeclarationErrors: false, diagnostics: diagnostics, filterOpt: null, diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs index 57b7dd3f995d6..02b7133564480 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs @@ -2442,6 +2442,281 @@ .locals init (int V_0) //x "); } + [WorkItem(18678, "https://github.com/dotnet/roslyn/issues/18678")] + [Fact] + public void TryCatchConstantFalseFilter1() + { + var src = @" +using System; +class C +{ + static void Main() + { + try + { + throw new Exception(); + } + catch (Exception) when (false) + { + Console.Write(""Catch""); + } + } +}"; + var comp = CompileAndVerify(src); + comp.VerifyIL("C.Main", @" +{ + // Code size 6 (0x6) + .maxstack 1 + IL_0000: newobj ""System.Exception..ctor()"" + IL_0005: throw +}"); + } + + [WorkItem(18678, "https://github.com/dotnet/roslyn/issues/18678")] + [Fact] + public void TryCatchConstantFalseFilter2() + { + var src = @" +using System; +class C +{ + static void Main() + { + try + { + throw new Exception(); + } + catch (NullReferenceException) when (false) + { + Console.Write(""Catch1""); + } + catch (Exception) when (false) + { + Console.Write(""Catch2""); + } + catch when (false) + { + Console.Write(""Catch""); + } + } +}"; + var comp = CompileAndVerify(src); + comp.VerifyIL("C.Main", @" +{ + // Code size 6 (0x6) + .maxstack 1 + IL_0000: newobj ""System.Exception..ctor()"" + IL_0005: throw +}"); + } + + [WorkItem(18678, "https://github.com/dotnet/roslyn/issues/18678")] + [Fact] + public void TryCatchConstantFalseFilter3() + { + var src = @" +using System; +class C +{ + static void Main() + { + try + { + throw new Exception(); + } + catch (NullReferenceException) when ((1+1) == 2) + { + Console.Write(""Catch1""); + } + catch (Exception) when (true == false) + { + Console.Write(""Catch2""); + } + catch when ((1+1) != 2) + { + Console.Write(""Catch""); + } + } +}"; + var comp = CompileAndVerify(src); + comp.VerifyIL("C.Main", @" +{ + // Code size 39 (0x27) + .maxstack 2 + .try + { + IL_0000: newobj ""System.Exception..ctor()"" + IL_0005: throw + } + filter + { + IL_0006: isinst ""System.NullReferenceException"" + IL_000b: dup + IL_000c: brtrue.s IL_0012 + IL_000e: pop + IL_000f: ldc.i4.0 + IL_0010: br.s IL_0017 + IL_0012: pop + IL_0013: ldc.i4.1 + IL_0014: ldc.i4.0 + IL_0015: cgt.un + IL_0017: endfilter + } // end filter + { // handler + IL_0019: pop + IL_001a: ldstr ""Catch1"" + IL_001f: call ""void System.Console.Write(string)"" + IL_0024: leave.s IL_0026 + } + IL_0026: ret +}"); + } + + [WorkItem(18678, "https://github.com/dotnet/roslyn/issues/18678")] + [Fact] + public void TryCatchConstantFalseFilterCombined() + { + var src = @" +using System; +class C +{ + static void Main() + { + var message = ""ExceptionMessage""; + try + { + throw new Exception(message); + } + catch (NullReferenceException) when (false) + { + Console.Write(""NullReferenceCatch""); + } + catch (Exception e) when (e.Message == message) + { + Console.Write(""ExceptionFilter""); + } + catch (Exception) + { + Console.Write(""ExceptionCatch""); + } + catch when (false) + { + Console.Write(""Catch""); + } + } +}"; + var comp = CompileAndVerify(src, expectedOutput: "ExceptionFilter"); + comp.VerifyIL("C.Main", @" +{ + // Code size 68 (0x44) + .maxstack 2 + .locals init (string V_0) //message + IL_0000: ldstr ""ExceptionMessage"" + IL_0005: stloc.0 + .try + { + IL_0006: ldloc.0 + IL_0007: newobj ""System.Exception..ctor(string)"" + IL_000c: throw + } + filter + { + IL_000d: isinst ""System.Exception"" + IL_0012: dup + IL_0013: brtrue.s IL_0019 + IL_0015: pop + IL_0016: ldc.i4.0 + IL_0017: br.s IL_0027 + IL_0019: callvirt ""string System.Exception.Message.get"" + IL_001e: ldloc.0 + IL_001f: call ""bool string.op_Equality(string, string)"" + IL_0024: ldc.i4.0 + IL_0025: cgt.un + IL_0027: endfilter + } // end filter + { // handler + IL_0029: pop + IL_002a: ldstr ""ExceptionFilter"" + IL_002f: call ""void System.Console.Write(string)"" + IL_0034: leave.s IL_0043 + } + catch System.Exception + { + IL_0036: pop + IL_0037: ldstr ""ExceptionCatch"" + IL_003c: call ""void System.Console.Write(string)"" + IL_0041: leave.s IL_0043 + } + IL_0043: ret +}"); + } + + [WorkItem(18678, "https://github.com/dotnet/roslyn/issues/18678")] + [Fact] + public void TryCatchFinallyConstantFalseFilter() + { + var src = @" +using System; +class C +{ + static void Main() + { + try + { + + try + { + throw new Exception(); + } + catch (NullReferenceException) when (false) + { + Console.Write(""Catch1""); + } + catch (Exception) when (false) + { + Console.Write(""Catch2""); + } + finally + { + Console.Write(""Finally""); + } + } + catch + { + Console.Write(""OuterCatch""); + } + } +}"; + var comp = CompileAndVerify(src, expectedOutput: "FinallyOuterCatch"); + comp.VerifyIL("C.Main", @" +{ + // Code size 31 (0x1f) + .maxstack 1 + .try + { + .try + { + IL_0000: newobj ""System.Exception..ctor()"" + IL_0005: throw + } + finally + { + IL_0006: ldstr ""Finally"" + IL_000b: call ""void System.Console.Write(string)"" + IL_0010: endfinally + } + } + catch object + { + IL_0011: pop + IL_0012: ldstr ""OuterCatch"" + IL_0017: call ""void System.Console.Write(string)"" + IL_001c: leave.s IL_001e + } + IL_001e: ret +}"); + } + [Fact] public void TryCatchWithReturnValue() { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs index 4448de074facf..7675c2e870bfb 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs @@ -20470,7 +20470,7 @@ static void Main() { var reader = block.MetadataReader; AssertEx.SetEqual(new[] { "mscorlib 4.0", "System.ValueTuple 4.0" }, reader.DumpAssemblyReferences()); - Assert.Contains("ValueTuple`2, System, AssemblyRef:System.ValueTuple", reader.DumpTypeReferences()); + Assert.Contains("ValueTuple`2, System, AssemblyReference:System.ValueTuple", reader.DumpTypeReferences()); } // emit with pdb @@ -20479,7 +20479,7 @@ static void Main() { var reader = assembly.GetMetadataReader(); AssertEx.SetEqual(new[] { "mscorlib 4.0", "System.ValueTuple 4.0" }, reader.DumpAssemblyReferences()); - Assert.Contains("ValueTuple`2, System, AssemblyRef:System.ValueTuple", reader.DumpTypeReferences()); + Assert.Contains("ValueTuple`2, System, AssemblyReference:System.ValueTuple", reader.DumpTypeReferences()); }); // no assertion in MetadataWriter } @@ -20510,7 +20510,7 @@ static void Main() { var reader = block.MetadataReader; AssertEx.SetEqual(new[] { "mscorlib 4.0", "lib 0.0" }, reader.DumpAssemblyReferences()); - Assert.Contains("ReferencedType, , AssemblyRef:lib", reader.DumpTypeReferences()); + Assert.Contains("ReferencedType, , AssemblyReference:lib", reader.DumpTypeReferences()); } // emit with pdb @@ -20519,7 +20519,7 @@ static void Main() { var reader = assembly.GetMetadataReader(); AssertEx.SetEqual(new[] { "mscorlib 4.0", "lib 0.0" }, reader.DumpAssemblyReferences()); - Assert.Contains("ReferencedType, , AssemblyRef:lib", reader.DumpTypeReferences()); + Assert.Contains("ReferencedType, , AssemblyReference:lib", reader.DumpTypeReferences()); }); // no assertion in MetadataWriter } @@ -22751,7 +22751,6 @@ static void Main() // System.Console.WriteLine(notAliteral.M()); Diagnostic(ErrorCode.ERR_BadExtensionArgTypes, "M").WithArguments("(int A, int B)", "M", "C.M((int x, long y))").WithLocation(23, 46) ); - } [Fact] @@ -22872,5 +22871,210 @@ void M() Diagnostic(ErrorCode.ERR_TypelessTupleInAs, "(0, null) as (int, T)?").WithLocation(6, 17) ); } + + [Fact] + public void CheckedConstantConversions() + { + var source = +@"#pragma warning disable 219 +class C +{ + static void Main() + { + unchecked + { + var u = ((byte, byte))(0, -1); + var (a, b) = ((byte, byte))(0, -2); + } + checked + { + var c = ((byte, byte))(0, -1); + var (a, b) = ((byte, byte))(0, -2); + } } -} \ No newline at end of file +}"; + var comp = CreateStandardCompilation( + source, + references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, + options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // (13,39): error CS0221: Constant value '-1' cannot be converted to a 'byte' (use 'unchecked' syntax to override) + // var c = ((byte, byte))(0, -1); + Diagnostic(ErrorCode.ERR_ConstOutOfRangeChecked, "-1").WithArguments("-1", "byte").WithLocation(13, 39), + // (14,44): error CS0221: Constant value '-2' cannot be converted to a 'byte' (use 'unchecked' syntax to override) + // var (a, b) = ((byte, byte))(0, -2); + Diagnostic(ErrorCode.ERR_ConstOutOfRangeChecked, "-2").WithArguments("-2", "byte").WithLocation(14, 44)); + } + + [Fact] + [WorkItem(18459, "https://github.com/dotnet/roslyn/issues/18459")] + public void CheckedConversions() + { + var source = +@"using System; +class C +{ + static (long, byte) Default((int, int) t) + { + return ((long, byte))t; + } + static (long, byte) Unchecked((int, int) t) + { + unchecked + { + return ((long, byte))t; + } + } + static (long, byte) Checked((int, int) t) + { + checked + { + return ((long, byte))t; + } + } + static void Main() + { + var d = Default((-1, -1)); + Console.Write(d); + var u = Unchecked((-1, -1)); + Console.Write(u); + try + { + var c = Checked((-1, -1)); + Console.Write(c); + } + catch (OverflowException) + { + Console.Write(""overflow""); + } + } +}"; + var comp = CreateStandardCompilation( + source, + references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, + options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: @"(-1, 255)(-1, 255)overflow"); + verifier.VerifyIL("C.Default", +@"{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ValueTuple V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldfld ""int System.ValueTuple.Item1"" + IL_0008: conv.i8 + IL_0009: ldloc.0 + IL_000a: ldfld ""int System.ValueTuple.Item2"" + IL_000f: conv.u1 + IL_0010: newobj ""System.ValueTuple..ctor(long, byte)"" + IL_0015: ret +}"); + verifier.VerifyIL("C.Unchecked", +@"{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ValueTuple V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldfld ""int System.ValueTuple.Item1"" + IL_0008: conv.i8 + IL_0009: ldloc.0 + IL_000a: ldfld ""int System.ValueTuple.Item2"" + IL_000f: conv.u1 + IL_0010: newobj ""System.ValueTuple..ctor(long, byte)"" + IL_0015: ret +}"); + verifier.VerifyIL("C.Checked", +@"{ + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ValueTuple V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldfld ""int System.ValueTuple.Item1"" + IL_0008: conv.i8 + IL_0009: ldloc.0 + IL_000a: ldfld ""int System.ValueTuple.Item2"" + IL_000f: conv.ovf.u1 + IL_0010: newobj ""System.ValueTuple..ctor(long, byte)"" + IL_0015: ret +}"); + } + + [Fact] + [WorkItem(19434, "https://github.com/dotnet/roslyn/issues/19434")] + public void ExplicitTupleLiteralConversionWithNullable01() + { + var source = @" +class C +{ + static void Main() + { + int x = 1; + var y = ((byte, byte)?)(x, x); + System.Console.WriteLine(y.Value); + } +} +"; + + var comp = CompileAndVerify(source, + additionalRefs: s_valueTupleRefs, options: TestOptions.DebugExe, expectedOutput: +@"(1, 1)"); + + comp.VerifyIL("C.Main()", @" +{ + // Code size 38 (0x26) + .maxstack 3 + .locals init (int V_0, //x + (byte, byte)? V_1) //y + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: stloc.0 + IL_0003: ldloca.s V_1 + IL_0005: ldloc.0 + IL_0006: conv.u1 + IL_0007: ldloc.0 + IL_0008: conv.u1 + IL_0009: newobj ""System.ValueTuple..ctor(byte, byte)"" + IL_000e: call ""(byte, byte)?..ctor((byte, byte))"" + IL_0013: ldloca.s V_1 + IL_0015: call ""(byte, byte) (byte, byte)?.Value.get"" + IL_001a: box ""System.ValueTuple"" + IL_001f: call ""void System.Console.WriteLine(object)"" + IL_0024: nop + IL_0025: ret +} +"); + + comp = CompileAndVerify(source, + additionalRefs: s_valueTupleRefs, options: TestOptions.ReleaseExe, expectedOutput: +@"(1, 1)"); + + comp.VerifyIL("C.Main()", @" +{ + // Code size 36 (0x24) + .maxstack 3 + .locals init (int V_0, //x + (byte, byte)? V_1) //y + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_1 + IL_0004: ldloc.0 + IL_0005: conv.u1 + IL_0006: ldloc.0 + IL_0007: conv.u1 + IL_0008: newobj ""System.ValueTuple..ctor(byte, byte)"" + IL_000d: call ""(byte, byte)?..ctor((byte, byte))"" + IL_0012: ldloca.s V_1 + IL_0014: call ""(byte, byte) (byte, byte)?.Value.get"" + IL_0019: box ""System.ValueTuple"" + IL_001e: call ""void System.Console.WriteLine(object)"" + IL_0023: ret +} +"); + } + } +} diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs index 146fddc1aa862..284bf20c025c6 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -703,14 +704,6 @@ public static IEnumerable Power(int number, int exponent) CompileAndVerify(text, expectedOutput: expectedOutput); } - // When ReflectionEmit supports writing exception handler info, this method - // can be removed and CompileAndVerify references above will resolve to - // the overload that emits with both CCI and ReflectionEmit. (Bug #7012) - private CompilationVerifier CompileAndVerify(string source, string expectedOutput = null) - { - return base.CompileAndVerify(source: source, expectedOutput: expectedOutput); - } - [WorkItem(540719, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540719")] [Fact] public void LabelBetweenLocalAndInitialize() diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 39c1c7f56c87b..3472ef5b26ed6 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -7735,46 +7735,43 @@ .locals init (int? V_0, expectedOutput: "null1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 69 (0x45) + // Code size 65 (0x41) .maxstack 1 .locals init (int? V_0, int V_1, - int? V_2, - int V_3, //i - int? V_4, - int? V_5) + int V_2, //i + int? V_3, + int? V_4) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: call ""bool int?.HasValue.get"" - IL_0010: brtrue.s IL_0014 - IL_0012: br.s IL_0037 - IL_0014: ldloc.0 - IL_0015: stloc.s V_5 - IL_0017: ldloca.s V_5 - IL_0019: call ""int int?.GetValueOrDefault()"" - IL_001e: stloc.1 - IL_001f: ldloca.s V_5 - IL_0021: call ""bool int?.HasValue.get"" - IL_0026: brfalse.s IL_002a + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloca.s V_0 + IL_0007: call ""bool int?.HasValue.get"" + IL_000c: brtrue.s IL_0010 + IL_000e: br.s IL_0033 + IL_0010: ldloc.0 + IL_0011: stloc.s V_4 + IL_0013: ldloca.s V_4 + IL_0015: call ""int int?.GetValueOrDefault()"" + IL_001a: stloc.1 + IL_001b: ldloca.s V_4 + IL_001d: call ""bool int?.HasValue.get"" + IL_0022: brfalse.s IL_0026 + IL_0024: br.s IL_0026 + IL_0026: ldloc.1 + IL_0027: stloc.2 IL_0028: br.s IL_002a - IL_002a: ldloc.1 - IL_002b: stloc.3 - IL_002c: br.s IL_002e - IL_002e: ldloc.3 - IL_002f: call ""void System.Console.Write(int)"" - IL_0034: nop - IL_0035: br.s IL_0044 - IL_0037: ldstr ""null"" - IL_003c: call ""void System.Console.Write(string)"" - IL_0041: nop - IL_0042: br.s IL_0044 - IL_0044: ret + IL_002a: ldloc.2 + IL_002b: call ""void System.Console.Write(int)"" + IL_0030: nop + IL_0031: br.s IL_0040 + IL_0033: ldstr ""null"" + IL_0038: call ""void System.Console.Write(string)"" + IL_003d: nop + IL_003e: br.s IL_0040 + IL_0040: ret }" ); } @@ -7821,32 +7818,29 @@ .locals init (System.IComparable V_0) expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 32 (0x20) + // Code size 28 (0x1c) .maxstack 1 .locals init (int V_0, System.IComparable V_1, - int V_2, - System.IComparable V_3, //i - int V_4) + System.IComparable V_2, //i + int V_3) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: box ""int"" - IL_000f: stloc.1 + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: box ""int"" + IL_000b: stloc.1 + IL_000c: br.s IL_000e + IL_000e: ldloc.1 + IL_000f: stloc.2 IL_0010: br.s IL_0012 - IL_0012: ldloc.1 - IL_0013: stloc.3 - IL_0014: br.s IL_0016 - IL_0016: ldloc.3 - IL_0017: call ""void System.Console.Write(object)"" - IL_001c: nop - IL_001d: br.s IL_001f - IL_001f: ret + IL_0012: ldloc.2 + IL_0013: call ""void System.Console.Write(object)"" + IL_0018: nop + IL_0019: br.s IL_001b + IL_001b: ret }" ); } @@ -7900,37 +7894,34 @@ .locals init (int? V_0, expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 45 (0x2d) + // Code size 41 (0x29) .maxstack 1 .locals init (int? V_0, System.IComparable V_1, - int? V_2, - System.IComparable V_3, //i - int? V_4) + System.IComparable V_2, //i + int? V_3) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: call ""bool int?.HasValue.get"" - IL_0010: brtrue.s IL_0014 - IL_0012: br.s IL_001d - IL_0014: ldloc.0 - IL_0015: box ""int?"" - IL_001a: stloc.1 - IL_001b: br.s IL_001f - IL_001d: br.s IL_002c - IL_001f: ldloc.1 - IL_0020: stloc.3 - IL_0021: br.s IL_0023 - IL_0023: ldloc.3 - IL_0024: call ""void System.Console.Write(object)"" - IL_0029: nop - IL_002a: br.s IL_002c - IL_002c: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloca.s V_0 + IL_0007: call ""bool int?.HasValue.get"" + IL_000c: brtrue.s IL_0010 + IL_000e: br.s IL_0019 + IL_0010: ldloc.0 + IL_0011: box ""int?"" + IL_0016: stloc.1 + IL_0017: br.s IL_001b + IL_0019: br.s IL_0028 + IL_001b: ldloc.1 + IL_001c: stloc.2 + IL_001d: br.s IL_001f + IL_001f: ldloc.2 + IL_0020: call ""void System.Console.Write(object)"" + IL_0025: nop + IL_0026: br.s IL_0028 + IL_0028: ret }" ); } @@ -7992,44 +7983,41 @@ .locals init (object V_0, expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 62 (0x3e) + // Code size 58 (0x3a) .maxstack 1 .locals init (object V_0, int V_1, - object V_2, - int V_3, //i - object V_4, - int? V_5) + int V_2, //i + object V_3, + int? V_4) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: brtrue.s IL_000e - IL_000c: br.s IL_002e - IL_000e: ldloc.0 - IL_000f: isinst ""int?"" - IL_0014: unbox.any ""int?"" - IL_0019: stloc.s V_5 - IL_001b: ldloca.s V_5 - IL_001d: call ""int int?.GetValueOrDefault()"" - IL_0022: stloc.1 - IL_0023: ldloca.s V_5 - IL_0025: call ""bool int?.HasValue.get"" - IL_002a: brfalse.s IL_002e - IL_002c: br.s IL_0030 - IL_002e: br.s IL_003d - IL_0030: ldloc.1 - IL_0031: stloc.3 - IL_0032: br.s IL_0034 - IL_0034: ldloc.3 - IL_0035: call ""void System.Console.Write(int)"" - IL_003a: nop - IL_003b: br.s IL_003d - IL_003d: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: brtrue.s IL_000a + IL_0008: br.s IL_002a + IL_000a: ldloc.0 + IL_000b: isinst ""int?"" + IL_0010: unbox.any ""int?"" + IL_0015: stloc.s V_4 + IL_0017: ldloca.s V_4 + IL_0019: call ""int int?.GetValueOrDefault()"" + IL_001e: stloc.1 + IL_001f: ldloca.s V_4 + IL_0021: call ""bool int?.HasValue.get"" + IL_0026: brfalse.s IL_002a + IL_0028: br.s IL_002c + IL_002a: br.s IL_0039 + IL_002c: ldloc.1 + IL_002d: stloc.2 + IL_002e: br.s IL_0030 + IL_0030: ldloc.2 + IL_0031: call ""void System.Console.Write(int)"" + IL_0036: nop + IL_0037: br.s IL_0039 + IL_0039: ret }" ); } @@ -8101,52 +8089,49 @@ .locals init (object V_0, expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 75 (0x4b) + // Code size 71 (0x47) .maxstack 2 .locals init (object V_0, T V_1, - object V_2, - T V_3, //i + T V_2, //i + object V_3, object V_4, - object V_5, - T V_6) + T V_5) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: brtrue.s IL_000e - IL_000c: br.s IL_0036 - IL_000e: ldloc.0 - IL_000f: stloc.s V_5 - IL_0011: ldloc.s V_5 - IL_0013: isinst ""T"" - IL_0018: ldnull - IL_0019: cgt.un - IL_001b: dup - IL_001c: brtrue.s IL_002a - IL_001e: ldloca.s V_6 - IL_0020: initobj ""T"" - IL_0026: ldloc.s V_6 - IL_0028: br.s IL_0031 - IL_002a: ldloc.s V_5 - IL_002c: unbox.any ""T"" - IL_0031: stloc.1 - IL_0032: brfalse.s IL_0036 - IL_0034: br.s IL_0038 - IL_0036: br.s IL_004a - IL_0038: ldloc.1 - IL_0039: stloc.3 - IL_003a: br.s IL_003c - IL_003c: ldloc.3 - IL_003d: box ""T"" - IL_0042: call ""void System.Console.Write(object)"" - IL_0047: nop - IL_0048: br.s IL_004a - IL_004a: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: brtrue.s IL_000a + IL_0008: br.s IL_0032 + IL_000a: ldloc.0 + IL_000b: stloc.s V_4 + IL_000d: ldloc.s V_4 + IL_000f: isinst ""T"" + IL_0014: ldnull + IL_0015: cgt.un + IL_0017: dup + IL_0018: brtrue.s IL_0026 + IL_001a: ldloca.s V_5 + IL_001c: initobj ""T"" + IL_0022: ldloc.s V_5 + IL_0024: br.s IL_002d + IL_0026: ldloc.s V_4 + IL_0028: unbox.any ""T"" + IL_002d: stloc.1 + IL_002e: brfalse.s IL_0032 + IL_0030: br.s IL_0034 + IL_0032: br.s IL_0046 + IL_0034: ldloc.1 + IL_0035: stloc.2 + IL_0036: br.s IL_0038 + IL_0038: ldloc.2 + IL_0039: box ""T"" + IL_003e: call ""void System.Console.Write(object)"" + IL_0043: nop + IL_0044: br.s IL_0046 + IL_0046: ret }" ); } @@ -8218,52 +8203,49 @@ .locals init (System.IComparable V_0, expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 75 (0x4b) + // Code size 71 (0x47) .maxstack 2 .locals init (System.IComparable V_0, T V_1, - System.IComparable V_2, - T V_3, //i - System.IComparable V_4, - object V_5, - T V_6) + T V_2, //i + System.IComparable V_3, + object V_4, + T V_5) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: brtrue.s IL_000e - IL_000c: br.s IL_0036 - IL_000e: ldloc.0 - IL_000f: stloc.s V_5 - IL_0011: ldloc.s V_5 - IL_0013: isinst ""T"" - IL_0018: ldnull - IL_0019: cgt.un - IL_001b: dup - IL_001c: brtrue.s IL_002a - IL_001e: ldloca.s V_6 - IL_0020: initobj ""T"" - IL_0026: ldloc.s V_6 - IL_0028: br.s IL_0031 - IL_002a: ldloc.s V_5 - IL_002c: unbox.any ""T"" - IL_0031: stloc.1 - IL_0032: brfalse.s IL_0036 - IL_0034: br.s IL_0038 - IL_0036: br.s IL_004a - IL_0038: ldloc.1 - IL_0039: stloc.3 - IL_003a: br.s IL_003c - IL_003c: ldloc.3 - IL_003d: box ""T"" - IL_0042: call ""void System.Console.Write(object)"" - IL_0047: nop - IL_0048: br.s IL_004a - IL_004a: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: brtrue.s IL_000a + IL_0008: br.s IL_0032 + IL_000a: ldloc.0 + IL_000b: stloc.s V_4 + IL_000d: ldloc.s V_4 + IL_000f: isinst ""T"" + IL_0014: ldnull + IL_0015: cgt.un + IL_0017: dup + IL_0018: brtrue.s IL_0026 + IL_001a: ldloca.s V_5 + IL_001c: initobj ""T"" + IL_0022: ldloc.s V_5 + IL_0024: br.s IL_002d + IL_0026: ldloc.s V_4 + IL_0028: unbox.any ""T"" + IL_002d: stloc.1 + IL_002e: brfalse.s IL_0032 + IL_0030: br.s IL_0034 + IL_0032: br.s IL_0046 + IL_0034: ldloc.1 + IL_0035: stloc.2 + IL_0036: br.s IL_0038 + IL_0038: ldloc.2 + IL_0039: box ""T"" + IL_003e: call ""void System.Console.Write(object)"" + IL_0043: nop + IL_0044: br.s IL_0046 + IL_0046: ret }" ); } @@ -8337,54 +8319,51 @@ .locals init (U V_0, expectedOutput: "1"); compVerifier.VerifyIL("Program.M", @"{ - // Code size 85 (0x55) + // Code size 81 (0x51) .maxstack 2 .locals init (U V_0, T V_1, - U V_2, - T V_3, //i - U V_4, - object V_5, - T V_6) + T V_2, //i + U V_3, + object V_4, + T V_5) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: box ""U"" - IL_000f: brtrue.s IL_0013 - IL_0011: br.s IL_0040 - IL_0013: ldloc.0 - IL_0014: box ""U"" - IL_0019: stloc.s V_5 - IL_001b: ldloc.s V_5 - IL_001d: isinst ""T"" - IL_0022: ldnull - IL_0023: cgt.un - IL_0025: dup - IL_0026: brtrue.s IL_0034 - IL_0028: ldloca.s V_6 - IL_002a: initobj ""T"" - IL_0030: ldloc.s V_6 - IL_0032: br.s IL_003b - IL_0034: ldloc.s V_5 - IL_0036: unbox.any ""T"" - IL_003b: stloc.1 - IL_003c: brfalse.s IL_0040 - IL_003e: br.s IL_0042 - IL_0040: br.s IL_0054 - IL_0042: ldloc.1 - IL_0043: stloc.3 - IL_0044: br.s IL_0046 - IL_0046: ldloc.3 - IL_0047: box ""T"" - IL_004c: call ""void System.Console.Write(object)"" - IL_0051: nop - IL_0052: br.s IL_0054 - IL_0054: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: box ""U"" + IL_000b: brtrue.s IL_000f + IL_000d: br.s IL_003c + IL_000f: ldloc.0 + IL_0010: box ""U"" + IL_0015: stloc.s V_4 + IL_0017: ldloc.s V_4 + IL_0019: isinst ""T"" + IL_001e: ldnull + IL_001f: cgt.un + IL_0021: dup + IL_0022: brtrue.s IL_0030 + IL_0024: ldloca.s V_5 + IL_0026: initobj ""T"" + IL_002c: ldloc.s V_5 + IL_002e: br.s IL_0037 + IL_0030: ldloc.s V_4 + IL_0032: unbox.any ""T"" + IL_0037: stloc.1 + IL_0038: brfalse.s IL_003c + IL_003a: br.s IL_003e + IL_003c: br.s IL_0050 + IL_003e: ldloc.1 + IL_003f: stloc.2 + IL_0040: br.s IL_0042 + IL_0042: ldloc.2 + IL_0043: box ""T"" + IL_0048: call ""void System.Console.Write(object)"" + IL_004d: nop + IL_004e: br.s IL_0050 + IL_0050: ret }" ); } @@ -8598,57 +8577,54 @@ .locals init (T V_0, //t ); compVerifier.VerifyIL("Program.M2", @"{ - // Code size 82 (0x52) + // Code size 78 (0x4e) .maxstack 2 .locals init (System.ValueType V_0, T V_1, - System.ValueType V_2, - T V_3, //t - System.ValueType V_4, - object V_5, - T V_6, - T V_7) + T V_2, //t + System.ValueType V_3, + object V_4, + T V_5, + T V_6) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: brtrue.s IL_000e - IL_000c: br.s IL_0036 - IL_000e: ldloc.0 - IL_000f: stloc.s V_5 - IL_0011: ldloc.s V_5 - IL_0013: isinst ""T"" - IL_0018: ldnull - IL_0019: cgt.un - IL_001b: dup - IL_001c: brtrue.s IL_002a - IL_001e: ldloca.s V_6 - IL_0020: initobj ""T"" - IL_0026: ldloc.s V_6 - IL_0028: br.s IL_0031 - IL_002a: ldloc.s V_5 - IL_002c: unbox.any ""T"" - IL_0031: stloc.1 - IL_0032: brfalse.s IL_0036 - IL_0034: br.s IL_0038 - IL_0036: br.s IL_0041 - IL_0038: ldloc.1 - IL_0039: stloc.3 - IL_003a: br.s IL_003c - IL_003c: ldloc.3 - IL_003d: stloc.s V_7 - IL_003f: br.s IL_004f - IL_0041: ldloca.s V_6 - IL_0043: initobj ""T"" - IL_0049: ldloc.s V_6 - IL_004b: stloc.s V_7 - IL_004d: br.s IL_004f - IL_004f: ldloc.s V_7 - IL_0051: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: brtrue.s IL_000a + IL_0008: br.s IL_0032 + IL_000a: ldloc.0 + IL_000b: stloc.s V_4 + IL_000d: ldloc.s V_4 + IL_000f: isinst ""T"" + IL_0014: ldnull + IL_0015: cgt.un + IL_0017: dup + IL_0018: brtrue.s IL_0026 + IL_001a: ldloca.s V_5 + IL_001c: initobj ""T"" + IL_0022: ldloc.s V_5 + IL_0024: br.s IL_002d + IL_0026: ldloc.s V_4 + IL_0028: unbox.any ""T"" + IL_002d: stloc.1 + IL_002e: brfalse.s IL_0032 + IL_0030: br.s IL_0034 + IL_0032: br.s IL_003d + IL_0034: ldloc.1 + IL_0035: stloc.2 + IL_0036: br.s IL_0038 + IL_0038: ldloc.2 + IL_0039: stloc.s V_6 + IL_003b: br.s IL_004b + IL_003d: ldloca.s V_5 + IL_003f: initobj ""T"" + IL_0045: ldloc.s V_5 + IL_0047: stloc.s V_6 + IL_0049: br.s IL_004b + IL_004b: ldloc.s V_6 + IL_004d: ret }" ); } @@ -8780,50 +8756,47 @@ .locals init (int V_0, //t ); compVerifier.VerifyIL("Program.M2", @"{ - // Code size 75 (0x4b) + // Code size 71 (0x47) .maxstack 1 .locals init (T V_0, int V_1, - T V_2, - int V_3, //t - T V_4, - int? V_5, - int V_6) + int V_2, //t + T V_3, + int? V_4, + int V_5) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.2 - IL_0003: ldloc.2 - IL_0004: stloc.s V_4 - IL_0006: ldloc.s V_4 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: box ""T"" - IL_000f: brtrue.s IL_0013 - IL_0011: br.s IL_0038 - IL_0013: ldloc.0 - IL_0014: box ""T"" - IL_0019: isinst ""int?"" - IL_001e: unbox.any ""int?"" - IL_0023: stloc.s V_5 - IL_0025: ldloca.s V_5 - IL_0027: call ""int int?.GetValueOrDefault()"" - IL_002c: stloc.1 - IL_002d: ldloca.s V_5 - IL_002f: call ""bool int?.HasValue.get"" - IL_0034: brfalse.s IL_0038 - IL_0036: br.s IL_003a - IL_0038: br.s IL_0043 - IL_003a: ldloc.1 - IL_003b: stloc.3 - IL_003c: br.s IL_003e - IL_003e: ldloc.3 - IL_003f: stloc.s V_6 - IL_0041: br.s IL_0048 - IL_0043: ldc.i4.0 - IL_0044: stloc.s V_6 - IL_0046: br.s IL_0048 - IL_0048: ldloc.s V_6 - IL_004a: ret + IL_0002: stloc.3 + IL_0003: ldloc.3 + IL_0004: stloc.0 + IL_0005: ldloc.0 + IL_0006: box ""T"" + IL_000b: brtrue.s IL_000f + IL_000d: br.s IL_0034 + IL_000f: ldloc.0 + IL_0010: box ""T"" + IL_0015: isinst ""int?"" + IL_001a: unbox.any ""int?"" + IL_001f: stloc.s V_4 + IL_0021: ldloca.s V_4 + IL_0023: call ""int int?.GetValueOrDefault()"" + IL_0028: stloc.1 + IL_0029: ldloca.s V_4 + IL_002b: call ""bool int?.HasValue.get"" + IL_0030: brfalse.s IL_0034 + IL_0032: br.s IL_0036 + IL_0034: br.s IL_003f + IL_0036: ldloc.1 + IL_0037: stloc.2 + IL_0038: br.s IL_003a + IL_003a: ldloc.2 + IL_003b: stloc.s V_5 + IL_003d: br.s IL_0044 + IL_003f: ldc.i4.0 + IL_0040: stloc.s V_5 + IL_0042: br.s IL_0044 + IL_0044: ldloc.s V_5 + IL_0046: ret }" ); } @@ -9125,81 +9098,497 @@ public static string M2(object o) compVerifier.VerifyDiagnostics(); compVerifier.VerifyIL("Program.M2", @"{ - // Code size 154 (0x9a) + // Code size 150 (0x96) .maxstack 2 .locals init (object V_0, Generic.Color V_1, Generic.Color V_2, - object V_3, - Generic.Color V_4, //c - object V_5, - Generic.Color? V_6, - Generic.Color? V_7, - int V_8, - string V_9) + Generic.Color V_3, //c + object V_4, + Generic.Color? V_5, + Generic.Color? V_6, + int V_7, + string V_8) IL_0000: nop IL_0001: ldarg.0 - IL_0002: stloc.3 - IL_0003: ldloc.3 - IL_0004: stloc.s V_5 - IL_0006: ldloc.s V_5 - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: brtrue.s IL_000e - IL_000c: br.s IL_0060 - IL_000e: ldloc.0 - IL_000f: isinst ""Generic.Color?"" - IL_0014: unbox.any ""Generic.Color?"" - IL_0019: stloc.s V_6 - IL_001b: ldloca.s V_6 - IL_001d: call ""Generic.Color Generic.Color?.GetValueOrDefault()"" - IL_0022: stloc.1 - IL_0023: ldloca.s V_6 - IL_0025: call ""bool Generic.Color?.HasValue.get"" - IL_002a: brfalse.s IL_002e - IL_002c: br.s IL_0062 - IL_002e: ldloc.0 - IL_002f: isinst ""Generic.Color?"" - IL_0034: unbox.any ""Generic.Color?"" - IL_0039: stloc.s V_7 - IL_003b: ldloca.s V_7 - IL_003d: call ""Generic.Color Generic.Color?.GetValueOrDefault()"" - IL_0042: stloc.2 + IL_0002: stloc.s V_4 + IL_0004: ldloc.s V_4 + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: brtrue.s IL_000c + IL_000a: br.s IL_005e + IL_000c: ldloc.0 + IL_000d: isinst ""Generic.Color?"" + IL_0012: unbox.any ""Generic.Color?"" + IL_0017: stloc.s V_5 + IL_0019: ldloca.s V_5 + IL_001b: call ""Generic.Color Generic.Color?.GetValueOrDefault()"" + IL_0020: stloc.1 + IL_0021: ldloca.s V_5 + IL_0023: call ""bool Generic.Color?.HasValue.get"" + IL_0028: brfalse.s IL_002c + IL_002a: br.s IL_0060 + IL_002c: ldloc.0 + IL_002d: isinst ""Generic.Color?"" + IL_0032: unbox.any ""Generic.Color?"" + IL_0037: stloc.s V_6 + IL_0039: ldloca.s V_6 + IL_003b: call ""Generic.Color Generic.Color?.GetValueOrDefault()"" + IL_0040: stloc.2 + IL_0041: ldloca.s V_6 + IL_0043: call ""bool Generic.Color?.HasValue.get"" + IL_0048: brfalse.s IL_005e + IL_004a: ldloc.2 + IL_004b: stloc.s V_7 + IL_004d: ldloc.s V_7 + IL_004f: brfalse.s IL_005a + IL_0051: br.s IL_0053 + IL_0053: ldloc.s V_7 + IL_0055: ldc.i4.1 + IL_0056: beq.s IL_005c + IL_0058: br.s IL_005e + IL_005a: br.s IL_0078 + IL_005c: br.s IL_0081 + IL_005e: br.s IL_008a + IL_0060: ldloc.1 + IL_0061: stloc.3 + IL_0062: br.s IL_0064 + IL_0064: ldstr ""Generic.Color."" + IL_0069: ldloc.3 + IL_006a: box ""Generic.Color"" + IL_006f: call ""string string.Concat(object, object)"" + IL_0074: stloc.s V_8 + IL_0076: br.s IL_0093 + IL_0078: ldstr ""Generic.Color.Red"" + IL_007d: stloc.s V_8 + IL_007f: br.s IL_0093 + IL_0081: ldstr ""Generic.Color.Blue"" + IL_0086: stloc.s V_8 + IL_0088: br.s IL_0093 + IL_008a: ldstr ""None"" + IL_008f: stloc.s V_8 + IL_0091: br.s IL_0093 + IL_0093: ldloc.s V_8 + IL_0095: ret +}" + ); + } + + [Fact, WorkItem(19280, "https://github.com/dotnet/roslyn/issues/19280")] + public void ShareLikeKindedTemps_01() + { + var source = @"using System; + +public class Program +{ + public static void Main() + { + } + static bool b = false; + public static void M(object o) + { + switch (o) + { + case int i when b: break; + case var _ when b: break; + case int i when b: break; + case var _ when b: break; + case int i when b: break; + case var _ when b: break; + case int i when b: break; + case var _ when b: break; + } + } +}"; + var compVerifier = CompileAndVerify(source, + options: TestOptions.ReleaseDll.WithOutputKind(OutputKind.ConsoleApplication), + expectedOutput: ""); + compVerifier.VerifyIL("Program.M", +@"{ + // Code size 203 (0xcb) + .maxstack 1 + .locals init (object V_0, + int V_1, + int? V_2) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: brfalse IL_0094 + IL_0008: ldloc.0 + IL_0009: isinst ""int?"" + IL_000e: unbox.any ""int?"" + IL_0013: stloc.2 + IL_0014: ldloca.s V_2 + IL_0016: call ""int int?.GetValueOrDefault()"" + IL_001b: stloc.1 + IL_001c: ldloca.s V_2 + IL_001e: call ""bool int?.HasValue.get"" + IL_0023: brfalse.s IL_0094 + IL_0025: br.s IL_008d + IL_0027: ldloc.0 + IL_0028: brfalse.s IL_00a4 + IL_002a: ldloc.0 + IL_002b: isinst ""int?"" + IL_0030: unbox.any ""int?"" + IL_0035: stloc.2 + IL_0036: ldloca.s V_2 + IL_0038: call ""int int?.GetValueOrDefault()"" + IL_003d: stloc.1 + IL_003e: ldloca.s V_2 + IL_0040: call ""bool int?.HasValue.get"" + IL_0045: brfalse.s IL_00a4 + IL_0047: br.s IL_009d + IL_0049: ldloc.0 + IL_004a: brfalse.s IL_00b4 + IL_004c: ldloc.0 + IL_004d: isinst ""int?"" + IL_0052: unbox.any ""int?"" + IL_0057: stloc.2 + IL_0058: ldloca.s V_2 + IL_005a: call ""int int?.GetValueOrDefault()"" + IL_005f: stloc.1 + IL_0060: ldloca.s V_2 + IL_0062: call ""bool int?.HasValue.get"" + IL_0067: brfalse.s IL_00b4 + IL_0069: br.s IL_00ad + IL_006b: ldloc.0 + IL_006c: brfalse.s IL_00c4 + IL_006e: ldloc.0 + IL_006f: isinst ""int?"" + IL_0074: unbox.any ""int?"" + IL_0079: stloc.2 + IL_007a: ldloca.s V_2 + IL_007c: call ""int int?.GetValueOrDefault()"" + IL_0081: stloc.1 + IL_0082: ldloca.s V_2 + IL_0084: call ""bool int?.HasValue.get"" + IL_0089: brfalse.s IL_00c4 + IL_008b: br.s IL_00bd + IL_008d: ldsfld ""bool Program.b"" + IL_0092: brtrue.s IL_00ca + IL_0094: ldsfld ""bool Program.b"" + IL_0099: brtrue.s IL_00ca + IL_009b: br.s IL_0027 + IL_009d: ldsfld ""bool Program.b"" + IL_00a2: brtrue.s IL_00ca + IL_00a4: ldsfld ""bool Program.b"" + IL_00a9: brtrue.s IL_00ca + IL_00ab: br.s IL_0049 + IL_00ad: ldsfld ""bool Program.b"" + IL_00b2: brtrue.s IL_00ca + IL_00b4: ldsfld ""bool Program.b"" + IL_00b9: brtrue.s IL_00ca + IL_00bb: br.s IL_006b + IL_00bd: ldsfld ""bool Program.b"" + IL_00c2: brtrue.s IL_00ca + IL_00c4: ldsfld ""bool Program.b"" + IL_00c9: pop + IL_00ca: ret +}" + ); + compVerifier = CompileAndVerify(source, + options: TestOptions.DebugDll.WithOutputKind(OutputKind.ConsoleApplication), + expectedOutput: ""); + compVerifier.VerifyIL("Program.M", +@"{ + // Code size 276 (0x114) + .maxstack 1 + .locals init (object V_0, + int V_1, + int V_2, //i + int V_3, //i + int V_4, //i + int V_5, //i + object V_6, + int? V_7) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.s V_6 + IL_0004: ldloc.s V_6 + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: brtrue.s IL_000c + IL_000a: br.s IL_002c + IL_000c: ldloc.0 + IL_000d: isinst ""int?"" + IL_0012: unbox.any ""int?"" + IL_0017: stloc.s V_7 + IL_0019: ldloca.s V_7 + IL_001b: call ""int int?.GetValueOrDefault()"" + IL_0020: stloc.1 + IL_0021: ldloca.s V_7 + IL_0023: call ""bool int?.HasValue.get"" + IL_0028: brfalse.s IL_002c + IL_002a: br.s IL_00a8 + IL_002c: br IL_00b8 + IL_0031: ldloc.0 + IL_0032: brtrue.s IL_0036 + IL_0034: br.s IL_0056 + IL_0036: ldloc.0 + IL_0037: isinst ""int?"" + IL_003c: unbox.any ""int?"" + IL_0041: stloc.s V_7 IL_0043: ldloca.s V_7 - IL_0045: call ""bool Generic.Color?.HasValue.get"" - IL_004a: brfalse.s IL_0060 - IL_004c: ldloc.2 - IL_004d: stloc.s V_8 - IL_004f: ldloc.s V_8 - IL_0051: brfalse.s IL_005c - IL_0053: br.s IL_0055 - IL_0055: ldloc.s V_8 - IL_0057: ldc.i4.1 - IL_0058: beq.s IL_005e - IL_005a: br.s IL_0060 - IL_005c: br.s IL_007c - IL_005e: br.s IL_0085 - IL_0060: br.s IL_008e - IL_0062: ldloc.1 - IL_0063: stloc.s V_4 - IL_0065: br.s IL_0067 - IL_0067: ldstr ""Generic.Color."" - IL_006c: ldloc.s V_4 - IL_006e: box ""Generic.Color"" - IL_0073: call ""string string.Concat(object, object)"" - IL_0078: stloc.s V_9 - IL_007a: br.s IL_0097 - IL_007c: ldstr ""Generic.Color.Red"" - IL_0081: stloc.s V_9 - IL_0083: br.s IL_0097 - IL_0085: ldstr ""Generic.Color.Blue"" - IL_008a: stloc.s V_9 - IL_008c: br.s IL_0097 - IL_008e: ldstr ""None"" - IL_0093: stloc.s V_9 - IL_0095: br.s IL_0097 - IL_0097: ldloc.s V_9 - IL_0099: ret + IL_0045: call ""int int?.GetValueOrDefault()"" + IL_004a: stloc.1 + IL_004b: ldloca.s V_7 + IL_004d: call ""bool int?.HasValue.get"" + IL_0052: brfalse.s IL_0056 + IL_0054: br.s IL_00c6 + IL_0056: br.s IL_00d3 + IL_0058: ldloc.0 + IL_0059: brtrue.s IL_005d + IL_005b: br.s IL_007d + IL_005d: ldloc.0 + IL_005e: isinst ""int?"" + IL_0063: unbox.any ""int?"" + IL_0068: stloc.s V_7 + IL_006a: ldloca.s V_7 + IL_006c: call ""int int?.GetValueOrDefault()"" + IL_0071: stloc.1 + IL_0072: ldloca.s V_7 + IL_0074: call ""bool int?.HasValue.get"" + IL_0079: brfalse.s IL_007d + IL_007b: br.s IL_00e1 + IL_007d: br.s IL_00ef + IL_007f: ldloc.0 + IL_0080: brtrue.s IL_0084 + IL_0082: br.s IL_00a4 + IL_0084: ldloc.0 + IL_0085: isinst ""int?"" + IL_008a: unbox.any ""int?"" + IL_008f: stloc.s V_7 + IL_0091: ldloca.s V_7 + IL_0093: call ""int int?.GetValueOrDefault()"" + IL_0098: stloc.1 + IL_0099: ldloca.s V_7 + IL_009b: call ""bool int?.HasValue.get"" + IL_00a0: brfalse.s IL_00a4 + IL_00a2: br.s IL_00fa + IL_00a4: br.s IL_0108 + IL_00a6: br.s IL_0113 + IL_00a8: ldloc.1 + IL_00a9: stloc.2 + IL_00aa: ldsfld ""bool Program.b"" + IL_00af: brtrue.s IL_00b6 + IL_00b1: br IL_002c + IL_00b6: br.s IL_0113 + IL_00b8: ldsfld ""bool Program.b"" + IL_00bd: brtrue.s IL_00c4 + IL_00bf: br IL_0031 + IL_00c4: br.s IL_0113 + IL_00c6: ldloc.1 + IL_00c7: stloc.3 + IL_00c8: ldsfld ""bool Program.b"" + IL_00cd: brtrue.s IL_00d1 + IL_00cf: br.s IL_0056 + IL_00d1: br.s IL_0113 + IL_00d3: ldsfld ""bool Program.b"" + IL_00d8: brtrue.s IL_00df + IL_00da: br IL_0058 + IL_00df: br.s IL_0113 + IL_00e1: ldloc.1 + IL_00e2: stloc.s V_4 + IL_00e4: ldsfld ""bool Program.b"" + IL_00e9: brtrue.s IL_00ed + IL_00eb: br.s IL_007d + IL_00ed: br.s IL_0113 + IL_00ef: ldsfld ""bool Program.b"" + IL_00f4: brtrue.s IL_00f8 + IL_00f6: br.s IL_007f + IL_00f8: br.s IL_0113 + IL_00fa: ldloc.1 + IL_00fb: stloc.s V_5 + IL_00fd: ldsfld ""bool Program.b"" + IL_0102: brtrue.s IL_0106 + IL_0104: br.s IL_00a4 + IL_0106: br.s IL_0113 + IL_0108: ldsfld ""bool Program.b"" + IL_010d: brtrue.s IL_0111 + IL_010f: br.s IL_00a6 + IL_0111: br.s IL_0113 + IL_0113: ret +}" + ); + compVerifier.VerifyPdb( +@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [Fact, WorkItem(19280, "https://github.com/dotnet/roslyn/issues/19280")] + public void TestSignificanceOfDynamicVersusObjectAndTupleNamesInUniquenessOfPatternMatchingTemps() + { + var source = +@"using System; +public class Generic +{ +} +class Program +{ + public static void Main(string[] args) + { + var g = new Generic(); + M2(g, true, false, false); + M2(g, false, true, false); + M2(g, false, false, true); + } + public static void M2(object o, bool b1, bool b2, bool b3) + { + switch (o) + { + case Generic g when b1: Console.Write(""a""); break; + case var _ when b2: Console.Write(""b""); break; + case Generic g when b3: Console.Write(""c""); break; + } + } +} +"; + var compilation = CreateStandardCompilation(source, + options: TestOptions.DebugDll.WithOutputKind(OutputKind.ConsoleApplication), + references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }) + .VerifyDiagnostics(); + var compVerifier = CompileAndVerify(compilation, expectedOutput: "abc"); + compVerifier.VerifyIL("Program.M2", +@"{ + // Code size 105 (0x69) + .maxstack 2 + .locals init (object V_0, + Generic V_1, + Generic V_2, + Generic V_3, //g + Generic V_4, //g + object V_5) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.s V_5 + IL_0004: ldloc.s V_5 + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: brtrue.s IL_000c + IL_000a: br.s IL_0018 + IL_000c: ldloc.0 + IL_000d: isinst ""Generic"" + IL_0012: dup + IL_0013: stloc.1 + IL_0014: brfalse.s IL_0018 + IL_0016: br.s IL_002d + IL_0018: br.s IL_0041 + IL_001a: ldloc.0 + IL_001b: brtrue.s IL_001f + IL_001d: br.s IL_002b + IL_001f: ldloc.0 + IL_0020: isinst ""Generic"" + IL_0025: dup + IL_0026: stloc.2 + IL_0027: brfalse.s IL_002b + IL_0029: br.s IL_0053 + IL_002b: br.s IL_0068 + IL_002d: ldloc.1 + IL_002e: stloc.3 + IL_002f: ldarg.1 + IL_0030: brtrue.s IL_0034 + IL_0032: br.s IL_0018 + IL_0034: ldstr ""a"" + IL_0039: call ""void System.Console.Write(string)"" + IL_003e: nop + IL_003f: br.s IL_0068 + IL_0041: ldarg.2 + IL_0042: brtrue.s IL_0046 + IL_0044: br.s IL_001a + IL_0046: ldstr ""b"" + IL_004b: call ""void System.Console.Write(string)"" + IL_0050: nop + IL_0051: br.s IL_0068 + IL_0053: ldloc.2 + IL_0054: stloc.s V_4 + IL_0056: ldarg.3 + IL_0057: brtrue.s IL_005b + IL_0059: br.s IL_002b + IL_005b: ldstr ""c"" + IL_0060: call ""void System.Console.Write(string)"" + IL_0065: nop + IL_0066: br.s IL_0068 + IL_0068: ret }" ); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index fe10624070036..db307fc169220 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; @@ -10,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; @@ -40,7 +43,7 @@ public void Main() EmitResult emitResult; using (var output = new MemoryStream()) { - emitResult = compilation.Emit(output, null, null, null); + emitResult = compilation.Emit(output, pdbStream: null, xmlDocumentationStream: null, win32Resources: null); } emitResult.Diagnostics.Verify( @@ -48,7 +51,6 @@ public void Main() Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "x")); } - [Fact] public void CompilationEmitWithQuotedMainType() { @@ -148,7 +150,7 @@ namespace N.Foo; EmitResult emitResult; using (var output = new MemoryStream()) { - emitResult = comp.Emit(output, null, null, null); + emitResult = comp.Emit(output, pdbStream: null, xmlDocumentationStream: null, win32Resources: null); } Assert.False(emitResult.Success); @@ -236,6 +238,1274 @@ public static void Main() } } + [Fact] + public void EmitRefAssembly_PrivateMain() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class C +{ + internal static void Main() + { + System.Console.WriteLine(""hello""); + } +} +", options: TestOptions.DebugExe); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + // Previously, this would crash when trying to get the entry point for the ref assembly + // (but the Main method is not emitted in the ref assembly...) + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + VerifyEntryPoint(output, expectZero: false); + VerifyMethods(output, "C", new[] { "void C.Main()", "C..ctor()" }); + VerifyMvid(output, hasMvidSection: false); + + VerifyEntryPoint(metadataOutput, expectZero: true); + VerifyMethods(metadataOutput, "C", new[] { "C..ctor()" }); + VerifyMvid(metadataOutput, hasMvidSection: true); + } + + void VerifyEntryPoint(MemoryStream stream, bool expectZero) + { + stream.Position = 0; + int entryPoint = new PEHeaders(stream).CorHeader.EntryPointTokenOrRelativeVirtualAddress; + Assert.Equal(expectZero, entryPoint == 0); + } + } + + private class TestResourceSectionBuilder : ResourceSectionBuilder + { + public TestResourceSectionBuilder() + { + } + + protected override void Serialize(BlobBuilder builder, SectionLocation location) + { + builder.WriteInt32(0x12345678); + builder.WriteInt32(location.PointerToRawData); + builder.WriteInt32(location.RelativeVirtualAddress); + } + } + + private class TestPEBuilder : ManagedPEBuilder + { + public static readonly Guid s_mvid = Guid.Parse("a78fa2c3-854e-42bf-8b8d-75a450a6dc18"); + + public TestPEBuilder(PEHeaderBuilder header, + MetadataRootBuilder metadataRootBuilder, + BlobBuilder ilStream, + ResourceSectionBuilder nativeResources) + : base(header, metadataRootBuilder, ilStream, nativeResources: nativeResources) + { + } + + protected override ImmutableArray
CreateSections() + { + return base.CreateSections().Add( + new Section(".mvid", SectionCharacteristics.MemRead | + SectionCharacteristics.ContainsInitializedData | + SectionCharacteristics.MemDiscardable)); + } + + protected override BlobBuilder SerializeSection(string name, SectionLocation location) + { + if (name.Equals(".mvid", StringComparison.Ordinal)) + { + var sectionBuilder = new BlobBuilder(); + sectionBuilder.WriteGuid(s_mvid); + return sectionBuilder; + } + + return base.SerializeSection(name, location); + } + } + + [Fact] + public void MvidSectionNotFirst() + { + var ilBuilder = new BlobBuilder(); + var metadataBuilder = new MetadataBuilder(); + + var peBuilder = new TestPEBuilder( + PEHeaderBuilder.CreateLibraryHeader(), + new MetadataRootBuilder(metadataBuilder), + ilBuilder, + nativeResources: new TestResourceSectionBuilder()); + + var peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + + var peStream = new MemoryStream(); + peBlob.WriteContentTo(peStream); + + peStream.Position = 0; + using (var peReader = new PEReader(peStream)) + { + AssertEx.Equal(new[] { ".text", ".rsrc", ".reloc", ".mvid" }, + peReader.PEHeaders.SectionHeaders.Select(h => h.Name)); + + peStream.Position = 0; + var mvid = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(peStream); + Assert.Equal(TestPEBuilder.s_mvid, mvid); + } + } + + /// + /// Extract the MVID using two different methods (PEReader and MvidReader) and compare them. + /// We only expect an .mvid section in ref assemblies. + /// + private void VerifyMvid(MemoryStream stream, bool hasMvidSection) + { + stream.Position = 0; + using (var reader = new PEReader(stream)) + { + var metadataReader = reader.GetMetadataReader(); + Guid mvidFromModuleDefinition = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid); + + stream.Position = 0; + var mvidFromMvidReader = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(stream); + + Assert.NotEqual(Guid.Empty, mvidFromModuleDefinition); + if (hasMvidSection) + { + Assert.Equal(mvidFromModuleDefinition, mvidFromMvidReader); + } + else + { + Assert.Equal(Guid.Empty, mvidFromMvidReader); + } + } + } + + [Fact] + public void EmitRefAssembly_PrivatePropertySetter() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class C +{ + public int PrivateSetter { get; private set; } +} +"); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + VerifyMethods(output, "C", new[] { "System.Int32 C.k__BackingField", "System.Int32 C.PrivateSetter.get", "void C.PrivateSetter.set", + "C..ctor()", "System.Int32 C.PrivateSetter { get; private set; }" }); + VerifyMethods(metadataOutput, "C", new[] { "System.Int32 C.PrivateSetter.get", "C..ctor()", "System.Int32 C.PrivateSetter { get; }" }); + VerifyMvid(output, hasMvidSection: false); + VerifyMvid(metadataOutput, hasMvidSection: true); + } + } + + [Fact] + public void EmitRefAssembly_PrivatePropertyGetter() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class C +{ + public int PrivateGetter { private get; set; } +} +"); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + VerifyMethods(output, "C", new[] { "System.Int32 C.k__BackingField", "System.Int32 C.PrivateGetter.get", "void C.PrivateGetter.set", + "C..ctor()", "System.Int32 C.PrivateGetter { private get; set; }" }); + VerifyMethods(metadataOutput, "C", new[] { "void C.PrivateGetter.set", "C..ctor()", "System.Int32 C.PrivateGetter { set; }" }); + } + } + + [Fact] + public void EmitRefAssembly_PrivateIndexerGetter() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class C +{ + public int this[int i] { private get { return 0; } set { } } +} +"); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + VerifyMethods(output, "C", new[] { "System.Int32 C.this[System.Int32 i].get", "void C.this[System.Int32 i].set", + "C..ctor()", "System.Int32 C.this[System.Int32 i] { private get; set; }" }); + VerifyMethods(metadataOutput, "C", new[] { "void C.this[System.Int32 i].set", "C..ctor()", + "System.Int32 C.this[System.Int32 i] { set; }" }); + } + } + + [Fact] + public void EmitRefAssembly_SealedPropertyWithInternalInheritedGetter() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class Base +{ + public virtual int Property { internal get { return 0; } set { } } +} +public class C : Base +{ + public sealed override int Property { set { } } +} +"); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + emitResult.Diagnostics.Verify(); + Assert.True(emitResult.Success); + + VerifyMethods(output, "C", new[] { "void C.Property.set", "C..ctor()", "System.Int32 C.Property.get", "System.Int32 C.Property { internal get; set; }" }); + // A getter is synthesized on C.Property so that it can be marked as sealed. It is emitted despite being internal because it is virtual. + VerifyMethods(metadataOutput, "C", new[] { "void C.Property.set", "C..ctor()", "System.Int32 C.Property.get", "System.Int32 C.Property { internal get; set; }" }); + } + } + + [Fact] + public void EmitRefAssembly_PrivateAccessorOnEvent() + { + CSharpCompilation comp = CreateStandardCompilation(@" +public class C +{ + public event System.Action PrivateAdder { private add { } remove { } } + public event System.Action PrivateRemover { add { } private remove { } } +} +"); + comp.VerifyDiagnostics( + // (4,47): error CS1609: Modifiers cannot be placed on event accessor declarations + // public event System.Action PrivateAdder { private add { } remove { } } + Diagnostic(ErrorCode.ERR_NoModifiersOnAccessor, "private").WithLocation(4, 47), + // (5,57): error CS1609: Modifiers cannot be placed on event accessor declarations + // public event System.Action PrivateRemover { add { } private remove { } } + Diagnostic(ErrorCode.ERR_NoModifiersOnAccessor, "private").WithLocation(5, 57) + ); + } + + private static void VerifyMethods(MemoryStream stream, string containingType, string[] expectedMethods) + { + stream.Position = 0; + var metadataRef = AssemblyMetadata.CreateFromImage(stream.ToArray()).GetReference(); + + var compWithMetadata = CreateCompilation("", references: new[] { MscorlibRef, metadataRef }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + AssertEx.Equal( + expectedMethods, + compWithMetadata.GetMember(containingType).GetMembers().Select(m => m.ToTestDisplayString())); + } + + [Fact] + public void RefAssembly_HasReferenceAssemblyAttribute() + { + var emitRefAssembly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + + Action assemblyValidator = assembly => + { + var reader = assembly.GetMetadataReader(); + var attributes = reader.GetAssemblyDefinition().GetCustomAttributes(); + AssertEx.Equal(new[] { + "MemberReference:Void System.Runtime.CompilerServices.CompilationRelaxationsAttribute.ctor(Int32)", + "MemberReference:Void System.Runtime.CompilerServices.RuntimeCompatibilityAttribute.ctor()", + "MemberReference:Void System.Diagnostics.DebuggableAttribute.ctor(DebuggingModes)", + "MemberReference:Void System.Runtime.CompilerServices.ReferenceAssemblyAttribute.ctor()" + }, + attributes.Select(a => MetadataReaderUtils.Dump(reader, reader.GetCustomAttribute(a).Constructor))); + }; + + CompileAndVerify("", emitOptions: emitRefAssembly, assemblyValidator: assemblyValidator); + } + + [Fact] + public void RefAssembly_HandlesMissingReferenceAssemblyAttribute() + { + var emitRefAssembly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + + Action assemblyValidator = assembly => + { + var reader = assembly.GetMetadataReader(); + var attributes = reader.GetAssemblyDefinition().GetCustomAttributes(); + AssertEx.SetEqual(attributes.Select(a => MetadataReaderUtils.Dump(reader, reader.GetCustomAttribute(a).Constructor)), + new string[] { + "MemberReference:Void System.Runtime.CompilerServices.CompilationRelaxationsAttribute.ctor(Int32)", + "MemberReference:Void System.Runtime.CompilerServices.RuntimeCompatibilityAttribute.ctor()", + "MemberReference:Void System.Diagnostics.DebuggableAttribute.ctor(DebuggingModes)" + }); + }; + + var comp = CreateStandardCompilation(""); + comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_ReferenceAssemblyAttribute__ctor); + CompileAndVerify(compilation: comp, emitOptions: emitRefAssembly, assemblyValidator: assemblyValidator); + } + + [Fact] + public void RefAssembly_ReferenceAssemblyAttributeAlsoInSource() + { + var emitRefAssembly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + + Action assemblyValidator = assembly => + { + var reader = assembly.GetMetadataReader(); + var attributes = reader.GetAssemblyDefinition().GetCustomAttributes(); + AssertEx.Equal(new string[] { + "MemberReference:Void System.Runtime.CompilerServices.CompilationRelaxationsAttribute.ctor(Int32)", + "MemberReference:Void System.Runtime.CompilerServices.RuntimeCompatibilityAttribute.ctor()", + "MemberReference:Void System.Diagnostics.DebuggableAttribute.ctor(DebuggingModes)", + "MemberReference:Void System.Runtime.CompilerServices.ReferenceAssemblyAttribute.ctor()" + }, + attributes.Select(a => MetadataReaderUtils.Dump(reader, reader.GetCustomAttribute(a).Constructor))); + }; + string source = @"[assembly:System.Runtime.CompilerServices.ReferenceAssembly()]"; + CompileAndVerify(source, emitOptions: emitRefAssembly, assemblyValidator: assemblyValidator); + } + + [Theory] + [InlineData("public int M() { return 1; }", "public int M() { return 2; }", Match.BothMetadataAndRefOut)] + [InlineData("public int M() { return 1; }", "public int M() { error(); }", Match.BothMetadataAndRefOut)] + [InlineData("private void M() { }", "", Match.RefOut)] + [InlineData("internal void M() { }", "", Match.RefOut)] + [InlineData("private void M() { dynamic x = 1; }", "", Match.RefOut)] // no reference added from method bodies + [InlineData(@"private void M() { var x = new { id = 1 }; }", "", Match.RefOut)] + [InlineData("private int P { get { Error(); } set { Error(); } }", "", Match.RefOut)] // errors in methods bodies don't matter + [InlineData("public int P { get; set; }", "", Match.Different)] + [InlineData("protected int P { get; set; }", "", Match.Different)] + [InlineData("private int P { get; set; }", "", Match.RefOut)] // private auto-property and underlying field are removed + [InlineData("internal int P { get; set; }", "", Match.RefOut)] + [InlineData("private event Action E { add { Error(); } remove { Error(); } }", "", Match.RefOut)] + [InlineData("internal event Action E { add { Error(); } remove { Error(); } }", "", Match.RefOut)] + [InlineData("private class C2 { }", "", Match.Different)] // all types are included + [InlineData("private struct S { }", "", Match.Different)] + [InlineData("public struct S { private int i; }", "public struct S { }", Match.Different)] + [InlineData("private int i;", "", Match.RefOut)] + [InlineData("public C() { }", "", Match.BothMetadataAndRefOut)] + public void RefAssembly_InvariantToSomeChanges(string left, string right, Match expectedMatch) + { + string sourceTemplate = @" +using System; +public class C +{ + CHANGE +} +"; + + CompareAssemblies(sourceTemplate, left, right, expectedMatch, includePrivateMembers: true); + CompareAssemblies(sourceTemplate, left, right, expectedMatch, includePrivateMembers: false); + } + + [ConditionalFact(typeof(ClrOnly), typeof(DesktopOnly))] + public void RefAssembly_NoPia() + { + string piaSource = @" +using System; +using System.Runtime.InteropServices; + +[assembly: Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58257"")] +[assembly: ImportedFromTypeLib(""Pia1.dll"")] + +public struct S { public int field; } + +[ComImport()] +[Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58280"")] +public interface ITest1 +{ + S M(); +}"; + + var pia = CreateStandardCompilation(piaSource, options: TestOptions.ReleaseDll, assemblyName: "pia"); + pia.VerifyEmitDiagnostics(); + + string source = @" +public class D : ITest1 +{ + public S M() + { + throw null; + } +} +"; + var piaImageReference = pia.EmitToImageReference(embedInteropTypes: true); + verifyRefOnly(piaImageReference); + verifyRefOut(piaImageReference); + + var piaMetadataReference = pia.ToMetadataReference(embedInteropTypes: true); + verifyRefOnly(piaMetadataReference); + verifyRefOut(piaMetadataReference); + + void verifyRefOnly(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + var refOnlyImage = EmitRefOnly(comp); + verifyNoPia(refOnlyImage); + } + + void verifyRefOut(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.DebugDll, + references: new MetadataReference[] { reference }); + var (image, refImage) = EmitRefOut(comp); + verifyNoPia(image); + verifyNoPia(refImage); + } + + void verifyNoPia(ImmutableArray image) + { + var reference = CompilationVerifier.LoadTestEmittedExecutableForSymbolValidation(image, OutputKind.DynamicallyLinkedLibrary); + var comp = CreateStandardCompilation("", references: new[] { reference }); + var referencedAssembly = comp.GetReferencedAssemblySymbol(reference); + var module = (PEModuleSymbol)referencedAssembly.Modules[0]; + + var itest1 = module.GlobalNamespace.GetMember("ITest1"); + Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")); + + var method = (PEMethodSymbol)itest1.GetMember("M"); + Assert.Equal("S ITest1.M()", method.ToTestDisplayString()); + + var s = (NamedTypeSymbol)method.ReturnType; + Assert.Equal("S", s.ToTestDisplayString()); + Assert.NotNull(s.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")); + + var field = s.GetMember("field"); + Assert.Equal("System.Int32 S.field", field.ToTestDisplayString()); + } + } + + [ConditionalFact(typeof(ClrOnly), typeof(DesktopOnly))] + public void RefAssembly_NoPia_ReferenceFromMethodBody() + { + string piaSource = @" +using System; +using System.Runtime.InteropServices; + +[assembly: Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58257"")] +[assembly: ImportedFromTypeLib(""Pia1.dll"")] + +public struct S { public int field; } + +[ComImport()] +[Guid(""f9c2d51d-4f44-45f0-9eda-c9d599b58280"")] +public interface ITest1 +{ + S M(); +}"; + + var pia = CreateStandardCompilation(piaSource, options: TestOptions.ReleaseDll, assemblyName: "pia"); + pia.VerifyEmitDiagnostics(); + + string source = @" +public class D +{ + public void M2() + { + ITest1 x = null; + S s = x.M(); + } +} +"; + var piaImageReference = pia.EmitToImageReference(embedInteropTypes: true); + verifyRefOnly(piaImageReference); + verifyRefOut(piaImageReference); + + var piaMetadataReference = pia.ToMetadataReference(embedInteropTypes: true); + verifyRefOnly(piaMetadataReference); + verifyRefOut(piaMetadataReference); + + void verifyRefOnly(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, + options: new EmitOptions(includePrivateMembers: false).WithEmitMetadataOnly(true)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var refImage = output.ToImmutable(); + verifyNoPia(refImage, expectMissing: true); + } + } + + void verifyRefOut(MetadataReference reference) + { + var comp = CreateStandardCompilation(source, options: TestOptions.ReleaseDll, + references: new MetadataReference[] { reference }); + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + EmitResult emitResult = comp.Emit(output, metadataPEStream: metadataOutput, + options: new EmitOptions(includePrivateMembers: false)); + Assert.True(emitResult.Success); + emitResult.Diagnostics.Verify(); + + var image = output.ToImmutable(); + var refImage = metadataOutput.ToImmutable(); + + verifyNoPia(image, expectMissing: false); + verifyNoPia(refImage, expectMissing: false); + } + } + + // The ref assembly produced by refout has more types than that produced by refonly, + // because refout will bind the method bodies (and therefore populate more referenced types). + // This will be refined in the future. Follow-up issue: https://github.com/dotnet/roslyn/issues/19403 + void verifyNoPia(ImmutableArray image, bool expectMissing) + { + var reference = CompilationVerifier.LoadTestEmittedExecutableForSymbolValidation(image, OutputKind.DynamicallyLinkedLibrary); + var comp = CreateStandardCompilation("", references: new[] { reference }); + var referencedAssembly = comp.GetReferencedAssemblySymbol(reference); + var module = (PEModuleSymbol)referencedAssembly.Modules[0]; + + var itest1 = module.GlobalNamespace.GetMember("ITest1"); + if (expectMissing) + { + Assert.Null(itest1); + Assert.Null(module.GlobalNamespace.GetMember("S")); + return; + } + + Assert.NotNull(itest1.GetAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute").ToString()); + + var method = (PEMethodSymbol)itest1.GetMember("M"); + Assert.Equal("S ITest1.M()", method.ToTestDisplayString()); + + var s = (NamedTypeSymbol)method.ReturnType; + Assert.Equal("S", s.ToTestDisplayString()); + + var field = s.GetMember("field"); + Assert.Equal("System.Int32 S.field", field.ToTestDisplayString()); + } + } + + [Theory] + [InlineData("internal void M() { }", "", Match.Different)] + public void RefAssembly_InvariantToSomeChangesWithInternalsVisibleTo(string left, string right, Match expectedMatch) + { + string sourceTemplate = @" +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleToAttribute(""Friend"")] +public class C +{ + CHANGE +} +"; + + CompareAssemblies(sourceTemplate, left, right, expectedMatch, includePrivateMembers: true); + CompareAssemblies(sourceTemplate, left, right, expectedMatch, includePrivateMembers: false); + } + + public enum Match + { + BothMetadataAndRefOut, + RefOut, + Different + } + + /// + /// Are the metadata-only assemblies identical with two source code modifications? + /// Metadata-only assemblies can either include private/internal members or not. + /// + private void CompareAssemblies(string sourceTemplate, string change1, string change2, Match expectedMatch, bool includePrivateMembers) + { + bool expectMatch = includePrivateMembers ? + expectedMatch == Match.BothMetadataAndRefOut : + (expectedMatch == Match.BothMetadataAndRefOut || expectedMatch == Match.RefOut); + + string name = GetUniqueName(); + string source1 = sourceTemplate.Replace("CHANGE", change1); + CSharpCompilation comp1 = CreateStandardCompilation(Parse(source1), + options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); + ImmutableArray image1 = comp1.EmitToArray(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); + + var source2 = sourceTemplate.Replace("CHANGE", change2); + Compilation comp2 = CreateStandardCompilation(Parse(source2), + options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); + ImmutableArray image2 = comp2.EmitToArray(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); + + if (expectMatch) + { + AssertEx.Equal(image1, image2, message: $"Expecting match for includePrivateMembers={includePrivateMembers} case, but differences were found."); + } + else + { + AssertEx.NotEqual(image1, image2, message: $"Expecting difference for includePrivateMembers={includePrivateMembers} case, but they matched."); + } + + var mvid1 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image1.DangerousGetUnderlyingArray())); + var mvid2 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image2.DangerousGetUnderlyingArray())); + + if (!includePrivateMembers) + { + Assert.NotEqual(Guid.Empty, mvid1); + Assert.Equal(expectMatch, mvid1 == mvid2); + } + else + { + Assert.Equal(Guid.Empty, mvid1); + Assert.Equal(Guid.Empty, mvid2); + } + } + + [Fact] + public void RefAssemblyClient_StructWithPrivateReferenceTypeField() + { + VerifyRefAssemblyClient(@" +public struct S +{ + private object _field; + public static S GetValue() => new S() { _field = new object() }; +}", +@"class C +{ + void M() + { + unsafe + { + System.Console.WriteLine(sizeof(S*)); + } + } +}", +comp => comp.VerifyDiagnostics( + // (7,45): error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type ('S') + // System.Console.WriteLine(sizeof(S*)); + Diagnostic(ErrorCode.ERR_ManagedAddr, "S*").WithArguments("S").WithLocation(7, 45) + )); + } + + [Fact] + public void RefAssemblyClient_EmitAllNestedTypes() + { + VerifyRefAssemblyClient(@" +public interface I1 { } +public interface I2 { } +public class A: I1 +{ + private class X: I2 { } +}", +@"class C +{ + I1 M(A a) + { + return (I1)a; + } +}", +comp => comp.VerifyDiagnostics()); + } + + [Fact] + public void RefAssemblyClient_EmitAllTypes() + { + VerifyRefAssemblyClient(@" +public interface I1 { } +public interface I2 { } +public class A: I1 { } +internal class X: I2 { } +", +@"class C +{ + I1 M(A a) + { + return (I1)a; + } +}", +comp => comp.VerifyDiagnostics()); + } + + [Fact] + public void RefAssemblyClient_StructWithPrivateGenericField() + { + VerifyRefAssemblyClient(@" +public struct Container +{ + private T contained; +}", +@"public struct Usage +{ + public Container x; +}", +comp => comp.VerifyDiagnostics( + // (3,29): error CS0523: Struct member 'Usage.x' of type 'Container' causes a cycle in the struct layout + // public Container x; + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "x").WithArguments("Usage.x", "Container").WithLocation(3, 29) + )); + } + + [Fact] + public void RefAssemblyClient_EmitAllVirtualMethods() + { + + var comp1 = CreateCSharpCompilation("CS1", +@"[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""CS2"")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""CS3"")] +public abstract class C1 +{ + internal abstract void M(); +}", + referencedAssemblies: new[] { MscorlibRef }); + comp1.VerifyDiagnostics(); + var image1 = comp1.EmitToImageReference(EmitOptions.Default); + + var comp2 = CreateCSharpCompilation("CS2", +@"public abstract class C2 : C1 +{ + internal override void M() { } +}", + referencedAssemblies: new[] { MscorlibRef, image1 }); + var image2 = comp2.EmitToImageReference(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false)); + + // If internal virtual methods were not included in ref assemblies, then C3 could not be concrete and would report + // error CS0534: 'C3' does not implement inherited abstract member 'C1.M()' + + var comp3 = CreateCSharpCompilation("CS3", +@"public class C3 : C2 +{ +}", + referencedAssemblies: new[] { MscorlibRef, image1, image2 }); + comp3.VerifyDiagnostics(); + } + + [Fact] + public void RefAssemblyClient_StructWithPrivateIntField() + { + VerifyRefAssemblyClient(@" +public struct S +{ + private int i; +}", +@"class C +{ + string M() + { + S s; + return s.ToString(); + } +}", +comp => comp.VerifyDiagnostics( + // (6,16): error CS0165: Use of unassigned local variable 's' + // return s.ToString(); + Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(6, 16) + )); + } + + /// + /// The client compilation should not be affected (except for some diagnostic differences) + /// by the library assembly only having metadata, or not including private members. + /// + private void VerifyRefAssemblyClient(string lib_cs, string client_cs, Action validator, int debugFlag = -1) + { + // Whether the library is compiled in full, as metadata-only, or as a ref assembly should be transparent + // to the client and the validator should be able to verify the same expectations. + + if (debugFlag == -1 || debugFlag == 0) + { + VerifyRefAssemblyClient(lib_cs, client_cs, validator, + EmitOptions.Default.WithEmitMetadataOnly(false)); + } + + if (debugFlag == -1 || debugFlag == 1) + { + VerifyRefAssemblyClient(lib_cs, client_cs, validator, + EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(true)); + } + + if (debugFlag == -1 || debugFlag == 2) + { + VerifyRefAssemblyClient(lib_cs, client_cs, validator, + EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false)); + } + } + + private static void VerifyRefAssemblyClient(string lib_cs, string source, Action validator, EmitOptions emitOptions) + { + string name = GetUniqueName(); + var libComp = CreateStandardCompilation(Parse(lib_cs), + options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); + var libImage = libComp.EmitToImageReference(emitOptions); + + var comp = CreateStandardCompilation(source, references: new[] { libImage }, options: TestOptions.DebugDll.WithAllowUnsafe(true)); + validator(comp); + } + + [Theory] + [InlineData("", false)] + [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1"")]", false)] + [InlineData(@"[assembly: System.Reflection.AssemblyVersion(""1.0.0.*"")]", true)] + public void RefAssembly_EmitAsDeterministic(string source, bool hasWildcard) + { + var name = GetUniqueName(); + var options = TestOptions.DebugDll.WithDeterministic(false); + var comp1 = CreateStandardCompilation(source, options: options, assemblyName: name); + + var (out1, refOut1) = EmitRefOut(comp1); + var refOnly1 = EmitRefOnly(comp1); + VerifyIdentitiesMatch(out1, refOut1); + VerifyIdentitiesMatch(out1, refOnly1); + AssertEx.Equal(refOut1, refOut1); + + // The resolution of the PE header time date stamp is seconds (divided by two), and we want to make sure that has an opportunity to change + // between calls to Emit. + Thread.Sleep(TimeSpan.FromSeconds(3)); + + // Re-using the same compilation results in the same time stamp + var (out15, refOut15) = EmitRefOut(comp1); + VerifyIdentitiesMatch(out1, out15); + VerifyIdentitiesMatch(refOut1, refOut15); + AssertEx.Equal(refOut1, refOut15); + + // Using a new compilation results in new time stamp + var comp2 = CreateStandardCompilation(source, options: options, assemblyName: name); + var (out2, refOut2) = EmitRefOut(comp2); + var refOnly2 = EmitRefOnly(comp2); + VerifyIdentitiesMatch(out2, refOut2); + VerifyIdentitiesMatch(out2, refOnly2); + + VerifyIdentitiesMatch(out1, out2, expectMatch: !hasWildcard); + VerifyIdentitiesMatch(refOut1, refOut2, expectMatch: !hasWildcard); + + if (hasWildcard) + { + AssertEx.NotEqual(refOut1, refOut2); + AssertEx.NotEqual(refOut1, refOnly2); + } + else + { + // If no wildcards, the binaries are emitted deterministically + AssertEx.Equal(refOut1, refOut2); + AssertEx.Equal(refOut1, refOnly2); + } + } + + private void VerifySigned(ImmutableArray image, bool expectSigned = true) + { + using (var reader = new PEReader(image)) + { + var flags = reader.PEHeaders.CorHeader.Flags; + Assert.Equal(expectSigned, flags.HasFlag(CorFlags.StrongNameSigned)); + } + } + + private static void VerifyIdentitiesMatch(ImmutableArray firstImage, ImmutableArray secondImage, + bool expectMatch = true, bool expectPublicKey = false) + { + var id1 = ModuleMetadata.CreateFromImage(firstImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + var id2 = ModuleMetadata.CreateFromImage(secondImage).GetMetadataReader().ReadAssemblyIdentityOrThrow(); + Assert.Equal(expectMatch, id1 == id2); + if (expectPublicKey) + { + Assert.True(id1.HasPublicKey); + Assert.True(id2.HasPublicKey); + } + } + + private static (ImmutableArray image, ImmutableArray refImage) EmitRefOut(CSharpCompilation comp) + { + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + var options = EmitOptions.Default.WithIncludePrivateMembers(false); + comp.VerifyEmitDiagnostics(); + var result = comp.Emit(output, metadataPEStream: metadataOutput, + options: options); + return (output.ToImmutable(), metadataOutput.ToImmutable()); + } + } + + private static ImmutableArray EmitRefOnly(CSharpCompilation comp) + { + using (var output = new MemoryStream()) + { + var options = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + comp.VerifyEmitDiagnostics(); + var result = comp.Emit(output, + options: options); + return output.ToImmutable(); + } + } + + [Fact] + public void RefAssembly_PublicSigning() + { + var snk = Temp.CreateFile().WriteAllBytes(TestResources.General.snKey); + + var comp = CreateStandardCompilation("public class C{}", + options: TestOptions.ReleaseDll.WithCryptoKeyFile(snk.Path).WithPublicSign(true)); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifySigned(image); + VerifySigned(refImage); + VerifySigned(refOnlyImage); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); + } + + [Fact] + public void RefAssembly_StrongNameProvider() + { + var signedDllOptions = TestOptions.ReleaseDll. + WithCryptoKeyFile(SigningTestHelpers.KeyPairFile). + WithStrongNameProvider(new SigningTestHelpers.VirtualizedStrongNameProvider(ImmutableArray.Empty)); + + var comp = CreateStandardCompilation("public class C{}", options: signedDllOptions); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifySigned(image); + VerifySigned(refImage); + VerifySigned(refOnlyImage); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); + } + + [Fact] + public void RefAssembly_StrongNameProviderAndDelaySign() + { + var signedDllOptions = TestOptions.ReleaseDll + .WithCryptoKeyFile(SigningTestHelpers.KeyPairFile) + .WithDelaySign(true) + .WithStrongNameProvider(new SigningTestHelpers.VirtualizedStrongNameProvider(ImmutableArray.Empty)); + + var comp = CreateStandardCompilation("public class C{}", options: signedDllOptions); + + comp.VerifyDiagnostics(); + var (image, refImage) = EmitRefOut(comp); + var refOnlyImage = EmitRefOnly(comp); + VerifySigned(image, expectSigned: false); + VerifySigned(refImage, expectSigned: false); + VerifySigned(refOnlyImage, expectSigned: false); + VerifyIdentitiesMatch(image, refImage, expectPublicKey: true); + VerifyIdentitiesMatch(image, refOnlyImage, expectPublicKey: true); + } + + [Theory] + [InlineData("public int M() { error(); }", true)] + [InlineData("public int M() { error() }", false)] // This may get relaxed. See follow-up issue https://github.com/dotnet/roslyn/issues/17612 + [InlineData("public int M();", true)] + [InlineData("public int M() { int Local(); }", true)] + [InlineData("public C();", true)] + [InlineData("~ C();", true)] + [InlineData("public Error M() { return null; }", false)] // This may get relaxed. See follow-up issue https://github.com/dotnet/roslyn/issues/17612 + [InlineData("public static explicit operator C(int i);", true)] + [InlineData("public async Task M();", false)] + [InlineData("partial void M(); partial void M();", false)] // This may get relaxed. See follow-up issue https://github.com/dotnet/roslyn/issues/17612 + public void RefAssembly_IgnoresSomeDiagnostics(string change, bool expectSuccess) + { + string sourceTemplate = @" +using System.Threading.Tasks; +public partial class C +{ + CHANGE +} +"; + VerifyIgnoresDiagnostics(EmitOptions.Default.WithEmitMetadataOnly(false).WithTolerateErrors(false), success: false); + VerifyIgnoresDiagnostics(EmitOptions.Default.WithEmitMetadataOnly(true).WithTolerateErrors(false), success: expectSuccess); + + void VerifyIgnoresDiagnostics(EmitOptions emitOptions, bool success) + { + string source = sourceTemplate.Replace("CHANGE", change); + string name = GetUniqueName(); + CSharpCompilation comp = CreateStandardCompilation(Parse(source), + options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); + + using (var output = new MemoryStream()) + { + var emitResult = comp.Emit(output, options: emitOptions); + Assert.Equal(!success, emitResult.Diagnostics.HasAnyErrors()); + Assert.Equal(success, emitResult.Success); + } + } + } + + [Fact] + public void RefAssembly_VerifyTypesAndMembers() + { + string source = @" +public class PublicClass +{ + public void PublicMethod() { System.Console.Write(new { anonymous = 1 }); } + private void PrivateMethod() { System.Console.Write(""Hello""); } + protected void ProtectedMethod() { System.Console.Write(""Hello""); } + internal void InternalMethod() { System.Console.Write(""Hello""); } + public event System.Action PublicEvent; + internal event System.Action InternalEvent; +} +"; + CSharpCompilation comp = CreateCompilation(source, references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + // verify metadata (types, members, attributes) of the regular assembly + CompileAndVerify(comp, emitOptions: EmitOptions.Default, verify: true); + + var realImage = comp.EmitToImageReference(EmitOptions.Default); + var compWithReal = CreateCompilation("", references: new[] { MscorlibRef, realImage }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + AssertEx.Equal( + new[] { "", "<>f__AnonymousType0<j__TPar>", "PublicClass" }, + compWithReal.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); + + AssertEx.Equal( + new[] { "void PublicClass.PublicMethod()", "void PublicClass.PrivateMethod()", + "void PublicClass.ProtectedMethod()", "void PublicClass.InternalMethod()", + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "void PublicClass.InternalEvent.add", "void PublicClass.InternalEvent.remove", + "PublicClass..ctor()", + "event System.Action PublicClass.PublicEvent", "event System.Action PublicClass.InternalEvent" }, + compWithReal.GetMember("PublicClass").GetMembers() + .Select(m => m.ToTestDisplayString())); + + AssertEx.Equal( + new[] { "System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute", + "System.Diagnostics.DebuggableAttribute" }, + compWithReal.SourceModule.GetReferencedAssemblySymbols().Last().GetAttributes().Select(a => a.AttributeClass.ToTestDisplayString())); + + // verify metadata (types, members, attributes) of the metadata-only assembly + var emitMetadataOnly = EmitOptions.Default.WithEmitMetadataOnly(true); + CompileAndVerify(comp, emitOptions: emitMetadataOnly, verify: true); + + var metadataImage = comp.EmitToImageReference(emitMetadataOnly); + var compWithMetadata = CreateCompilation("", references: new[] { MscorlibRef, metadataImage }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + AssertEx.Equal( + new[] { "", "PublicClass" }, + compWithMetadata.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); + + AssertEx.Equal( + new[] { "void PublicClass.PublicMethod()", "void PublicClass.PrivateMethod()", + "void PublicClass.ProtectedMethod()", "void PublicClass.InternalMethod()", + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "void PublicClass.InternalEvent.add", "void PublicClass.InternalEvent.remove", + "PublicClass..ctor()", + "event System.Action PublicClass.PublicEvent", "event System.Action PublicClass.InternalEvent" }, + compWithMetadata.GetMember("PublicClass").GetMembers().Select(m => m.ToTestDisplayString())); + + AssertEx.Equal( + new[] { "System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute", + "System.Diagnostics.DebuggableAttribute" }, + compWithMetadata.SourceModule.GetReferencedAssemblySymbols().Last().GetAttributes().Select(a => a.AttributeClass.ToTestDisplayString())); + + MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitMetadataOnly)); + + // verify metadata (types, members, attributes) of the ref assembly + var emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + CompileAndVerify(comp, emitOptions: emitRefOnly, verify: true); + + var refImage = comp.EmitToImageReference(emitRefOnly); + var compWithRef = CreateCompilation("", references: new[] { MscorlibRef, refImage }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + AssertEx.Equal( + new[] { "", "PublicClass" }, + compWithRef.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); + + AssertEx.Equal( + new[] { "void PublicClass.PublicMethod()", "void PublicClass.ProtectedMethod()", + "void PublicClass.PublicEvent.add", "void PublicClass.PublicEvent.remove", + "PublicClass..ctor()", "event System.Action PublicClass.PublicEvent"}, + compWithRef.GetMember("PublicClass").GetMembers().Select(m => m.ToTestDisplayString())); + + AssertEx.Equal( + new[] { + "System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute", + "System.Diagnostics.DebuggableAttribute", + "System.Runtime.CompilerServices.ReferenceAssemblyAttribute" }, + compWithRef.SourceModule.GetReferencedAssemblySymbols().Last().GetAttributes().Select(a => a.AttributeClass.ToTestDisplayString())); + + MetadataReaderUtils.AssertEmptyOrThrowNull(comp.EmitToArray(emitRefOnly)); + } + + [Fact] + public void RefAssembly_VerifyTypesAndMembersOnStruct() + { + string source = @" +internal struct InternalStruct +{ + internal int P { get; set; } +} +"; + CSharpCompilation comp = CreateCompilation(source, references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + // verify metadata (types, members, attributes) of the ref assembly + var emitRefOnly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + CompileAndVerify(comp, emitOptions: emitRefOnly, verify: true); + + var refImage = comp.EmitToImageReference(emitRefOnly); + var compWithRef = CreateCompilation("", references: new[] { MscorlibRef, refImage }, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + AssertEx.Equal( + new[] { "", "InternalStruct" }, + compWithRef.SourceModule.GetReferencedAssemblySymbols().Last().GlobalNamespace.GetMembers().Select(m => m.ToDisplayString())); + + AssertEx.Equal( + new[] { "System.Int32 InternalStruct.

k__BackingField", "InternalStruct..ctor()" }, + compWithRef.GetMember("InternalStruct").GetMembers().Select(m => m.ToTestDisplayString())); + } + + [Fact] + public void EmitMetadataOnly_DisallowPdbs() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + using (var output = new MemoryStream()) + using (var pdbOutput = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, pdbOutput, + options: EmitOptions.Default.WithEmitMetadataOnly(true))); + } + } + + [Fact] + public void EmitMetadataOnly_DisallowMetadataPeStream() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + using (var output = new MemoryStream()) + using (var metadataPeOutput = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, metadataPEStream: metadataPeOutput, + options: EmitOptions.Default.WithEmitMetadataOnly(true))); + } + } + + [Fact] + public void IncludePrivateMembers_DisallowMetadataPeStream() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + using (var output = new MemoryStream()) + using (var metadataPeOutput = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, metadataPEStream: metadataPeOutput, + options: EmitOptions.Default.WithIncludePrivateMembers(true))); + } + } + + [Fact] + public void EmitMetadata_DisallowOutputtingNetModule() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true).WithOutputKind(OutputKind.NetModule)); + + using (var output = new MemoryStream()) + using (var metadataPeOutput = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, metadataPEStream: metadataPeOutput, + options: EmitOptions.Default)); + } + } + + [Fact] + public void EmitMetadataOnly_DisallowOutputtingNetModule() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true).WithOutputKind(OutputKind.NetModule)); + + using (var output = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, + options: EmitOptions.Default.WithEmitMetadataOnly(true))); + } + } + + [Fact] + public void RefAssembly_AllowEmbeddingPdb() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll); + + using (var output = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + var result = comp.Emit(output, metadataPEStream: metadataOutput, + options: EmitOptions.Default.WithDebugInformationFormat(DebugInformationFormat.Embedded).WithIncludePrivateMembers(false)); + + VerifyEmbeddedDebugInfo(output, new[] { DebugDirectoryEntryType.CodeView, DebugDirectoryEntryType.EmbeddedPortablePdb }); + VerifyEmbeddedDebugInfo(metadataOutput, new DebugDirectoryEntryType[] { DebugDirectoryEntryType.Reproducible }); + } + + void VerifyEmbeddedDebugInfo(MemoryStream stream, DebugDirectoryEntryType[] expected) + { + using (var peReader = new PEReader(stream.ToImmutable())) + { + var entries = peReader.ReadDebugDirectory(); + AssertEx.Equal(expected, entries.Select(e => e.Type)); + } + } + } + + [Fact] + public void EmitMetadataOnly_DisallowEmbeddingPdb() + { + CSharpCompilation comp = CreateCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll); + + using (var output = new MemoryStream()) + { + Assert.Throws(() => comp.Emit(output, + options: EmitOptions.Default.WithEmitMetadataOnly(true) + .WithDebugInformationFormat(DebugInformationFormat.Embedded))); + } + } + + [Fact] + public void EmitMetadata() + { + string source = @" +public abstract class PublicClass +{ + public void PublicMethod() { System.Console.Write(""Hello""); } +} +"; + CSharpCompilation comp = CreateCompilation(source, references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + using (var output = new MemoryStream()) + using (var pdbOutput = new MemoryStream()) + using (var metadataOutput = new MemoryStream()) + { + var result = comp.Emit(output, pdbOutput, metadataPEStream: metadataOutput); + Assert.True(result.Success); + Assert.NotEqual(0, output.Position); + Assert.NotEqual(0, pdbOutput.Position); + Assert.NotEqual(0, metadataOutput.Position); + MetadataReaderUtils.AssertNotThrowNull(ImmutableArray.CreateRange(output.GetBuffer())); + MetadataReaderUtils.AssertEmptyOrThrowNull(ImmutableArray.CreateRange(metadataOutput.GetBuffer())); + } + + var peImage = comp.EmitToArray(); + MetadataReaderUtils.AssertNotThrowNull(peImage); + } + ///

/// Check that when we emit metadata only, we include metadata for /// compiler generate methods (e.g. the ones for implicit interface @@ -294,7 +1564,7 @@ public class Class1 : CppCli.CppBase2, CppCli.CppInterface1 var class1TypeDef = (Cci.ITypeDefinition)class1; var symbolSynthesized = class1.GetSynthesizedExplicitImplementations(CancellationToken.None); - var context = new EmitContext(module, null, new DiagnosticBag()); + var context = new EmitContext(module, null, new DiagnosticBag(), metadataOnly: false, includePrivateMembers: true); var cciExplicit = class1TypeDef.GetExplicitImplementationOverrides(context); var cciMethods = class1TypeDef.GetMethods(context).Where(m => ((MethodSymbol)m).MethodKind != MethodKind.Constructor); @@ -939,7 +2209,7 @@ public class Test EmitResult emitResult; using (var output = new MemoryStream()) { - emitResult = compilation.Emit(output, null, null, null); + emitResult = compilation.Emit(output, pdbStream: null, xmlDocumentationStream: null, win32Resources: null); } Assert.False(emitResult.Success); @@ -972,7 +2242,7 @@ public static void Main() EmitResult emitResult; using (var output = new MemoryStream()) { - emitResult = compilation.Emit(output, null, null, null); + emitResult = compilation.Emit(output, pdbStream: null, xmlDocumentationStream: null, win32Resources: null); } Assert.True(emitResult.Success); @@ -1010,7 +2280,7 @@ public static void Main() EmitResult emitResult; using (var output = new MemoryStream()) { - emitResult = compilation.Emit(output, null, null, null); + emitResult = compilation.Emit(output, pdbStream: null, xmlDocumentationStream: null, win32Resources: null); } Assert.True(emitResult.Success); @@ -2726,7 +3996,7 @@ public void BrokenPDBStream() var output = new MemoryStream(); var pdb = new BrokenStream(); pdb.BreakHow = BrokenStream.BreakHowType.ThrowOnSetLength; - var result = compilation.Emit(output, pdb); + var result = compilation.Emit(output, pdbStream: pdb); // error CS0041: Unexpected error writing debug information -- 'Exception from HRESULT: 0x806D0004' var err = result.Diagnostics.Single(); @@ -2737,7 +4007,7 @@ public void BrokenPDBStream() Assert.Equal(ioExceptionMessage, (string)err.Arguments[0]); pdb.Dispose(); - result = compilation.Emit(output, pdb); + result = compilation.Emit(output, pdbStream: pdb); // error CS0041: Unexpected error writing debug information -- 'Exception from HRESULT: 0x806D0004' err = result.Diagnostics.Single(); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs index 63d9d60fc5943..b393d6bff2fbd 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs @@ -15,17 +15,23 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Emit { + [CompilerTrait(CompilerFeature.Determinism)] public class DeterministicTests : EmitMetadataTestBase { private Guid CompiledGuid(string source, string assemblyName, bool debug) + { + return CompiledGuid(source, assemblyName, options: debug ? TestOptions.DebugExe : TestOptions.ReleaseExe); + } + + private Guid CompiledGuid(string source, string assemblyName, CSharpCompilationOptions options, EmitOptions emitOptions = null) { var compilation = CreateCompilation(source, assemblyName: assemblyName, references: new[] { MscorlibRef }, - options: (debug ? TestOptions.DebugExe : TestOptions.ReleaseExe).WithDeterministic(true)); + options: options.WithDeterministic(true)); Guid result = default(Guid); - base.CompileAndVerify(compilation, validator: a => + base.CompileAndVerify(compilation, emitOptions: emitOptions, validator: a => { var module = a.Modules[0]; result = module.GetModuleVersionIdOrThrow(); @@ -66,8 +72,8 @@ public void BanVersionWildcards() references: new[] { MscorlibRef }, options: TestOptions.DebugDll.WithDeterministic(false)); - var resultDeterministic = compilationDeterministic.Emit(Stream.Null, Stream.Null); - var resultNonDeterministic = compilationNonDeterministic.Emit(Stream.Null, Stream.Null); + var resultDeterministic = compilationDeterministic.Emit(Stream.Null, pdbStream: Stream.Null); + var resultNonDeterministic = compilationNonDeterministic.Emit(Stream.Null, pdbStream: Stream.Null); Assert.False(resultDeterministic.Success); Assert.True(resultNonDeterministic.Success); @@ -104,6 +110,22 @@ public static void Main(string[] args) {} Assert.NotEqual(mvid3, mvid7); } + [Fact] + public void RefAssembly() + { + var source = +@"class Program +{ + public static void Main(string[] args) {} + CHANGE +}"; + var emitRefAssembly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + + var mvid1 = CompiledGuid(source.Replace("CHANGE", ""), "X1", TestOptions.DebugDll, emitRefAssembly); + var mvid2 = CompiledGuid(source.Replace("CHANGE", "private void M() { }"), "X1", TestOptions.DebugDll, emitRefAssembly); + Assert.Equal(mvid1, mvid2); + } + const string CompareAllBytesEmitted_Source = @" using System; using System.Linq; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicAnalysisResourceTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicAnalysisResourceTests.cs index caf46a49ac240..9b05a1b5c8d19 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicAnalysisResourceTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicAnalysisResourceTests.cs @@ -24,6 +24,11 @@ public static bool[] CreatePayload(System.Guid mvid, int methodToken, int fileIn return payload; } + public static bool[] CreatePayload(System.Guid mvid, int methodToken, int[] fileIndices, ref bool[] payload, int payloadLength) + { + return payload; + } + public static void FlushPayload() { } @@ -63,14 +68,14 @@ public void TestSpansPresentInResource() { var c = CreateStandardCompilation(Parse(ExampleSource + InstrumentationHelperSource, @"C:\myproject\doc1.cs")); var peImage = c.EmitToArray(EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))); - + var peReader = new PEReader(peImage); var reader = DynamicAnalysisDataReader.TryCreateFromPE(peReader, ""); VerifyDocuments(reader, reader.Documents, - @"'C:\myproject\doc1.cs' FF-9A-1F-F4-03-A5-A1-F7-8D-CD-00-15-67-0E-BA-F7-23-9D-3F-0F (SHA1)"); + @"'C:\myproject\doc1.cs' 44-3F-7C-A1-EF-CA-A8-16-40-D2-09-4F-3E-52-7C-44-8D-22-C8-02 (SHA1)"); - Assert.Equal(12, reader.Methods.Length); + Assert.Equal(13, reader.Methods.Length); string[] sourceLines = ExampleSource.Split('\n'); @@ -212,9 +217,9 @@ public static void Main() var reader = DynamicAnalysisDataReader.TryCreateFromPE(peReader, ""); VerifyDocuments(reader, reader.Documents, - @"'C:\myproject\doc1.cs' 89-73-A7-64-40-88-BA-0A-21-33-05-5D-E7-22-9B-74-1C-6A-2C-DC (SHA1)"); + @"'C:\myproject\doc1.cs' 6A-DC-C0-8A-16-CB-7C-A5-99-8B-2E-0C-3C-81-69-2C-B2-10-EE-F1 (SHA1)"); - Assert.Equal(5, reader.Methods.Length); + Assert.Equal(6, reader.Methods.Length); string[] sourceLines = source.Split('\n'); @@ -332,9 +337,9 @@ ref int BamBam(ref int x) // Method 7 var reader = DynamicAnalysisDataReader.TryCreateFromPE(peReader, ""); VerifyDocuments(reader, reader.Documents, - @"'C:\myproject\doc1.cs' 45-00-3A-4C-F3-AC-01-6A-D2-57-35-E5-43-5E-BD-DB-98-AF-FD-41 (SHA1)"); + @"'C:\myproject\doc1.cs' A3-08-94-55-7C-64-8D-C7-61-7A-11-0B-4B-68-2C-3B-51-C3-C4-58 (SHA1)"); - Assert.Equal(14, reader.Methods.Length); + Assert.Equal(15, reader.Methods.Length); string[] sourceLines = source.Split('\n'); @@ -343,7 +348,7 @@ ref int BamBam(ref int x) // Method 7 new SpanResult(10, 8, 10, 15, "Fred()")); VerifySpans(reader, reader.Methods[1], sourceLines, - new SpanResult(14,4,16,5, "static void Fred()")); + new SpanResult(14, 4, 16, 5, "static void Fred()")); VerifySpans(reader, reader.Methods[2], sourceLines, new SpanResult(18, 4, 21, 5, "static C()"), @@ -537,7 +542,7 @@ public void Deconstruct(out int x, out int y) } } "; - var c = CreateStandardCompilation(Parse(source + InstrumentationHelperSource, @"C:\myproject\doc1.cs"), references: new[] { ValueTupleRef, SystemRuntimeFacadeRef}); + var c = CreateStandardCompilation(Parse(source + InstrumentationHelperSource, @"C:\myproject\doc1.cs"), references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); var peImage = c.EmitToArray(EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))); var peReader = new PEReader(peImage); @@ -705,7 +710,7 @@ static void TestMain() // Method 1 VerifySpans(reader, reader.Methods[2], sourceLines, new SpanResult(15, 4, 15, 28, "static int Init() => 33"), new SpanResult(15, 25, 15, 27, "33")); - + VerifySpans(reader, reader.Methods[5], sourceLines, // Synthesized instance constructor new SpanResult(17, 13, 17, 19, "Init()"), new SpanResult(18, 13, 18, 24, "Init() + 12"), diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs index eed66d111bea2..a21dda5488649 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DynamicAnalysis/DynamicInstrumentationTests.cs @@ -26,6 +26,7 @@ public static void Main(string[] args) } } "; + string expectedOutput = @"Flushing Method 1 File 1 @@ -47,8 +48,28 @@ File 1 True True True +True +True "; - string expectedCreatePayloadIL = @"{ + + string expectedCreatePayloadForMethodsSpanningSingleFileIL = @"{ + // Code size 21 (0x15) + .maxstack 6 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldc.i4.1 + IL_0003: newarr ""int"" + IL_0008: dup + IL_0009: ldc.i4.0 + IL_000a: ldarg.2 + IL_000b: stelem.i4 + IL_000c: ldarg.3 + IL_000d: ldarg.s V_4 + IL_000f: call ""bool[] Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload(System.Guid, int, int[], ref bool[], int)"" + IL_0014: ret +}"; + + string expectedCreatePayloadForMethodsSpanningMultipleFilesIL = @"{ // Code size 87 (0x57) .maxstack 3 IL_0000: ldsfld ""System.Guid Microsoft.CodeAnalysis.Runtime.Instrumentation._mvid"" @@ -59,8 +80,8 @@ .maxstack 3 IL_000f: newarr ""bool[]"" IL_0014: stsfld ""bool[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._payloads"" IL_0019: ldc.i4.s 100 - IL_001b: newarr ""int"" - IL_0020: stsfld ""int[] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" + IL_001b: newarr ""int[]"" + IL_0020: stsfld ""int[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" IL_0025: ldarg.0 IL_0026: stsfld ""System.Guid Microsoft.CodeAnalysis.Runtime.Instrumentation._mvid"" IL_002b: ldarg.3 @@ -74,10 +95,10 @@ .maxstack 3 IL_0041: ldarg.3 IL_0042: ldind.ref IL_0043: stelem.ref - IL_0044: ldsfld ""int[] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" + IL_0044: ldsfld ""int[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" IL_0049: ldarg.1 IL_004a: ldarg.2 - IL_004b: stelem.i4 + IL_004b: stelem.ref IL_004c: ldarg.3 IL_004d: ldind.ref IL_004e: ret @@ -88,12 +109,13 @@ .maxstack 3 }"; string expectedFlushPayloadIL = @"{ - // Code size 247 (0xf7) + // Code size 288 (0x120) .maxstack 5 .locals init (bool[] V_0, int V_1, //i bool[] V_2, //payload - int V_3) //j + int V_3, //j + int V_4) //j IL_0000: ldsfld ""bool[][] .PayloadRoot0"" IL_0005: ldtoken ""void Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload()"" IL_000a: ldelem.ref @@ -106,7 +128,7 @@ .locals init (bool[] V_0, IL_001e: ldsfld ""bool[][] .PayloadRoot0"" IL_0023: ldtoken ""void Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload()"" IL_0028: ldelema ""bool[]"" - IL_002d: ldc.i4.s 14 + IL_002d: ldc.i4.s 16 IL_002f: call ""bool[] Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload(System.Guid, int, int, ref bool[], int)"" IL_0034: stloc.0 IL_0035: ldloc.0 @@ -136,7 +158,7 @@ .locals init (bool[] V_0, IL_005a: stelem.i1 IL_005b: ldc.i4.0 IL_005c: stloc.1 - IL_005d: br IL_00e9 + IL_005d: br IL_0112 IL_0062: ldloc.0 IL_0063: ldc.i4.6 IL_0064: ldc.i4.1 @@ -146,84 +168,110 @@ .locals init (bool[] V_0, IL_006c: ldelem.ref IL_006d: stloc.2 IL_006e: ldloc.0 - IL_006f: ldc.i4.s 13 + IL_006f: ldc.i4.s 15 IL_0071: ldc.i4.1 IL_0072: stelem.i1 IL_0073: ldloc.2 - IL_0074: brfalse.s IL_00e1 - IL_0076: ldloc.0 - IL_0077: ldc.i4.7 - IL_0078: ldc.i4.1 - IL_0079: stelem.i1 - IL_007a: ldstr ""Method "" - IL_007f: ldloca.s V_1 - IL_0081: call ""string int.ToString()"" - IL_0086: call ""string string.Concat(string, string)"" - IL_008b: call ""void System.Console.WriteLine(string)"" - IL_0090: ldloc.0 - IL_0091: ldc.i4.8 - IL_0092: ldc.i4.1 - IL_0093: stelem.i1 - IL_0094: ldstr ""File "" - IL_0099: ldsfld ""int[] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" - IL_009e: ldloc.1 - IL_009f: ldelema ""int"" - IL_00a4: call ""string int.ToString()"" - IL_00a9: call ""string string.Concat(string, string)"" - IL_00ae: call ""void System.Console.WriteLine(string)"" - IL_00b3: ldloc.0 - IL_00b4: ldc.i4.s 9 - IL_00b6: ldc.i4.1 - IL_00b7: stelem.i1 - IL_00b8: ldc.i4.0 - IL_00b9: stloc.3 - IL_00ba: br.s IL_00db - IL_00bc: ldloc.0 - IL_00bd: ldc.i4.s 11 - IL_00bf: ldc.i4.1 - IL_00c0: stelem.i1 - IL_00c1: ldloc.2 - IL_00c2: ldloc.3 - IL_00c3: ldelem.u1 - IL_00c4: call ""void System.Console.WriteLine(bool)"" - IL_00c9: ldloc.0 - IL_00ca: ldc.i4.s 12 - IL_00cc: ldc.i4.1 - IL_00cd: stelem.i1 - IL_00ce: ldloc.2 - IL_00cf: ldloc.3 - IL_00d0: ldc.i4.0 - IL_00d1: stelem.i1 - IL_00d2: ldloc.0 - IL_00d3: ldc.i4.s 10 - IL_00d5: ldc.i4.1 - IL_00d6: stelem.i1 - IL_00d7: ldloc.3 - IL_00d8: ldc.i4.1 - IL_00d9: add - IL_00da: stloc.3 - IL_00db: ldloc.3 - IL_00dc: ldloc.2 - IL_00dd: ldlen - IL_00de: conv.i4 - IL_00df: blt.s IL_00bc - IL_00e1: ldloc.0 - IL_00e2: ldc.i4.5 + IL_0074: brfalse IL_010a + IL_0079: ldloc.0 + IL_007a: ldc.i4.7 + IL_007b: ldc.i4.1 + IL_007c: stelem.i1 + IL_007d: ldstr ""Method "" + IL_0082: ldloca.s V_1 + IL_0084: call ""string int.ToString()"" + IL_0089: call ""string string.Concat(string, string)"" + IL_008e: call ""void System.Console.WriteLine(string)"" + IL_0093: ldloc.0 + IL_0094: ldc.i4.8 + IL_0095: ldc.i4.1 + IL_0096: stelem.i1 + IL_0097: ldc.i4.0 + IL_0098: stloc.3 + IL_0099: br.s IL_00ca + IL_009b: ldloc.0 + IL_009c: ldc.i4.s 10 + IL_009e: ldc.i4.1 + IL_009f: stelem.i1 + IL_00a0: ldstr ""File "" + IL_00a5: ldsfld ""int[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" + IL_00aa: ldloc.1 + IL_00ab: ldelem.ref + IL_00ac: ldloc.3 + IL_00ad: ldelema ""int"" + IL_00b2: call ""string int.ToString()"" + IL_00b7: call ""string string.Concat(string, string)"" + IL_00bc: call ""void System.Console.WriteLine(string)"" + IL_00c1: ldloc.0 + IL_00c2: ldc.i4.s 9 + IL_00c4: ldc.i4.1 + IL_00c5: stelem.i1 + IL_00c6: ldloc.3 + IL_00c7: ldc.i4.1 + IL_00c8: add + IL_00c9: stloc.3 + IL_00ca: ldloc.3 + IL_00cb: ldsfld ""int[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._fileIndices"" + IL_00d0: ldloc.1 + IL_00d1: ldelem.ref + IL_00d2: ldlen + IL_00d3: conv.i4 + IL_00d4: blt.s IL_009b + IL_00d6: ldloc.0 + IL_00d7: ldc.i4.s 11 + IL_00d9: ldc.i4.1 + IL_00da: stelem.i1 + IL_00db: ldc.i4.0 + IL_00dc: stloc.s V_4 + IL_00de: br.s IL_0103 + IL_00e0: ldloc.0 + IL_00e1: ldc.i4.s 13 IL_00e3: ldc.i4.1 IL_00e4: stelem.i1 - IL_00e5: ldloc.1 - IL_00e6: ldc.i4.1 - IL_00e7: add - IL_00e8: stloc.1 - IL_00e9: ldloc.1 - IL_00ea: ldsfld ""bool[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._payloads"" - IL_00ef: ldlen - IL_00f0: conv.i4 - IL_00f1: blt IL_0062 - IL_00f6: ret + IL_00e5: ldloc.2 + IL_00e6: ldloc.s V_4 + IL_00e8: ldelem.u1 + IL_00e9: call ""void System.Console.WriteLine(bool)"" + IL_00ee: ldloc.0 + IL_00ef: ldc.i4.s 14 + IL_00f1: ldc.i4.1 + IL_00f2: stelem.i1 + IL_00f3: ldloc.2 + IL_00f4: ldloc.s V_4 + IL_00f6: ldc.i4.0 + IL_00f7: stelem.i1 + IL_00f8: ldloc.0 + IL_00f9: ldc.i4.s 12 + IL_00fb: ldc.i4.1 + IL_00fc: stelem.i1 + IL_00fd: ldloc.s V_4 + IL_00ff: ldc.i4.1 + IL_0100: add + IL_0101: stloc.s V_4 + IL_0103: ldloc.s V_4 + IL_0105: ldloc.2 + IL_0106: ldlen + IL_0107: conv.i4 + IL_0108: blt.s IL_00e0 + IL_010a: ldloc.0 + IL_010b: ldc.i4.5 + IL_010c: ldc.i4.1 + IL_010d: stelem.i1 + IL_010e: ldloc.1 + IL_010f: ldc.i4.1 + IL_0110: add + IL_0111: stloc.1 + IL_0112: ldloc.1 + IL_0113: ldsfld ""bool[][] Microsoft.CodeAnalysis.Runtime.Instrumentation._payloads"" + IL_0118: ldlen + IL_0119: conv.i4 + IL_011a: blt IL_0062 + IL_011f: ret }"; + CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); - verifier.VerifyIL("Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload", expectedCreatePayloadIL); + verifier.VerifyIL("Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload(System.Guid, int, int, ref bool[], int)", expectedCreatePayloadForMethodsSpanningSingleFileIL); + verifier.VerifyIL("Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload(System.Guid, int, int[], ref bool[], int)", expectedCreatePayloadForMethodsSpanningMultipleFilesIL); verifier.VerifyIL("Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload", expectedFlushPayloadIL); verifier.VerifyDiagnostics(); } @@ -344,6 +392,8 @@ File 1 True True True +True +True "; string expectedBarneyIL = @"{ @@ -509,6 +559,8 @@ File 1 True True True +True +True "; string expectedReleaseGetValueIL = @"{ @@ -684,6 +736,8 @@ public static void Main(string[] args) // M .True() .True() .True() + .True() + .True() .True(); CompilationVerifier verifier = CompileAndVerify(source, expectedOutput: checker.ExpectedOutput, options: TestOptions.ReleaseExe); @@ -793,6 +847,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput, options: TestOptions.ReleaseExe); verifier.VerifyDiagnostics(); @@ -884,6 +940,8 @@ void L1() .True() .True() .True() + .True() + .True() .True(); CompilationVerifier verifier = CompileAndVerify(source, expectedOutput: checker.ExpectedOutput, options: TestOptions.ReleaseExe); @@ -965,6 +1023,8 @@ File 5 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput, options: TestOptions.ReleaseExe); @@ -1062,6 +1122,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1158,6 +1220,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, options: TestOptions.UnsafeDebugExe, expectedOutput: expectedOutput); @@ -1345,6 +1409,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1437,6 +1503,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1512,6 +1580,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); } @@ -1599,6 +1669,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); } @@ -1673,6 +1745,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1776,6 +1850,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1862,6 +1938,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -1973,6 +2051,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -2057,6 +2137,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -2180,6 +2262,8 @@ File 1 True True True +True +True "; CompilationVerifier verifier = CompileAndVerify(source + InstrumentationHelperSource, expectedOutput: expectedOutput); @@ -2694,6 +2778,8 @@ static void Test() .True() .True() .True() + .True() + .True() .True(); var expectedOutput = @"Test @@ -2769,6 +2855,8 @@ static void Test() .True() .True() .True() + .True() + .True() .True(); var expectedOutput = @"Test @@ -2784,6 +2872,330 @@ static void Test() verifier.VerifyDiagnostics(); } + [Fact] + public void TestSynthesizedConstructorWithSpansInMultipleFilesCoverage() + { + var source1 = @" +using System; + +public partial class Class1 +{ + private int x = 1; +} + +public class Program +{ + public static void Main(string[] args) + { + Test(); + Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload(); + } + + static void Test() + { + Console.WriteLine(""Test""); + var c = new Class1(); + c.Method1(1); + } +} +" + InstrumentationHelperSource; + + var source2 = @" +public partial class Class1 +{ + private int y = 2; +} + +public partial class Class1 +{ + private int z = 3; +}"; + + var source3 = @" +using System; + +public partial class Class1 +{ + private Action a = i => + { + Console.WriteLine(i); + }; + + public void Method1(int i) + { + a(i); + Console.WriteLine(x); + Console.WriteLine(y); + Console.WriteLine(z); + } +}"; + + var sources = new[] { + (Name: "b.cs", Content: source1), + (Name: "c.cs", Content: source2), + (Name: "a.cs", Content: source3) + }; + + var expectedOutput = @"Test +1 +1 +2 +3 +Flushing +Method 1 +File 1 +True +True +True +True +True +Method 2 +File 1 +File 2 +File 3 +True +True +True +True +True +Method 3 +File 2 +True +True +True +Method 4 +File 2 +True +True +True +True +Method 7 +File 2 +True +True +False +True +True +True +True +True +True +True +True +True +True +True +True +True +"; + + var verifier = CompileAndVerify(sources, expectedOutput, options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); + + verifier = CompileAndVerify(sources, expectedOutput, options: TestOptions.DebugExe); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void TestSynthesizedStaticConstructorWithSpansInMultipleFilesCoverage() + { + var source1 = @" +using System; + +public partial class Class1 +{ + private static int x = 1; +} + +public class Program +{ + public static void Main(string[] args) + { + Test(); + Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload(); + } + + static void Test() + { + Console.WriteLine(""Test""); + var c = new Class1(); + Class1.Method1(1); + } +} +" + InstrumentationHelperSource; + + var source2 = @" +public partial class Class1 +{ + private static int y = 2; +} + +public partial class Class1 +{ + private static int z = 3; +}"; + + var source3 = @" +using System; + +public partial class Class1 +{ + private static Action a = i => + { + Console.WriteLine(i); + }; + + public static void Method1(int i) + { + a(i); + Console.WriteLine(x); + Console.WriteLine(y); + Console.WriteLine(z); + } +}"; + + var sources = new[] { + (Name: "b.cs", Content: source1), + (Name: "c.cs", Content: source2), + (Name: "a.cs", Content: source3) + }; + + var expectedOutput = @"Test +1 +1 +2 +3 +Flushing +Method 1 +File 1 +True +True +True +True +True +Method 2 +File 2 +Method 3 +File 1 +File 2 +File 3 +True +True +True +True +True +Method 4 +File 2 +True +True +True +Method 5 +File 2 +True +True +True +True +Method 8 +File 2 +True +True +False +True +True +True +True +True +True +True +True +True +True +True +True +True +"; + + var verifier = CompileAndVerify(sources, expectedOutput, options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); + + verifier = CompileAndVerify(sources, expectedOutput, options: TestOptions.DebugExe); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void TestLineDirectiveCoverage() + { + var source = @" +using System; + +public class Program +{ + public static void Main(string[] args) + { + Test(); + Microsoft.CodeAnalysis.Runtime.Instrumentation.FlushPayload(); + } + + static void Test() + { +#line 300 ""File2.cs"" + Console.WriteLine(""Start""); +#line hidden + Console.WriteLine(""Hidden""); +#line default + Console.WriteLine(""Visible""); +#line 400 ""File3.cs"" + Console.WriteLine(""End""); + } +} +" + InstrumentationHelperSource; + + var expectedOutput = @"Start +Hidden +Visible +End +Flushing +Method 1 +File 1 +True +True +True +Method 2 +File 1 +File 2 +File 3 +True +True +True +True +True +Method 5 +File 3 +True +True +False +True +True +True +True +True +True +True +True +True +True +True +True +True +"; + + var verifier = CompileAndVerify(source, expectedOutput, options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); + + verifier = CompileAndVerify(source, expectedOutput, options: TestOptions.DebugExe); + verifier.VerifyDiagnostics(); + } + private static void AssertNotInstrumented(CompilationVerifier verifier, string qualifiedMethodName) => AssertInstrumented(verifier, qualifiedMethodName, expected: false); @@ -2803,6 +3215,20 @@ private CompilationVerifier CompileAndVerify(string source, string expectedOutpu return base.CompileAndVerify(source, expectedOutput: expectedOutput, additionalRefs: s_refs, options: (options ?? TestOptions.ReleaseExe).WithDeterministic(true), emitOptions: EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))); } + private CompilationVerifier CompileAndVerify((string Path, string Content)[] sources, string expectedOutput = null, CSharpCompilationOptions options = null) + { + var trees = ArrayBuilder.GetInstance(); + foreach (var source in sources) + { + // The trees must be assigned unique file names in order for instrumentation to work correctly. + trees.Add(Parse(source.Content, filename: source.Path)); + } + + var compilation = CreateStandardCompilation(trees, s_refs, (options ?? TestOptions.ReleaseExe).WithDeterministic(true)); + trees.Free(); + return base.CompileAndVerify(compilation, expectedOutput: expectedOutput, emitOptions: EmitOptions.Default.WithInstrumentationKinds(ImmutableArray.Create(InstrumentationKind.TestCoverage))); + } + private static readonly MetadataReference[] s_refs = new[] { MscorlibRef_v4_0_30316_17626, SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, ValueTupleRef, SystemRuntimeFacadeRef }; } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index 7dce777325afd..2ceeac1661b2f 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -349,10 +349,12 @@ static void M()