diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f15014a3c4c2b..59bdf21d2b36f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,7 +14,7 @@ **Building**: - `build.sh` - Full solution build - `dotnet build Compilers.slnf` - Compiler-only build -- Run `dotnet msbuild /t:UpdateXlf` when .resx files are modified to update corresponding .xlf files +- `dotnet msbuild /t:UpdateXlf` - Update .xlf files when their corresponding .resx file is modified **Testing**: - `test.sh` - Run all tests @@ -24,7 +24,8 @@ **Formatting**: - Whitespace formatting preferences are stored in the `.editorconfig` file -- Run `dotnet format whitespace -f . --include ` followed by the relative paths to changed .cs and .vb files to apply formatting preferences +- When running `dotnet format whitespace` use the `--folder .` option followed by `--include ` to avoid a design-time build. +- `dotnet format whitespace --folder . --include ` - Applies formatting preferences to a particular .cs or .vb file ## Code Patterns @@ -52,21 +53,23 @@ var symbolInfo = semanticModel.GetSymbolInfo(expression); - Use `UseExportProvider` for MEF services - Test utilities in `Microsoft.CodeAnalysis.Test.Utilities` - Language-specific test bases: `CSharpTestBase`, `VisualBasicTestBase` +- Add `[WorkItem("https://github.com/dotnet/roslyn/issues/issueNumber")]` attribute to tests that fix specific GitHub issues ## Critical Integration Points -**Language Server Protocol**: `src/LanguageServer/` contains LSP implementation used by VS Code extension -**ServiceHub**: Remote services (`src/Workspaces/Remote/`) run out-of-process for performance -**Analyzers**: `src/Analyzers/` for static analysis, separate from `src/RoslynAnalyzers/` (internal tooling) -**VSIX Packaging**: Multiple deployment targets - `src/VisualStudio/Setup/` for main VS integration +- **Language Server Protocol**: `src/LanguageServer/` contains LSP implementation used by VS Code extension +- **ServiceHub**: Remote services (`src/Workspaces/Remote/`) run out-of-process for performance +- **Analyzers**: `src/Analyzers/` for static analysis, separate from `src/RoslynAnalyzers/` (internal tooling) +- **VSIX Packaging**: Multiple deployment targets - `src/VisualStudio/Setup/` for main VS integration ## Key Conventions -**Namespace Strategy**: `Microsoft.CodeAnalysis.[Language].[Area]` (e.g., `Microsoft.CodeAnalysis.CSharp.Formatting`) -**File Organization**: Group by feature area, separate language-specific implementations -**Immutability**: All syntax trees, documents, and solutions are immutable - create new instances for changes -**Cancellation**: Always thread `CancellationToken` through async operations -**MEF Lifecycle**: Use `[ImportingConstructor]` with obsolete attribute for MEF v2 compatibility +- **Namespace Strategy**: `Microsoft.CodeAnalysis.[Language].[Area]` (e.g., `Microsoft.CodeAnalysis.CSharp.Formatting`) +- **File Organization**: Group by feature area, separate language-specific implementations +- **Immutability**: All syntax trees, documents, and solutions are immutable - create new instances for changes +- **Cancellation**: Always thread `CancellationToken` through async operations +- **MEF Lifecycle**: Use `[ImportingConstructor]` with obsolete attribute for MEF v2 compatibility +- **PROTOTYPE Comments**: Only used to track follow-up work in feature branches and are disallowed in main branch ## Common Gotchas @@ -82,4 +85,4 @@ var symbolInfo = semanticModel.GetSymbolInfo(expression); - `docs/contributing/Building, Debugging, and Testing on Unix.md` - Development setup - `src/Compilers/Core/Portable/` - Core compiler APIs - `src/Workspaces/Core/Portable/` - Workspace object model -- Solution filters: `Roslyn.sln`, `Compilers.slnf`, `Ide.slnf` for focused builds \ No newline at end of file +- Solution filters: `Roslyn.sln`, `Compilers.slnf`, `Ide.slnf` for focused builds diff --git a/.github/instructions/Compiler.instructions.md b/.github/instructions/Compiler.instructions.md new file mode 100644 index 0000000000000..e9e40e2b3dcd3 --- /dev/null +++ b/.github/instructions/Compiler.instructions.md @@ -0,0 +1,95 @@ +# Roslyn Compiler Instructions for AI Coding Agents + +--- +applyTo: "src/{Compilers,Dependencies,ExpressionEvaluator,Tools}/**/*.{cs,vb}" +--- + +## Architecture Overview + +Roslyn follows a **layered compiler architecture**: +- **Lexer → Parser → Syntax Trees → Semantic Analysis → Lowering/Rewriting → Symbol Tables → Emit** +- Core abstraction: `Compilation` is immutable and reusable. Create new compilations via `AddSyntaxTrees()`, `RemoveSyntaxTrees()`, `ReplaceSyntaxTree()` for incremental changes +- **Internal vs Public APIs**: Use `InternalSyntax` namespace for performance-critical parsing; `Microsoft.CodeAnalysis` for public consumption + +### Key Directories +- `src/Compilers/Core/Portable/` - Language-agnostic compiler infrastructure +- `src/Compilers/CSharp/Portable/` - C# compiler implementation +- `src/Compilers/VisualBasic/Portable/` - VB compiler implementation +- `src/Dependencies/` - High-performance collections (`PooledObjects`, `Threading`) +- `src/ExpressionEvaluator/` - Debugger expression evaluation (uses special `LexerMode.DebuggerSyntax`) +- `src/Tools/` - Compiler tooling (BuildBoss, format tools, analyzers) + +## Essential Patterns + +### Test Structure Convention +Inherit from language-specific base classes: `CSharpTestBase` for C#, `VisualBasicTestBase` for VB +```cs +public class MyTests : CSharpTestBase +{ + [Fact] + public void TestMethod() + { + var comp = CreateCompilation(sourceCode); + // Test compilation, symbols, diagnostics + } +} +``` + +### Memory Management +- **Avoid LINQ in hot paths** - use manual enumeration or `struct` enumerators +- **Avoid `foreach` over collections without struct enumerators** +- **Use object pools extensively** - see patterns in `src/Dependencies/PooledObjects/` +- **Prefer `Debug.Assert()` over exceptions** for internal validation + +## Build & Test Workflows + +### Essential Build Commands +```powershell +# Full build (use VS Code tasks when available) +./build.sh + +# Build specific components +dotnet build Compilers.slnf # Compiler-only build +dotnet build src/Compilers/CSharp/csc/AnyCpu/ # C# compiler + +# Generate compiler code after changes +dotnet run --file eng/generate-compiler-code.cs +``` + +### Testing Strategy +- **Unit tests**: Test individual compiler phases (lexing, parsing) +- **Compilation tests**: Create `Compilation` objects and verify symbols/diagnostics +- **Cross-language patterns**: Many test patterns work for both C# and VB with minor syntax changes + +## Debugger Integration + +**Expression Evaluator** uses special parsing modes: +- `LexerMode.DebuggerSyntax` for expression evaluation +- `IsInFieldKeywordContext` flag for context-aware parsing +- `ConsumeFullText` parameter for complete expression parsing + +## MSBuild Integration + +Compiler tasks are in `src/Compilers/Core/MSBuildTask/`: +- `Csc.cs` - C# compiler task +- `Vbc.cs` - VB compiler task +- `ManagedCompiler.cs` - Base compiler task functionality + +## Performance Considerations + +1. **Lexer/Parser optimizations**: Use `InternalSyntax` types for performance-critical code +2. **Immutable data structures**: Roslyn heavily uses immutable collections and copy-on-write semantics +3. **Caching**: `Compilation` objects cache semantic information - reuse when possible +4. **Threading**: Most compiler operations are thread-safe through immutability + +## Symbol Resolution + +Navigate the symbol hierarchy: +```cs +var compilation = CreateCompilation(source); +var globalNamespace = compilation.GlobalNamespace; +var typeSymbol = globalNamespace.GetTypeMembers("MyClass").Single(); +var methodSymbol = typeSymbol.GetMembers("MyMethod").Single(); +``` + +Symbol equality is complex due to generics and substitution - always test with multiple generic scenarios. \ No newline at end of file diff --git a/.github/instructions/IDE.instructions.md b/.github/instructions/IDE.instructions.md new file mode 100644 index 0000000000000..87bde507b0256 --- /dev/null +++ b/.github/instructions/IDE.instructions.md @@ -0,0 +1,154 @@ +--- +applyTo: "src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}" +--- + +# Roslyn IDE Development Guide + +This guide provides essential knowledge for working effectively with Roslyn's IDE-focused codebase. + +## Architecture Overview + +Roslyn uses a **layered service architecture** built on MEF (Managed Extensibility Framework): + +- **Workspaces** (`src/Workspaces/`): Core abstractions - `Workspace`, `Solution`, `Project`, `Document` +- **Features** (`src/Features/`): Language-agnostic IDE features (refactoring, navigation, completion) +- **LanguageServer** (`src/LanguageServer/`): Shared LSP protocol implementation and Roslyn LSP executable +- **EditorFeatures** (`src/EditorFeatures/`): VS Editor integration and text manipulation +- **VisualStudio** (`src/VisualStudio/`): Visual Studio-specific implementations + +### Service Resolution Pattern + +```csharp +// Get workspace services +var service = workspace.Services.GetRequiredService(); + +// Get language-specific services +var csharpService = workspace.Services.GetLanguageServices(LanguageNames.CSharp) + .GetRequiredService(); + +// In tests, use ExportProvider directly +var service = ExportProvider.GetExportedValue(); +``` + +### MEF Export Patterns + +```csharp +// Workspace service +[ExportWorkspaceService(typeof(IMyService)), Shared] +internal class MyService : IMyService { } + +// Language service +[ExportLanguageService(typeof(IMyService), LanguageNames.CSharp), Shared] +internal class CSharpMyService : IMyService { } + +// Always use ImportingConstructor with obsolete warning +[ImportingConstructor] +[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +public MyService(IDependency dependency) { } +``` + +## Key Development Patterns + +### TestAccessor Pattern +For exposing internal state to tests without making it public: + +```csharp +internal class ProductionClass +{ + private int _privateField; + + internal TestAccessor GetTestAccessor() => new(this); + + internal readonly struct TestAccessor + { + private readonly ProductionClass _instance; + internal TestAccessor(ProductionClass instance) => _instance = instance; + internal ref int PrivateField => ref _instance._privateField; + } +} +``` + +### Diagnostic Analyzer Structure +```csharp +[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] +public sealed class MyAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor s_rule = new( + "MyAnalyzer001", "Title", "Message format", "Category", + DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(s_rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); + } +} +``` + +## Essential Build & Test Commands + +```bash +# Full build +.build.sh + +# Run specific test project +dotnet test src/EditorFeatures/Test/ + +# Build with analyzers +.build.sh -testUsedAssemblies + +# Generate compiler code (if changing syntax) +dotnet run --file eng/generate-compiler-code.cs +``` + +## Working with Tests + +### Test Workspace Creation +```csharp +[UseExportProvider] +public class MyTests +{ + [Fact] + public async Task TestSomething() + { + var workspace = EditorTestWorkspace.CreateCSharp("class C { }"); + var document = workspace.Documents.Single(); + // Test logic here + } +} +``` + +### Common Test Utilities +- `DescriptorFactory.CreateSimpleDescriptor()` - Create test diagnostic descriptors +- `VerifyCS.VerifyAnalyzerAsync()` - Verify C# analyzer behavior +- `TestWorkspace.CreateCSharp()` - Create test workspaces +- `UseExportProviderAttribute` - Required for MEF-dependent tests + +## Coding Conventions + +### Performance Rules +- **Avoid LINQ in hot paths** - Use manual loops in compiler/analyzer code +- **Avoid `foreach` over non-struct enumerators** - Use `for` loops or `.AsSpan()` +- **Use object pooling** - See `ObjectPool` usage patterns +- **Prefer `ReadOnlySpan`** over `IEnumerable` for performance-critical APIs + +### Naming Conventions +- Private fields: `_camelCase` +- Internal test accessors: `GetTestAccessor()` returning `TestAccessor` struct +- Diagnostic IDs: Consistent prefixes (RS, CA, IDE followed by numbers) +- MEF exports: Match interface names without "I" prefix + +### Resource Management +- MEF services are automatically disposed by the container +- Use `TestAccessor` pattern instead of `internal` accessibility for test-only APIs +- Always implement `IDisposable` for stateful services + +## Common Gotchas + +- **ImportingConstructor must be marked `[Obsolete]`** with `MefConstruction.ImportingConstructorMessage` +- **Use `Contract.ThrowIfNull()`** instead of manual null checks in public APIs +- **TestAccessor calls are forbidden in production code** - enforced by analyzer RS0043 +- **Language services must be exported with specific language name** - don't use generic exports +- **Workspace changes must use immutable updates** - call `Workspace.SetCurrentSolution()` appropriately \ No newline at end of file diff --git a/Roslyn.sln b/Roslyn.sln index a3ca9c3aea827..369df6245a0bd 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.11101.28 d18.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36511.14 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynDeployment", "src\Deployment\RoslynDeployment.csproj", "{600AF682-E097-407B-AD85-EE3CED37E680}" EndProject diff --git a/docs/contributing/Building, Debugging, and Testing on Windows.md b/docs/contributing/Building, Debugging, and Testing on Windows.md index cd3d77670b084..e06009adb89cc 100644 --- a/docs/contributing/Building, Debugging, and Testing on Windows.md +++ b/docs/contributing/Building, Debugging, and Testing on Windows.md @@ -98,14 +98,20 @@ will start a new Visual Studio instance using those VSIX which override our inst binaries. This means trying out a change to the language, IDE or debugger is as simple as hitting F5. Note that for changes to the compiler, out-of-process builds won't use the privately built version of the compiler. -The startup project needs to be set to `RoslynDeployment`. This should be -the default but in some cases will need to be set explicitly. +The startup project needs to be set to **RoslynDeployment**. This should be +the default but in some cases will need to be set explicitly. To set it, right-click +the RoslynDeployment project in Solution Explorer and select "Set as Startup Project". -Here are what is deployed with each extension, by project that builds it. If -you're working on a particular area, you probably want to set the appropriate -project as your startup project to optimize building and deploying only the relevant bits. +**RoslynDeployment** is a container project located in the `src/Deployment` folder that +bundles and deploys all the main Roslyn extensions together. When you press F5 with +RoslynDeployment set as the startup project, it will deploy all of the following extensions +at once, giving you a complete debugging experience with all Roslyn components. -- **Roslyn.VisualStudio.Setup**: this project can be found inside the VisualStudio folder +If you're working on a specific area and want to optimize build times by deploying only +the relevant extension, you can set one of the individual projects below as your startup +project instead: + +- **Roslyn.VisualStudio.Setup**: this project can be found inside the VisualStudio\Setup folder from the Solution Explorer, and builds Roslyn.VisualStudio.Setup.vsix. It contains the core language services that provide C# and VB editing. It also contains the copy of the compiler that is used to drive IntelliSense and @@ -114,10 +120,7 @@ project as your startup project to optimize building and deploying only the rele compiler used to actually produce your final .exe or .dll when you do a build. If you're working on fixing an IDE bug, this is the project you want to use. -- **Roslyn.VisualStudio.InteractiveComponents**: this project can be found in the - Interactive\Setup folder from the Solution Explorer, and builds - Roslyn.VisualStudio.InteractiveComponents.vsix. -- **Roslyn.Compilers.Extension**: this project can be found inside the Compilers\Packages folder +- **Roslyn.Compilers.Extension**: this project can be found inside the Compilers\Extension folder from the Solution Explorer, and builds Roslyn.Compilers.Extension.vsix. This deploys a copy of the command line compilers that are used to do actual builds in the IDE. It only affects builds triggered from the Visual Studio @@ -128,7 +131,7 @@ project as your startup project to optimize building and deploying only the rele CompilerExtension and VisualStudioSetup projects to ensure the real build and live analysis are synchronized. - **ExpressionEvaluatorPackage**: this project can be found inside the - ExpressionEvaluator\Setup folder from the Solution Explorer, and builds + ExpressionEvaluator\Package folder from the Solution Explorer, and builds ExpressionEvaluatorPackage.vsix. This deploys the expression evaluator and result providers, the components that are used by the debugger to parse and evaluate C# and VB expressions in the Watch window, Immediate window, and @@ -190,12 +193,15 @@ under `AppData`, not from `Program File`). ### Testing on the [dotnet/runtime](https://github.com/dotnet/runtime) repo -1. make sure that you can build the `runtime` repo as baseline (run `build.cmd libs+libs.tests`, which should be sufficient to build all C# code, installing any prerequisites if prompted to, and perhaps `git clean -xdf` and `build.cmd -restore` initially - see [runtime repo documentation](https://github.com/dotnet/runtime/blob/main/docs/workflow/README.md) for specific prerequisites and build instructions) -2. `build.cmd -pack` on your `roslyn` repo -3. in `%userprofile%\.nuget\packages\microsoft.net.compilers.toolset` delete the version of the toolset that you just packed so that the new one will get put into the cache -4. modify your local enlistment of `runtime` as illustrated in [this commit](https://github.com/RikkiGibson/runtime/commit/da3c6d96c3764e571269b07650a374678b476384) then build again - - add `\artifacts\packages\Debug\Shipping\` using the local path to your `roslyn` repo to `Directory.Build.props` - - add `true` and `4.1.0-dev` with the package version you just packed (look in above artifacts folder) to `eng/Versions.props` +1. Make sure that you can build the `runtime` repo as baseline (run `build.cmd libs+libs.tests`, which should be sufficient to build all C# code, installing any prerequisites if prompted to, and perhaps `git clean -xdf` and `build.cmd -restore` initially - see [runtime repo documentation](https://github.com/dotnet/runtime/blob/main/docs/workflow/README.md) for specific prerequisites and build instructions) +2. `build.cmd -pack -c Release` on your `roslyn` repo + - Note that `-c Debug` can also be used (along with changing `Release` to `Debug` in `RestoreAdditionalProjectSources` property value below). This will allow checking the compiler's debug assertions when building the runtime. + - It's good for us to investigate scenarios where compiling the runtime libraries causes the compiler's debug assertions to fail. However, assertion failures can obscure whether or not the compiler ultimately succeeds at building the runtime and producing correct binaries. So, if the goal is to only check for "functional breaks", then using a Release mode compiler to start with can be preferable. +3. Find the compiler toolset in your NuGet package cache (its location is likely `%NUGET_PACKAGES%\microsoft.net.compilers.toolset` or `%userprofile%\.nuget\packages\microsoft.net.compilers.toolset`). +4. If there is a toolset version in the NuGet cache with the same version as the toolset you just packed in Roslyn, delete the toolset version from the cache. This allows NuGet to pick up the new packages you just created locally instead of using a stale cached version. +5. Modify your local enlistment of `runtime` similarly to [this commit](https://github.com/RikkiGibson/runtime/commit/runtime-local-roslyn-build-example) then build again + - add `\artifacts\packages\Release\Shipping\` using the local path to your `roslyn` repo to `Directory.Build.props` + - add `true` and `5.3.0-dev` with the package version you just packed (look in above artifacts folder) to `eng/Versions.props` ### Testing a VS insertion diff --git a/eng/Packages.props b/eng/Packages.props index 37ee560eb8aaa..e09242f31ea14 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -25,16 +25,16 @@ - - - - + + + + @@ -52,7 +52,10 @@ - + + - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 4eac3d2bad5cf..ef481c2910684 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -8,8 +8,8 @@ This file should be imported by eng/Versions.props 3.11.0 4.10.0-1.24061.4 - - 2.0.0-rc.1.25509.115 + + 2.0.0-rc.1.25510.104 9.0.0 9.0.0 @@ -53,7 +53,7 @@ This file should be imported by eng/Versions.props $(MicrosoftCodeAnalysisPackageVersion) $(MicrosoftNetCompilersToolsetPackageVersion) - + $(SystemCommandLinePackageVersion) $(MicrosoftBclAsyncInterfacesPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e93ee48faf3f3..f280bc51348dc 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,15 +1,15 @@ - + https://github.com/dotnet/roslyn ae1fff344d46976624e68ae17164e0607ab68b10 - - https://github.com/dotnet/dotnet - 2a4d010d133eb0c2c2a985d4da28f637de833957 + + https://github.com/maestro-auth-test/dotnet + be28ec777bf12db631725399c442448d52093087 diff --git a/eng/build.ps1 b/eng/build.ps1 index a155c02046b31..591ad44e6241f 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -734,6 +734,15 @@ try { Push-Location $RepoRoot + # Workaround for DOTNET_HOST_PATH not being set by older MSBuild + # Removal is tracked by https://github.com/dotnet/roslyn/issues/80742 + if (-not $env:DOTNET_HOST_PATH) { + $env:DOTNET_HOST_PATH = Join-Path (Join-Path $RepoRoot '.dotnet') 'dotnet' + if (-not (Test-Path $env:DOTNET_HOST_PATH)) { + $env:DOTNET_HOST_PATH = "$($env:DOTNET_HOST_PATH).exe" + } + } + Subst-TempDir if ($ci) { diff --git a/eng/targets/TargetFrameworks.props b/eng/targets/TargetFrameworks.props index f5501deb5e5ef..bdc7bb8661fd3 100644 --- a/eng/targets/TargetFrameworks.props +++ b/eng/targets/TargetFrameworks.props @@ -1,12 +1,12 @@ - - diff --git a/global.json b/global.json index 3510281283d76..7f2a0fab49fd9 100644 --- a/global.json +++ b/global.json @@ -1,15 +1,16 @@ { "sdk": { - "version": "10.0.100-rc.1.25451.107", + "version": "10.0.100-rc.2.25502.107", "allowPrerelease": false, "rollForward": "patch" }, "tools": { - "dotnet": "10.0.100-rc.1.25451.107", + "dotnet": "10.0.100-rc.2.25502.107", "vs": { "version": "17.14.0" }, - "vswhere": "3.1.7" + "vswhere": "3.1.7", + "xcopy-msbuild": "17.14.16" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.25509.1", diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index c4a127094f72d..20b6d161bd54f 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -45,6 +45,8 @@ + + diff --git a/src/Analyzers/CSharp/Analyzers/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs index 9acb10643398d..1dfbe9120f9d6 100644 --- a/src/Analyzers/CSharp/Analyzers/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -17,17 +18,13 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression; using Constants = ConvertSwitchStatementToExpressionConstants; [DiagnosticAnalyzer(LanguageNames.CSharp)] -internal sealed partial class ConvertSwitchStatementToExpressionDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal sealed partial class ConvertSwitchStatementToExpressionDiagnosticAnalyzer() + : AbstractBuiltInCodeStyleDiagnosticAnalyzer(IDEDiagnosticIds.ConvertSwitchStatementToExpressionDiagnosticId, + EnforceOnBuildValues.ConvertSwitchStatementToExpression, + CSharpCodeStyleOptions.PreferSwitchExpression, + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Convert_switch_statement_to_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_switch_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources))) { - public ConvertSwitchStatementToExpressionDiagnosticAnalyzer() - : base(IDEDiagnosticIds.ConvertSwitchStatementToExpressionDiagnosticId, - EnforceOnBuildValues.ConvertSwitchStatementToExpression, - CSharpCodeStyleOptions.PreferSwitchExpression, - new LocalizableResourceString(nameof(CSharpAnalyzersResources.Convert_switch_statement_to_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), - new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_switch_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources))) - { - } - protected override void InitializeWorker(AnalysisContext context) => context.RegisterCompilationStartAction(context => { @@ -48,9 +45,11 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) var switchStatement = context.Node; if (switchStatement.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)) - { return; - } + + // Avoid providing code fixes for switch statements containing directives + if (switchStatement.ContainsDirectives) + return; var (nodeToGenerate, declaratorToRemoveOpt) = Analyzer.Analyze( (SwitchStatementSyntax)switchStatement, diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs index d31694d8f4d60..09b8536d26371 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs @@ -217,7 +217,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol? } var rewrittenConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenExpression, cancellationToken).ConvertedType; - if (!lambdaTypeInfo.ConvertedType.Equals(rewrittenConvertedType)) + if (!lambdaTypeInfo.ConvertedType.Equals(rewrittenConvertedType, SymbolEqualityComparer.IncludeNullability)) return; if (OverloadsChanged( @@ -274,7 +274,16 @@ private static bool IsIdentityOrImplicitConversion(Compilation compilation, ITyp return false; var conversion = compilation.ClassifyConversion(type1, type2); - return conversion.IsIdentityOrImplicitReference(); + if (!conversion.IsIdentityOrImplicitReference()) + return false; + + // When the types are the same except for nullability annotations, we need to check + // if they're truly compatible. This prevents suggesting conversions that would introduce + // nullability warnings, such as converting Task to Task on an invariant type. + // For types that differ (e.g., string vs object), we allow the conversion as that's proper + // covariance/contravariance and won't produce nullability warnings. + return !type1.Equals(type2, SymbolEqualityComparer.Default) || + type1.Equals(type2, SymbolEqualityComparer.IncludeNullability); } private static bool MayHaveSideEffects(ExpressionSyntax expression) diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer.cs index f7d161c8a19aa..c5e336169248f 100644 --- a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions; + namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; [DiagnosticAnalyzer(LanguageNames.CSharp)] diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..36810dbace4f3 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsDiagnosticAnalyzer.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +internal sealed partial class CSharpRemoveUnnecessaryNullableWarningSuppressionsDiagnosticAnalyzer() + : AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer( + IDEDiagnosticIds.RemoveUnnecessaryNullableWarningSuppression, + EnforceOnBuildValues.RemoveUnnecessaryNullableWarningSuppression, + option: null, + fadingOption: null, + new LocalizableResourceString(nameof(AnalyzersResources.Remove_unnecessary_suppression), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)), + new LocalizableResourceString(nameof(AnalyzersResources.Suppression_is_unnecessary), AnalyzersResources.ResourceManager, typeof(CompilerExtensionsResources))) +{ + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + => context.RegisterSemanticModelAction(AnalyzeSemanticModel); + + private void AnalyzeSemanticModel(SemanticModelAnalysisContext context) + { + if (ShouldSkipAnalysis(context, notification: null)) + return; + + using var _ = ArrayBuilder.GetInstance(out var unnecessaryNodes); + UnnecessaryNullableWarningSuppressionsUtilities.AddUnnecessaryNodes(context.SemanticModel, unnecessaryNodes, context.CancellationToken); + + foreach (var unaryNode in unnecessaryNodes) + { + context.ReportDiagnostic(Diagnostic.Create( + Descriptor, + unaryNode.OperatorToken.GetLocation(), + [unaryNode.GetLocation()])); + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/UnnecessaryNullableWarningSuppressionsUtilities.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/UnnecessaryNullableWarningSuppressionsUtilities.cs new file mode 100644 index 0000000000000..aa8976182d45b --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessarySuppressions/UnnecessaryNullableWarningSuppressionsUtilities.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; + +internal static class UnnecessaryNullableWarningSuppressionsUtilities +{ + private static bool ContainsErrorOrWarning(IEnumerable diagnostics) + => diagnostics.Any(d => d.Severity is DiagnosticSeverity.Error or DiagnosticSeverity.Warning); + + private static bool ShouldAnalyzeNode(SemanticModel semanticModel, PostfixUnaryExpressionSyntax node, CancellationToken cancellationToken) + { + if (node is not PostfixUnaryExpressionSyntax(SyntaxKind.SuppressNullableWarningExpression) postfixUnary) + return false; + + var spanToCheck = GetSpanToCheck(postfixUnary); + if (spanToCheck is null) + return false; + + // If there are any syntax or semantic diagnostics already in this node, then ignore it. We can't make a good + // judgement on how necessary the suppression is if there are other problems. + if (ContainsErrorOrWarning(postfixUnary.GetDiagnostics())) + return false; + + var semanticDiagnostics = semanticModel.GetDiagnostics(postfixUnary.Span, cancellationToken); + if (ContainsErrorOrWarning(semanticDiagnostics)) + return false; + + return true; + } + + public static void AddUnnecessaryNodes( + SemanticModel semanticModel, + ArrayBuilder result, + CancellationToken cancellationToken) + { + using var _1 = ArrayBuilder.GetInstance(out var nodesToCheck); + + var originalTree = semanticModel.SyntaxTree; + var originalRoot = originalTree.GetRoot(cancellationToken); + + foreach (var existingNode in originalRoot.DescendantNodes()) + { + if (existingNode is PostfixUnaryExpressionSyntax postfixUnary && + ShouldAnalyzeNode(semanticModel, postfixUnary, cancellationToken)) + { + nodesToCheck.Add(postfixUnary); + } + } + + if (nodesToCheck.IsEmpty) + return; + + using var _2 = PooledDictionary.GetInstance(out var nodeToAnnotation); + foreach (var node in nodesToCheck) + nodeToAnnotation[node] = new SyntaxAnnotation(); + + ForkSemanticModelAndCheckNodes(semanticModel, nodesToCheck, nodeToAnnotation, result, cancellationToken); + } + + private static void ForkSemanticModelAndCheckNodes( + SemanticModel semanticModel, + ArrayBuilder nodesToCheck, + PooledDictionary nodeToAnnotation, + ArrayBuilder result, + CancellationToken cancellationToken) + { + var originalTree = semanticModel.SyntaxTree; + var originalRoot = originalTree.GetRoot(cancellationToken); + + var updatedTree = originalTree.WithRootAndOptions( + originalRoot.ReplaceNodes( + nodesToCheck, + (original, current) => current.Operand.WithAdditionalAnnotations(nodeToAnnotation[original])), + originalTree.Options); + var updateRoot = updatedTree.GetRoot(cancellationToken); + var updatedCompilation = semanticModel.Compilation + .ReplaceSyntaxTree(originalTree, updatedTree) + .WithOptions(semanticModel.Compilation.Options.WithSpecificDiagnosticOptions([])); + var updatedSemanticModel = updatedCompilation.GetSemanticModel(updatedTree); + + // Group nodes by the span we need to check for errors/warnings. That way we only need to get the diagnostics + // once per span instead of once per node. + foreach (var group in nodeToAnnotation.GroupBy(tuple => GetSpanToCheck(updateRoot.GetAnnotatedNodes(tuple.Value).Single()))) + { + var groupSpan = group.Key; + if (groupSpan is null) + continue; + + var updatedDiagnostics = updatedSemanticModel.GetDiagnostics(groupSpan, cancellationToken); + if (ContainsErrorOrWarning(updatedDiagnostics)) + continue; + + // If there were no errors in that span after removing all the suppressions, then we can offer all of these + // nodes up for fixing. + foreach (var (suppressionNode, _) in group) + result.Add(suppressionNode); + } + } + + private static TextSpan? GetSpanToCheck(SyntaxNode updatedNode) + { + // If we're in a global statement, check all global statements from the start of this one to the end of the last one. + var globalStatement = updatedNode.Ancestors().OfType().FirstOrDefault(); + if (globalStatement is not null) + { + var compilationUnit = (CompilationUnitSyntax)globalStatement.GetRequiredParent(); + return TextSpan.FromBounds(globalStatement.SpanStart, compilationUnit.Members.OfType().Last().Span.End); + } + + // Otherwise, find our containing code-containing member (attributes, accessors, fields, methods, properties, + // anonymous methods), and check that entire member. This means we only offer to remove the suppression if all + // the suppressions in the member are unnecessary. We need this granularity as doing things on a + // per-suppression is just far too slow. + // + // We also check at this level because suppressions can have effects far outside of the containing statement (or + // even things like the containing block. For example: a suppression inside a block like `a = b!` can affect + // the nullability of a variable which may be referenced far outside of the block. So we really need to check + // the entire code region that the suppression is in to make an accurate determination. + var ancestor = updatedNode.Ancestors().FirstOrDefault( + n => n is AttributeSyntax + or AccessorDeclarationSyntax + or AnonymousMethodExpressionSyntax + or BaseFieldDeclarationSyntax + or BaseMethodDeclarationSyntax + or BasePropertyDeclarationSyntax); + + return ancestor?.Span; + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AssignOutParametersAtStartCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AssignOutParametersAtStartCodeFixProvider.cs index 99159cf61227a..fecd7def77b5d 100644 --- a/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AssignOutParametersAtStartCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AssignOutParametersAtStartCodeFixProvider.cs @@ -44,12 +44,7 @@ statement.Parent is BlockSyntax block && return; } - context.RegisterCodeFix( - CodeAction.Create( - CSharpCodeFixesResources.Assign_out_parameters_at_start, - GetDocumentUpdater(context), - nameof(CSharpCodeFixesResources.Assign_out_parameters_at_start)), - context.Diagnostics); + RegisterCodeFix(context, CSharpCodeFixesResources.Assign_out_parameters_at_start, nameof(CSharpCodeFixesResources.Assign_out_parameters_at_start)); } protected override void AssignOutParameters( diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index 4c18d2aa97c1d..bc9605c2b30bd 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -87,6 +87,7 @@ + @@ -188,6 +189,7 @@ + \ No newline at end of file diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceCodeFixProvider.cs index f37a7574042de..10f9731c35dec 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertNamespace/ConvertNamespaceCodeFixProvider.cs @@ -40,10 +40,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) _ => throw ExceptionUtilities.UnexpectedValue(diagnostic.Id), }); - context.RegisterCodeFix( - CodeAction.Create(title, GetDocumentUpdater(context), equivalenceKey), - context.Diagnostics); - + RegisterCodeFix(context, title, equivalenceKey); return Task.CompletedTask; } diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs index d68ba05e14247..1423b58a90466 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs @@ -34,14 +34,6 @@ public override ImmutableArray FixableDiagnosticIds public override Task RegisterCodeFixesAsync(CodeFixContext context) { - var switchLocation = context.Diagnostics.First().AdditionalLocations[0]; - var switchStatement = (SwitchStatementSyntax)switchLocation.FindNode(getInnermostNodeForTie: true, context.CancellationToken); - if (switchStatement.ContainsDirectives) - { - // Avoid providing code fixes for switch statements containing directives - return Task.CompletedTask; - } - RegisterCodeFix(context, CSharpAnalyzersResources.Convert_switch_statement_to_expression, nameof(CSharpAnalyzersResources.Convert_switch_statement_to_expression)); return Task.CompletedTask; } diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsCodeFixProvider.cs index e935efafd8ef4..45488dabb5903 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryImports/CSharpRemoveUnnecessaryImportsCodeFixProvider.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryImports; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryImports), Shared] [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddMissingReference)] +[ExtensionOrder(Before = PredefinedCodeFixProviderNames.FileHeader)] [ExtensionOrder(Before = PredefinedCodeFixProviderNames.ConvertToProgramMain)] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryNullableDirective/CSharpRemoveUnnecessaryNullableDirectiveCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryNullableDirective/CSharpRemoveUnnecessaryNullableDirectiveCodeFixProvider.cs index 43efc0ada18ef..ce5e78e396373 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryNullableDirective/CSharpRemoveUnnecessaryNullableDirectiveCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryNullableDirective/CSharpRemoveUnnecessaryNullableDirectiveCodeFixProvider.cs @@ -19,8 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.RemoveUnnecessaryNullableDirective; -[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryNullableDirective)] -[Shared] +[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryNullableDirective), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpRemoveUnnecessaryNullableDirectiveCodeFixProvider() diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsCodeFixProvider.cs new file mode 100644 index 0000000000000..4d0ab49c8cadd --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessarySuppressions/CSharpRemoveUnnecessaryNullableWarningSuppressionsCodeFixProvider.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryNullableWarningSuppressions), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpRemoveUnnecessaryNullableWarningSuppressionsCodeFixProvider() + : SyntaxEditorBasedCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds => [IDEDiagnosticIds.RemoveUnnecessaryNullableWarningSuppression]; + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + RegisterCodeFix(context, AnalyzersResources.Remove_unnecessary_suppression, nameof(AnalyzersResources.Remove_unnecessary_suppression)); + return Task.CompletedTask; + } + + protected override Task FixAllAsync( + Document document, + ImmutableArray diagnostics, + SyntaxEditor editor, + CancellationToken cancellationToken) + { + foreach (var diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start)) + { + if (diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken) is PostfixUnaryExpressionSyntax unaryExpression) + { + editor.ReplaceNode( + unaryExpression, + (current, _) => ((PostfixUnaryExpressionSyntax)current).Operand.WithTriviaFrom(current)); + } + } + + return Task.CompletedTask; + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnusedLocalFunction/CSharpRemoveUnusedLocalFunctionCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnusedLocalFunction/CSharpRemoveUnusedLocalFunctionCodeFixProvider.cs index 6fa5f28aa5519..427d2f54205b2 100644 --- a/src/Analyzers/CSharp/CodeFixes/RemoveUnusedLocalFunction/CSharpRemoveUnusedLocalFunctionCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnusedLocalFunction/CSharpRemoveUnusedLocalFunctionCodeFixProvider.cs @@ -30,12 +30,7 @@ public sealed override ImmutableArray FixableDiagnosticIds public override Task RegisterCodeFixesAsync(CodeFixContext context) { - context.RegisterCodeFix( - CodeAction.Create( - CSharpCodeFixesResources.Remove_unused_function, - GetDocumentUpdater(context), - nameof(CSharpCodeFixesResources.Remove_unused_function)), - context.Diagnostics); + RegisterCodeFix(context, CSharpCodeFixesResources.Remove_unused_function, nameof(CSharpCodeFixesResources.Remove_unused_function)); return Task.CompletedTask; } diff --git a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs index 74c11ed1b67a4..e9d06f96ab129 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseIsNullCheck/CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider.cs @@ -41,9 +41,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) var negated = diagnostic.Properties.ContainsKey(UseIsNullConstants.Negated); var title = GetTitle(negated, diagnostic.Location.SourceTree!.Options); - context.RegisterCodeFix( - CodeAction.Create(title, GetDocumentUpdater(context), title), - context.Diagnostics); + RegisterCodeFix(context, title, title); } return Task.CompletedTask; diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index d421e95bdebd4..8a416a9c9b2db 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -91,6 +91,7 @@ + diff --git a/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs b/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs index d52bb08843ecd..218dd346ec2b1 100644 --- a/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs +++ b/src/Analyzers/CSharp/Tests/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs @@ -1061,7 +1061,7 @@ static void Main() { } static int GetValue(int input) { - [|switch|] (input) + switch (input) { case 1: return 42; @@ -1098,7 +1098,7 @@ static int GetValue(int input) 1 => 42, _ => 80, }; - [|switch|] (input) + switch (input) { case 1: return 42; @@ -1134,7 +1134,7 @@ static int GetValue(int input) return 80; } - [|switch|] (input) + switch (input) { case 1: return 42; diff --git a/src/Analyzers/CSharp/Tests/GenerateMethod/GenerateMethodTests.cs b/src/Analyzers/CSharp/Tests/GenerateMethod/GenerateMethodTests.cs index d1ce4a4b184d7..abc94ae249764 100644 --- a/src/Analyzers/CSharp/Tests/GenerateMethod/GenerateMethodTests.cs +++ b/src/Analyzers/CSharp/Tests/GenerateMethod/GenerateMethodTests.cs @@ -10614,7 +10614,7 @@ private int Goo() } """, new(parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp14))); - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80628")] public Task TestGenerateAbstractMethodReturningTask() => TestInRegularAndScriptAsync( """ diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs index 576891bc75c89..002a5c881260f 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs @@ -2045,4 +2045,180 @@ readonly struct S public void M() { } } """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78108")] + public Task TestNotWithNullabilityDifferenceInTaskReturnType() + => TestMissingInRegularAndScriptAsync( + """ + #nullable enable + using System; + using System.Threading.Tasks; + + public static class Program + { + public static void Main() + { + AcceptAsyncDelegate(async () => await GetNonNullStringAsync()); + } + + private static void AcceptAsyncDelegate(Func> _) + { + } + + private static Task GetNonNullStringAsync() + { + return Task.FromResult("Fallback Value"); + } + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78108")] + public Task TestWithNullabilityMatchInTaskReturnType() + => TestInRegularAndScriptAsync( + """ + #nullable enable + using System; + using System.Threading.Tasks; + + public static class Program + { + public static void Main() + { + AcceptAsyncDelegate([|async () => await |]GetNullableStringAsync()); + } + + private static void AcceptAsyncDelegate(Func> _) + { + } + + private static Task GetNullableStringAsync() + { + return Task.FromResult("Fallback Value"); + } + } + """, + """ + #nullable enable + using System; + using System.Threading.Tasks; + + public static class Program + { + public static void Main() + { + AcceptAsyncDelegate(GetNullableStringAsync); + } + + private static void AcceptAsyncDelegate(Func> _) + { + } + + private static Task GetNullableStringAsync() + { + return Task.FromResult("Fallback Value"); + } + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78108")] + public Task TestCovarianceWithNullabilityEnabled() + => TestInRegularAndScriptAsync( + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(object o) => ""; + } + """, + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(object o) => ""; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78108")] + public Task TestContravarianceWithNullabilityEnabled() + => TestInRegularAndScriptAsync( + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(object o) => ""; + } + """, + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(object o) => ""; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78108")] + public Task TestCovarianceWithNullableReferenceTypes() + => TestInRegularAndScriptAsync( + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string? Quux(object o) => null; + } + """, + """ + #nullable enable + using System; + + class C + { + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string? Quux(object o) => null; + } + """); } diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessaryNullableWarningSuppressionsTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessaryNullableWarningSuppressionsTests.cs new file mode 100644 index 0000000000000..c66c577de2ec9 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessarySuppressions/RemoveUnnecessaryNullableWarningSuppressionsTests.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessarySuppressions; + +using VerifyCS = CSharpCodeFixVerifier< + CSharpRemoveUnnecessaryNullableWarningSuppressionsDiagnosticAnalyzer, + CSharpRemoveUnnecessaryNullableWarningSuppressionsCodeFixProvider>; + +[Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessarySuppressions)] +[WorkItem("https://github.com/dotnet/roslyn/issues/44176")] +public sealed class RemoveUnnecessaryNullableWarningSuppressionsTests +{ + [Fact] + public Task KeepWhenNeeded1() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + void M(string? s) + { + var t = s!.ToString(); + } + } + """, + }.RunAsync(); + + [Fact] + public Task KeepWhenNeeded2() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + void M() + { + object o = null!; + } + } + """, + }.RunAsync(); + + [Fact] + public Task RemoveWhenNotNeeded1() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + void M() + { + object o = ""[|!|]; + } + } + """, + FixedCode = """ + #nullable enable + + class C + { + void M() + { + object o = ""; + } + } + """, + }.RunAsync(); + + [Fact] + public Task RemoveWhenNotNeeded_FixAll() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + void M() + { + object o = ((""[|!|]) + "")[|!|]; + } + } + """, + FixedCode = """ + #nullable enable + + class C + { + void M() + { + object o = (("") + ""); + } + } + """, + }.RunAsync(); + + [Fact] + public Task TestWithDefaultAndTypeParameter() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + public TLanguageService GetService() + { + return default!; + } + } + """, + }.RunAsync(); + + [Fact] + public Task TestInAttribute() + => new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class XAttribute : System.Attribute + { + public XAttribute(string? s) { } + } + + [X(null[|!|])] + class C + { + } + """, + FixedCode = """ + #nullable enable + + class XAttribute : System.Attribute + { + public XAttribute(string? s) { } + } + + [X(null)] + class C + { + } + """, + }.RunAsync(); +} diff --git a/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForAssignmentTests.cs b/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForAssignmentTests.cs index 98be54b8413eb..923fa91a054a1 100644 --- a/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForAssignmentTests.cs +++ b/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForAssignmentTests.cs @@ -2366,4 +2366,70 @@ void M(C c) } """, languageVersion: LanguageVersion.CSharp14); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80640")] + public Task TestWhenBooleanOperatorsAreUsed() + => TestInRegularAndScriptAsync(""" + class C + { + void M(int i) + { + [|if|] (this) + { + i = 1; + } + else + { + i = 0; + } + } + + public static bool operator true(C v) => true; + + public static bool operator false(C v) => false; + } + """, """ + class C + { + void M(int i) + { + i = this ? 1 : 0; + } + + public static bool operator true(C v) => true; + + public static bool operator false(C v) => false; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80640")] + public Task TestWithImplicitBoolConversion() + => TestInRegularAndScriptAsync(""" + class C + { + void M(int i) + { + [|if|] (this) + { + i = 1; + } + else + { + i = 0; + } + } + + public static implicit operator bool(C v) => true; + } + """, """ + class C + { + void M(int i) + { + i = this ? 1 : 0; + } + + public static implicit operator bool(C v) => true; + } + """); } diff --git a/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForReturnTests.cs b/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForReturnTests.cs index 7907a3953265a..128443f286ac7 100644 --- a/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForReturnTests.cs +++ b/src/Analyzers/CSharp/Tests/UseConditionalExpression/UseConditionalExpressionForReturnTests.cs @@ -2238,4 +2238,58 @@ public object Convert(Type type, string body) } } """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80640")] + public Task TestMissingWhenBoolOperatorsAreUsed() + => TestMissingAsync(""" + class C + { + bool M() + { + [||]if (this) + { + return true; + } + else + { + return false; + } + } + + public static bool operator true(C v) => true; + + public static bool operator false(C v) => false; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80640")] + public Task TestWithImplicitBoolConversion() + => TestInRegularAndScriptAsync(""" + class C + { + bool M() + { + [|if|] (this) + { + return true; + } + else + { + return false; + } + } + + public static implicit operator bool(C v) => true; + } + """, """ + class C + { + bool M() + { + return this; + } + + public static implicit operator bool(C v) => true; + } + """); } diff --git a/src/Analyzers/Core/Analyzers/AnalyzersResources.resx b/src/Analyzers/Core/Analyzers/AnalyzersResources.resx index 4223c2a2f8317..7022298acc4ce 100644 --- a/src/Analyzers/Core/Analyzers/AnalyzersResources.resx +++ b/src/Analyzers/Core/Analyzers/AnalyzersResources.resx @@ -322,6 +322,9 @@ Remove unnecessary suppression + + Suppression is unnecessary + Remove redundant equality diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 52ee6ead8fbf8..4e79881afb13e 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -127,6 +127,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild ConstructorInitializerPlacement = /*IDE2004*/ EnforceOnBuild.WhenExplicitlyEnabled; public const EnforceOnBuild ConditionalExpressionPlacement = /*IDE2005*/ EnforceOnBuild.WhenExplicitlyEnabled; public const EnforceOnBuild ArrowExpressionClausePlacement = /*IDE2006*/ EnforceOnBuild.WhenExplicitlyEnabled; + public const EnforceOnBuild RemoveUnnecessaryNullableWarningSuppression = /*IDE0004*/ EnforceOnBuild.WhenExplicitlyEnabled; // TODO: Move to 'Recommended' OR 'HighlyRecommended' bucket once performance problems are addressed: https://github.com/dotnet/roslyn/issues/43304 public const EnforceOnBuild Regex = /*RE0001*/ EnforceOnBuild.WhenExplicitlyEnabled; public const EnforceOnBuild Json = /*JSON001*/ EnforceOnBuild.WhenExplicitlyEnabled; @@ -140,7 +141,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild PreferBuiltInOrFrameworkType = /*IDE0049*/ EnforceOnBuild.Never; public const EnforceOnBuild ConvertAnonymousTypeToTuple = /*IDE0050*/ EnforceOnBuild.Never; public const EnforceOnBuild RemoveUnreachableCode = /*IDE0035*/ EnforceOnBuild.Never; // Non-configurable fading diagnostic corresponding to CS0162. - public const EnforceOnBuild RemoveUnnecessarySuppression = /*IDE0079*/ EnforceOnBuild.Never; // IDE-only analyzer. + public const EnforceOnBuild RemoveUnnecessaryPragmaSuppression = /*IDE0079*/ EnforceOnBuild.Never; // IDE-only analyzer. public const EnforceOnBuild CopilotImplementNotImplementedException = /*IDE3000*/ EnforceOnBuild.Never; // IDE-only analyzer. // Pure IDE feature for lighting up editor features. Do not enforce on build. diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index e3cac691d325d..5ddd99716378a 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -211,6 +211,8 @@ internal static class IDEDiagnosticIds public const string SimplifyPropertyAccessorDiagnosticId = "IDE0360"; + public const string RemoveUnnecessaryNullableWarningSuppression = "IDE0370"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.cs index d942741c5cea8..efd8f45ae5d00 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessarySuppressions/AbstractRemoveUnnecessaryPragmaSuppressionsDiagnosticAnalyzer.cs @@ -29,7 +29,7 @@ internal abstract class AbstractRemoveUnnecessaryInlineSuppressionsDiagnosticAna nameof(AnalyzersResources.Remove_unnecessary_suppression), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)); internal static readonly DiagnosticDescriptor s_removeUnnecessarySuppressionDescriptor = CreateDescriptor( IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, - EnforceOnBuildValues.RemoveUnnecessarySuppression, + EnforceOnBuildValues.RemoveUnnecessaryPragmaSuppression, s_localizableRemoveUnnecessarySuppression, s_localizableRemoveUnnecessarySuppression, hasAnyCodeStyleOption: false, isUnnecessary: true); diff --git a/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs b/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs index b348431c9f96c..dfd0992df857c 100644 --- a/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs +++ b/src/Analyzers/Core/Analyzers/UseConditionalExpression/ForReturn/UseConditionalExpressionForReturnHelpers.cs @@ -46,6 +46,12 @@ public static bool TryMatchPattern( // // note: either (but not both) of these statements can be throw-statements. + // If true/false operators are used simplifying expression will cause a compiler error + if (ifOperation.Condition is IUnaryOperation { OperatorKind: UnaryOperatorKind.True or UnaryOperatorKind.False }) + { + return false; + } + if (falseStatement == null) { if (ifOperation.Parent is not IBlockOperation parentBlock) diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf index 26496c2b00074..3243af0a9b511 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.cs.xlf @@ -411,6 +411,11 @@ Zjednodušit inicializaci objektu + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text Hlavička souboru neodpovídá požadovanému textu. diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf index 5a52eac44aa90..b0a370dd92b37 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.de.xlf @@ -411,6 +411,11 @@ Initialisierung von Objekten vereinfachen + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text Der Dateiheader stimmt nicht mit dem erforderlichen Text überein. diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf index ecd358040dcbf..926ab59d006b0 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.es.xlf @@ -411,6 +411,11 @@ Simplificar la inicialización de objetos + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text El encabezado de archivo no coincide con el texto requerido. diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf index 34cc6b9e5dee0..837bac13ae7eb 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.fr.xlf @@ -411,6 +411,11 @@ Simplifier l'initialisation des objets + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text L'en-tête du fichier ne correspond pas au texte nécessaire diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf index 853e44033183e..38e039414e653 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.it.xlf @@ -411,6 +411,11 @@ Semplifica l'inizializzazione degli oggetti + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text L'intestazione del file non corrisponde al testo obbligatorio diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf index 92f825cce91a0..d02724bdd7121 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ja.xlf @@ -412,6 +412,11 @@ オブジェクトの初期化を簡略化します + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text ファイル ヘッダーが、必要なテキストと一致しません diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf index 086699fda54a3..7653d5cab4a24 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ko.xlf @@ -411,6 +411,11 @@ 개체 초기화 단순화 + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text 파일 헤더가 필요한 텍스트와 일치하지 않습니다. diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf index 84c9dda0cda65..9cdf89b501635 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pl.xlf @@ -411,6 +411,11 @@ Uprość inicjowanie obiektów + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text Nagłówek pliku nie jest zgodny z wymaganym tekstem diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf index 704936e6e3794..4be0173131385 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.pt-BR.xlf @@ -411,6 +411,11 @@ Simplificar a inicialização de objeto + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text O cabeçalho do arquivo não corresponde ao texto necessário diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf index 16a067d61c1ab..e7d5263258662 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.ru.xlf @@ -411,6 +411,11 @@ Упростите инициализацию объекта + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text Заголовок файла не соответствует требуемому тексту diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf index af0aedeeadaf3..ca2a1be137f67 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.tr.xlf @@ -411,6 +411,11 @@ Nesne başlatmayı kolaylaştır + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text Dosya üst bilgisi gerekli metinle eşleşmiyor diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf index 2f89565cd05a3..0a9c1c4b45602 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hans.xlf @@ -411,6 +411,11 @@ 简化对象初始化 + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text 文件标题与所需文本不匹配 diff --git a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf index 51f4235f1d10d..69dfbcf79adf8 100644 --- a/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/Core/Analyzers/xlf/AnalyzersResources.zh-Hant.xlf @@ -411,6 +411,11 @@ 簡化物件初始化 + + Suppression is unnecessary + Suppression is unnecessary + + The file header does not match the required text 檔案標頭與必要文字不相符 diff --git a/src/Analyzers/Core/CodeFixes/AddExplicitCast/AbstractAddExplicitCastCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/AddExplicitCast/AbstractAddExplicitCastCodeFixProvider.cs index f38bd63e087e3..d1d7eebefb81a 100644 --- a/src/Analyzers/Core/CodeFixes/AddExplicitCast/AbstractAddExplicitCastCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/AddExplicitCast/AbstractAddExplicitCastCodeFixProvider.cs @@ -12,10 +12,8 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes.AddExplicitCast; diff --git a/src/Analyzers/Core/CodeFixes/AddRequiredParentheses/AddRequiredParenthesesCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/AddRequiredParentheses/AddRequiredParenthesesCodeFixProvider.cs index 97ccef4d02f49..02183781df90d 100644 --- a/src/Analyzers/Core/CodeFixes/AddRequiredParentheses/AddRequiredParenthesesCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/AddRequiredParentheses/AddRequiredParenthesesCodeFixProvider.cs @@ -29,13 +29,8 @@ protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic, Doc public override Task RegisterCodeFixesAsync(CodeFixContext context) { - var firstDiagnostic = context.Diagnostics[0]; - context.RegisterCodeFix( - CodeAction.Create( - AnalyzersResources.Add_parentheses_for_clarity, - GetDocumentUpdater(context), - firstDiagnostic.Properties[AddRequiredParenthesesConstants.EquivalenceKey]!), - context.Diagnostics); + RegisterCodeFix( + context, AnalyzersResources.Add_parentheses_for_clarity, context.Diagnostics[0].Properties[AddRequiredParenthesesConstants.EquivalenceKey]!); return Task.CompletedTask; } diff --git a/src/Analyzers/Core/CodeFixes/MakeTypePartial/AbstractMakeTypePartialCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/MakeTypePartial/AbstractMakeTypePartialCodeFixProvider.cs index 4de179bac6305..7460dff1673ce 100644 --- a/src/Analyzers/Core/CodeFixes/MakeTypePartial/AbstractMakeTypePartialCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/MakeTypePartial/AbstractMakeTypePartialCodeFixProvider.cs @@ -12,13 +12,9 @@ namespace Microsoft.CodeAnalysis.MakeTypePartial; -internal abstract class AbstractMakeTypePartialCodeFixProvider : SyntaxEditorBasedCodeFixProvider +internal abstract class AbstractMakeTypePartialCodeFixProvider() + : SyntaxEditorBasedCodeFixProvider(supportsFixAll: false) { - protected AbstractMakeTypePartialCodeFixProvider() - : base(supportsFixAll: false) - { - } - public override Task RegisterCodeFixesAsync(CodeFixContext context) { RegisterCodeFix(context, CodeFixesResources.Make_type_partial, nameof(CodeFixesResources.Make_type_partial)); diff --git a/src/Analyzers/Core/CodeFixes/OrderModifiers/AbstractOrderModifiersCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/OrderModifiers/AbstractOrderModifiersCodeFixProvider.cs index ae3eb36cf82fa..8a704a39af120 100644 --- a/src/Analyzers/Core/CodeFixes/OrderModifiers/AbstractOrderModifiersCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/OrderModifiers/AbstractOrderModifiersCodeFixProvider.cs @@ -16,18 +16,12 @@ namespace Microsoft.CodeAnalysis.OrderModifiers; -internal abstract class AbstractOrderModifiersCodeFixProvider : SyntaxEditorBasedCodeFixProvider +internal abstract class AbstractOrderModifiersCodeFixProvider( + ISyntaxFacts syntaxFacts, + AbstractOrderModifiersHelpers helpers) : SyntaxEditorBasedCodeFixProvider { - private readonly ISyntaxFacts _syntaxFacts; - private readonly AbstractOrderModifiersHelpers _helpers; - - protected AbstractOrderModifiersCodeFixProvider( - ISyntaxFacts syntaxFacts, - AbstractOrderModifiersHelpers helpers) - { - _syntaxFacts = syntaxFacts; - _helpers = helpers; - } + private readonly ISyntaxFacts _syntaxFacts = syntaxFacts; + private readonly AbstractOrderModifiersHelpers _helpers = helpers; protected abstract ImmutableArray FixableCompilerErrorIds { get; } protected abstract CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider options); diff --git a/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchCodeFixProvider.cs index f8bd8a1e1d7e3..a39f26bbbfa16 100644 --- a/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchCodeFixProvider.cs @@ -22,17 +22,14 @@ internal abstract class AbstractPopulateSwitchCodeFixProvider< TSwitchOperation, TSwitchSyntax, TSwitchArmSyntax, - TMemberAccessExpression> + TMemberAccessExpression>(string diagnosticId) : SyntaxEditorBasedCodeFixProvider where TSwitchOperation : IOperation where TSwitchSyntax : SyntaxNode where TSwitchArmSyntax : SyntaxNode where TMemberAccessExpression : SyntaxNode { - public sealed override ImmutableArray FixableDiagnosticIds { get; } - - protected AbstractPopulateSwitchCodeFixProvider(string diagnosticId) - => FixableDiagnosticIds = [diagnosticId]; + public sealed override ImmutableArray FixableDiagnosticIds { get; } = [diagnosticId]; protected abstract ITypeSymbol GetSwitchType(TSwitchOperation switchStatement); protected abstract ICollection GetMissingEnumMembers(TSwitchOperation switchOperation); diff --git a/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchExpressionCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchExpressionCodeFixProvider.cs index 27f5502d3ecc8..e5f2d39511d33 100644 --- a/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchExpressionCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchExpressionCodeFixProvider.cs @@ -12,19 +12,14 @@ namespace Microsoft.CodeAnalysis.PopulateSwitch; internal abstract class AbstractPopulateSwitchExpressionCodeFixProvider< - TExpressionSyntax, TSwitchSyntax, TSwitchArmSyntax, TMemberAccessExpressionSyntax> + TExpressionSyntax, TSwitchSyntax, TSwitchArmSyntax, TMemberAccessExpressionSyntax>() : AbstractPopulateSwitchCodeFixProvider< - ISwitchExpressionOperation, TSwitchSyntax, TSwitchArmSyntax, TMemberAccessExpressionSyntax> + ISwitchExpressionOperation, TSwitchSyntax, TSwitchArmSyntax, TMemberAccessExpressionSyntax>(IDEDiagnosticIds.PopulateSwitchExpressionDiagnosticId) where TExpressionSyntax : SyntaxNode where TSwitchSyntax : TExpressionSyntax where TSwitchArmSyntax : SyntaxNode where TMemberAccessExpressionSyntax : TExpressionSyntax { - protected AbstractPopulateSwitchExpressionCodeFixProvider() - : base(IDEDiagnosticIds.PopulateSwitchExpressionDiagnosticId) - { - } - protected sealed override void FixOneDiagnostic( Document document, SyntaxEditor editor, SemanticModel semanticModel, bool addCases, bool addDefaultCase, bool onlyOneDiagnostic, diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index d1af593b1a51c..bbef2eddeef32 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -108,6 +108,7 @@ internal static class PredefinedCodeFixProviderNames public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports); public const string RemoveUnnecessaryLambdaExpression = nameof(RemoveUnnecessaryLambdaExpression); public const string RemoveUnnecessaryNullableDirective = nameof(RemoveUnnecessaryNullableDirective); + public const string RemoveUnnecessaryNullableWarningSuppressions = nameof(RemoveUnnecessaryNullableWarningSuppressions); public const string RemoveUnnecessaryParentheses = nameof(RemoveUnnecessaryParentheses); public const string RemoveUnnecessaryPragmaSuppressions = nameof(RemoveUnnecessaryPragmaSuppressions); public const string RemoveUnreachableCode = nameof(RemoveUnreachableCode); diff --git a/src/Analyzers/Core/CodeFixes/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs index e62c68493be53..c30cd1f77a932 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs @@ -7,14 +7,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.RemoveAsyncModifier; @@ -37,27 +35,18 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var token = diagnostic.Location.FindToken(cancellationToken); var node = token.GetAncestor(IsAsyncSupportingFunctionSyntax); if (node == null) - { return; - } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbol = GetMethodSymbol(node, semanticModel, cancellationToken); if (methodSymbol == null) - { return; - } - if (ShouldOfferFix(methodSymbol.ReturnType, knownTypes)) - { - context.RegisterCodeFix( - CodeAction.Create( - CodeFixesResources.Remove_async_modifier, - GetDocumentUpdater(context), - nameof(CodeFixesResources.Remove_async_modifier)), - context.Diagnostics); - } + if (!ShouldOfferFix(methodSymbol.ReturnType, knownTypes)) + return; + + RegisterCodeFix(context, CodeFixesResources.Remove_async_modifier, nameof(CodeFixesResources.Remove_async_modifier)); } protected sealed override async Task FixAllAsync( diff --git a/src/Analyzers/Core/CodeFixes/UseIsNullCheck/AbstractUseIsNullForReferenceEqualsCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseIsNullCheck/AbstractUseIsNullForReferenceEqualsCodeFixProvider.cs index 2919279a5a0ff..5ccde3abcabc1 100644 --- a/src/Analyzers/Core/CodeFixes/UseIsNullCheck/AbstractUseIsNullForReferenceEqualsCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseIsNullCheck/AbstractUseIsNullForReferenceEqualsCodeFixProvider.cs @@ -38,9 +38,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) { var negated = diagnostic.Properties.ContainsKey(UseIsNullConstants.Negated); var title = GetTitle(negated, diagnostic.Location.SourceTree!.Options); - context.RegisterCodeFix( - CodeAction.Create(title, GetDocumentUpdater(context), title), - context.Diagnostics); + RegisterCodeFix(context, title, title); } return Task.CompletedTask; diff --git a/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryImports/VisualBasicRemoveUnnecessaryImportsCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryImports/VisualBasicRemoveUnnecessaryImportsCodeFixProvider.vb index 7ffb60a42a6d8..0f8483c93a36e 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryImports/VisualBasicRemoveUnnecessaryImportsCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/RemoveUnnecessaryImports/VisualBasicRemoveUnnecessaryImportsCodeFixProvider.vb @@ -13,6 +13,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryImports + Friend Class VisualBasicRemoveUnnecessaryImportsCodeFixProvider Inherits AbstractRemoveUnnecessaryImportsCodeFixProvider diff --git a/src/CodeStyle/Tools/Program.cs b/src/CodeStyle/Tools/Program.cs index ef1992aaf7006..43f96e59e37d6 100644 --- a/src/CodeStyle/Tools/Program.cs +++ b/src/CodeStyle/Tools/Program.cs @@ -147,6 +147,10 @@ bool AddRule(DiagnosticDescriptor rule) result.AppendLine(); result.AppendLine($"# {rule.Id}: {rule.Title}"); + if (rule.HelpLinkUri is not null) + { + result.AppendLine($"# {rule.HelpLinkUri}"); + } result.AppendLine($"dotnet_diagnostic.{rule.Id}.severity = {severityString}"); return true; } diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 7cdbac3203129..a84a68327cc4c 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -466,13 +466,13 @@ private static string GetMethodName(BaseMethodDeclarationSyntax baseMethodDeclar return WellKnownMemberNames.DestructorName; case SyntaxKind.OperatorDeclaration: var operatorDeclaration = (OperatorDeclarationSyntax)baseMethodDeclarationSyntax; - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, operatorDeclaration.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDeclaration)); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, baseMethodDeclarationSyntax.Modifiers, operatorDeclaration.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDeclaration)); case SyntaxKind.ConversionOperatorDeclaration: var conversionDeclaration = (ConversionOperatorDeclarationSyntax)baseMethodDeclarationSyntax; - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, conversionDeclaration.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(conversionDeclaration)); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, baseMethodDeclarationSyntax.Modifiers, conversionDeclaration.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(conversionDeclaration)); case SyntaxKind.MethodDeclaration: MethodDeclarationSyntax methodDeclSyntax = (MethodDeclarationSyntax)baseMethodDeclarationSyntax; - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, methodDeclSyntax.ExplicitInterfaceSpecifier, methodDeclSyntax.Identifier.ValueText); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, baseMethodDeclarationSyntax.Modifiers, methodDeclSyntax.ExplicitInterfaceSpecifier, methodDeclSyntax.Identifier.ValueText); default: throw ExceptionUtilities.UnexpectedValue(baseMethodDeclarationSyntax.Kind()); } @@ -491,13 +491,13 @@ private static string GetPropertyOrEventName(BasePropertyDeclarationSyntax baseP { case SyntaxKind.PropertyDeclaration: var propertyDecl = (PropertyDeclarationSyntax)basePropertyDeclarationSyntax; - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, explicitInterfaceSpecifierSyntax, propertyDecl.Identifier.ValueText); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, basePropertyDeclarationSyntax.Modifiers, explicitInterfaceSpecifierSyntax, propertyDecl.Identifier.ValueText); case SyntaxKind.IndexerDeclaration: - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, explicitInterfaceSpecifierSyntax, WellKnownMemberNames.Indexer); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, basePropertyDeclarationSyntax.Modifiers, explicitInterfaceSpecifierSyntax, WellKnownMemberNames.Indexer); case SyntaxKind.EventDeclaration: case SyntaxKind.EventFieldDeclaration: var eventDecl = (EventDeclarationSyntax)basePropertyDeclarationSyntax; - return ExplicitInterfaceHelpers.GetMemberName(outerBinder, explicitInterfaceSpecifierSyntax, eventDecl.Identifier.ValueText); + return ExplicitInterfaceHelpers.GetMemberName(outerBinder, eventDecl.Modifiers, explicitInterfaceSpecifierSyntax, eventDecl.Identifier.ValueText); default: throw ExceptionUtilities.UnexpectedValue(basePropertyDeclarationSyntax.Kind()); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs index 148146b505474..b721b66851a2a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs @@ -341,6 +341,7 @@ ImmutableArray computeSortedAndFilteredCrefExtensionMembers(NamespaceOrT if (result.Kind == LookupResultKind.Viable) { + Debug.Assert(result.Symbol is not null); sortedSymbolsBuilder ??= ArrayBuilder.GetInstance(); sortedSymbolsBuilder.Add(result.Symbol); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index b9f7f3318e37b..2756ad1f2f14a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -10896,7 +10896,8 @@ private MethodGroupResolution ResolveDefaultMethodGroup( foreach (SingleLookupResult singleLookupResult in singleLookupResults) { - Symbol extensionMember = singleLookupResult.Symbol; + var extensionMember = singleLookupResult.Symbol; + Debug.Assert(extensionMember is not null); if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember)) { // Remove static/instance mismatches @@ -11109,7 +11110,8 @@ private static bool IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(Bo var methods = ArrayBuilder.GetInstance(capacity: singleLookupResults.Count); foreach (SingleLookupResult singleLookupResult in singleLookupResults) { - Symbol extensionMember = singleLookupResult.Symbol; + var extensionMember = singleLookupResult.Symbol; + Debug.Assert(extensionMember is not null); if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember)) { // Remove static/instance mismatches diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index a900ed546f5d0..86f7056023dce 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -208,6 +208,12 @@ internal void EnumerateAllExtensionMembersInSingleBinder(ArrayBuilder, bool, bool> handler) { +#nullable enable var pathBuilder = ArrayBuilder.GetInstance(); + var stack = ArrayBuilder.GetInstance(); exploreToNode(rootNode, currentRequiresFalseWhenClause: false); + stack.Free(); pathBuilder.Free(); return; // Recursive exploration helper // Returns true to continue, false to stop - bool exploreToNode(BoundDecisionDagNode currentNode, bool currentRequiresFalseWhenClause) + bool exploreToNode(BoundDecisionDagNode? currentNode, bool currentRequiresFalseWhenClause) { - if (currentNode == targetNode) + if (currentNode is null) { - return handler(pathBuilder.ToImmutable(), currentRequiresFalseWhenClause); + return true; } - pathBuilder.Push(currentNode); + int stackSize = stack.Count; + stack.Push(currentNode); - switch (currentNode) + do { - case null: - case BoundLeafDecisionDagNode: - break; + currentNode = stack.Pop(); - case BoundTestDecisionDagNode test: - bool skipWhenTrue = test.Test is BoundDagExplicitNullTest && !nullPaths; - bool skipWhenFalse = test.Test is BoundDagNonNullTest && !nullPaths; + if (currentNode is null) + { + pathBuilder.Pop(); + continue; + } - if (!skipWhenTrue) + if (currentNode == targetNode) + { + if (!handler(pathBuilder.ToImmutable(), currentRequiresFalseWhenClause)) { - if (!exploreToNode(test.WhenTrue, currentRequiresFalseWhenClause)) - return false; + stack.Count = stackSize; + return false; } - if (!skipWhenFalse) - { - if (!exploreToNode(test.WhenFalse, currentRequiresFalseWhenClause)) - return false; - } + continue; + } - break; + pathBuilder.Push(currentNode); - case BoundEvaluationDecisionDagNode evaluation: - if (!exploreToNode(evaluation.Next, currentRequiresFalseWhenClause)) - return false; + switch (currentNode) + { + case BoundLeafDecisionDagNode: + break; - break; + case BoundTestDecisionDagNode test: + stack.Push(null); // marker to pop from pathBuilder - case BoundWhenDecisionDagNode whenNode: - pathBuilder.Pop(); - return exploreToNode(whenNode.WhenFalse, currentRequiresFalseWhenClause: true); + bool skipWhenTrue = test.Test is BoundDagExplicitNullTest && !nullPaths; + bool skipWhenFalse = test.Test is BoundDagNonNullTest && !nullPaths; - default: - throw ExceptionUtilities.UnexpectedValue(currentNode.Kind); + if (!skipWhenFalse && test.WhenFalse is not null) + { + stack.Push(test.WhenFalse); + } + + if (!skipWhenTrue && test.WhenTrue is not null) + { + stack.Push(test.WhenTrue); + } + + continue; + + case BoundEvaluationDecisionDagNode evaluation: + + if (evaluation.Next is not null) + { + stack.Push(null); // marker to pop from pathBuilder + stack.Push(evaluation.Next); + continue; + } + break; + + case BoundWhenDecisionDagNode whenNode: + pathBuilder.Pop(); + if (!exploreToNode(whenNode.WhenFalse, currentRequiresFalseWhenClause: true)) + { + stack.Count = stackSize; + return false; + } + + continue; + + default: + throw ExceptionUtilities.UnexpectedValue(currentNode.Kind); + } + + pathBuilder.Pop(); } + while (stack.Count > stackSize); - pathBuilder.Pop(); return true; } +#nullable disable } /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 1f201b57bd0b5..2abaeb4d99f5f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -2531,15 +2531,15 @@ private BetterResult BetterFunctionMember( // Params collection better-ness if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm) { - int m1ParamsOrdinal = m1LeastOverriddenParameters.Length - 1; - int m2ParamsOrdinal = m2LeastOverriddenParameters.Length - 1; + var m1LastParameter = m1LeastOverriddenParameters[^1]; + var m2LastParameter = m2LeastOverriddenParameters[^1]; for (i = 0; i < arguments.Count; ++i) { var parameter1 = getParameterOrExtensionParameter(i, m1.Result, m1LeastOverriddenParameters, m1.LeastOverriddenMember); var parameter2 = getParameterOrExtensionParameter(i, m2.Result, m2LeastOverriddenParameters, m2.LeastOverriddenMember); - if ((parameter1.Ordinal == m1ParamsOrdinal) != (parameter2.Ordinal == m2ParamsOrdinal)) + if (((object)parameter1 == m1LastParameter) != ((object)parameter2 == m2LastParameter)) { // The argument is included into params collection for one candidate, but isn't included into params collection for the other candidate break; @@ -2548,8 +2548,8 @@ private BetterResult BetterFunctionMember( if (i == arguments.Count) { - TypeSymbol t1 = m1LeastOverriddenParameters[^1].Type; - TypeSymbol t2 = m2LeastOverriddenParameters[^1].Type; + TypeSymbol t1 = m1LastParameter.Type; + TypeSymbol t2 = m2LastParameter.Type; if (!Conversions.HasIdentityConversion(t1, t2)) { @@ -2574,7 +2574,7 @@ static TypeSymbol getParameterTypeAndRefKind(int i, MemberAnalysisResult memberR var type = parameter.Type; if (memberResolutionResult.Kind == MemberResolutionKind.ApplicableInExpandedForm && - parameter.Ordinal == parameters.Length - 1) + (object)parameter == parameters[^1]) { Debug.Assert(paramsElementTypeOpt.HasType); Debug.Assert(paramsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel); diff --git a/src/Compilers/CSharp/Portable/Binder/SingleLookupResult.cs b/src/Compilers/CSharp/Portable/Binder/SingleLookupResult.cs index f130b3dced1fd..24844788a4236 100644 --- a/src/Compilers/CSharp/Portable/Binder/SingleLookupResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/SingleLookupResult.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp { @@ -22,13 +21,14 @@ internal readonly struct SingleLookupResult internal readonly LookupResultKind Kind; // the symbol or null. - internal readonly Symbol Symbol; + internal readonly Symbol? Symbol; // the error of the result, if it is NonViable or Inaccessible - internal readonly DiagnosticInfo Error; + internal readonly DiagnosticInfo? Error; - internal SingleLookupResult(LookupResultKind kind, Symbol symbol, DiagnosticInfo error) + internal SingleLookupResult(LookupResultKind kind, Symbol? symbol, DiagnosticInfo? error) { + Debug.Assert(symbol is not null || kind == LookupResultKind.Empty); this.Kind = kind; this.Symbol = symbol; this.Error = error; diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index a0f2e8f160fa4..90414e0088861 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2529,7 +2529,7 @@ Left.InputType is the InputType Right.InputType is the Left.NarrowedType --> - + diff --git a/src/Compilers/CSharp/Portable/BoundTree/NullabilityRewriter.cs b/src/Compilers/CSharp/Portable/BoundTree/NullabilityRewriter.cs index 7f279795b1e8e..c6fc38e3d2b1f 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/NullabilityRewriter.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/NullabilityRewriter.cs @@ -134,6 +134,41 @@ private BoundNode VisitBinaryOperatorBase(BoundBinaryOperatorBase binaryOperator return currentBinary!; } + public override BoundNode? VisitBinaryPattern(BoundBinaryPattern node) + { + // Use an explicit stack to avoid blowing the managed stack when visiting deeply-recursive + // binary nodes + var stack = ArrayBuilder.GetInstance(); + BoundBinaryPattern? currentBinary = node; + + do + { + stack.Push(currentBinary); + currentBinary = currentBinary.Left as BoundBinaryPattern; + } + while (currentBinary is not null); + + Debug.Assert(stack.Count > 0); + var leftChild = (BoundPattern)Visit(stack.Peek().Left); + + do + { + currentBinary = stack.Pop(); + + TypeSymbol inputType = GetUpdatedSymbol(currentBinary, currentBinary.InputType); + TypeSymbol narrowedType = GetUpdatedSymbol(currentBinary, currentBinary.NarrowedType); + + var right = (BoundPattern)Visit(currentBinary.Right); + + currentBinary = currentBinary.Update(currentBinary.Disjunction, leftChild, right, inputType, narrowedType); + + leftChild = currentBinary; + } + while (stack.Count > 0); + + return currentBinary; + } + public override BoundNode? VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { ImmutableArray originalUserDefinedOperatorsOpt = GetUpdatedArray(node, node.OriginalUserDefinedOperatorsOpt); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 34678befd7e3e..b53ececc2c9b8 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -8165,4 +8165,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The pattern is too complex to analyze for redundancy. - + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index c02333c6c0359..4eda8fbb98eb1 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1653,25 +1653,25 @@ private string GetDeclarationName(CSharpSyntaxNode declaration) case SyntaxKind.MethodDeclaration: { var methodDecl = (MethodDeclarationSyntax)declaration; - return GetDeclarationName(declaration, methodDecl.ExplicitInterfaceSpecifier, methodDecl.Identifier.ValueText); + return GetDeclarationName(declaration, methodDecl.Modifiers, methodDecl.ExplicitInterfaceSpecifier, methodDecl.Identifier.ValueText); } case SyntaxKind.PropertyDeclaration: { var propertyDecl = (PropertyDeclarationSyntax)declaration; - return GetDeclarationName(declaration, propertyDecl.ExplicitInterfaceSpecifier, propertyDecl.Identifier.ValueText); + return GetDeclarationName(declaration, propertyDecl.Modifiers, propertyDecl.ExplicitInterfaceSpecifier, propertyDecl.Identifier.ValueText); } case SyntaxKind.IndexerDeclaration: { var indexerDecl = (IndexerDeclarationSyntax)declaration; - return GetDeclarationName(declaration, indexerDecl.ExplicitInterfaceSpecifier, WellKnownMemberNames.Indexer); + return GetDeclarationName(declaration, indexerDecl.Modifiers, indexerDecl.ExplicitInterfaceSpecifier, WellKnownMemberNames.Indexer); } case SyntaxKind.EventDeclaration: { var eventDecl = (EventDeclarationSyntax)declaration; - return GetDeclarationName(declaration, eventDecl.ExplicitInterfaceSpecifier, eventDecl.Identifier.ValueText); + return GetDeclarationName(declaration, eventDecl.Modifiers, eventDecl.ExplicitInterfaceSpecifier, eventDecl.Identifier.ValueText); } case SyntaxKind.DelegateDeclaration: @@ -1707,13 +1707,13 @@ private string GetDeclarationName(CSharpSyntaxNode declaration) case SyntaxKind.OperatorDeclaration: { var operatorDecl = (OperatorDeclarationSyntax)declaration; - return GetDeclarationName(declaration, operatorDecl.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDecl)); + return GetDeclarationName(declaration, operatorDecl.Modifiers, operatorDecl.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDecl)); } case SyntaxKind.ConversionOperatorDeclaration: { var operatorDecl = (ConversionOperatorDeclarationSyntax)declaration; - return GetDeclarationName(declaration, operatorDecl.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDecl)); + return GetDeclarationName(declaration, operatorDecl.Modifiers, operatorDecl.ExplicitInterfaceSpecifier, OperatorFacts.OperatorNameFromDeclaration(operatorDecl)); } case SyntaxKind.EventFieldDeclaration: @@ -1729,7 +1729,7 @@ private string GetDeclarationName(CSharpSyntaxNode declaration) } } - private string GetDeclarationName(CSharpSyntaxNode declaration, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifierOpt, string memberName) + private string GetDeclarationName(CSharpSyntaxNode declaration, SyntaxTokenList modifiers, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifierOpt, string memberName) { if (explicitInterfaceSpecifierOpt == null) { @@ -1741,7 +1741,7 @@ private string GetDeclarationName(CSharpSyntaxNode declaration, ExplicitInterfac // Option 2: detect explicit impl and return null // Option 3: get a binder and figure out the name // For now, we're going with Option 3 - return ExplicitInterfaceHelpers.GetMemberName(_binderFactory.GetBinder(declaration), explicitInterfaceSpecifierOpt, memberName); + return ExplicitInterfaceHelpers.GetMemberName(_binderFactory.GetBinder(declaration), modifiers, explicitInterfaceSpecifierOpt, memberName); } private NamespaceSymbol GetDeclaredNamespace(NamespaceOrTypeSymbol container, TextSpan declarationSpan, NameSyntax name) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 829a5ddc65df1..90671a00425fe 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -67,7 +67,7 @@ internal EmbeddableAttributes GetNeedsGeneratedAttributes() private EmbeddableAttributes GetNeedsGeneratedAttributesInternal() { - return (EmbeddableAttributes)_needsGeneratedAttributes | Compilation.GetNeedsGeneratedAttributes(); + return (EmbeddableAttributes)_needsGeneratedAttributes | Compilation.GetNeedsGeneratedAttributes(freezeState: this is not PEDeltaAssemblyBuilder); } private void SetNeedsGeneratedAttributes(EmbeddableAttributes attributes) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 07f6a8d1b946d..deaa35f37dc0c 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2434,6 +2434,8 @@ internal enum ErrorCode WRN_RedundantPattern = 9336, HDN_RedundantPatternStackGuard = 9337, + ERR_BadVisBaseType = 9338, + // Note: you will need to do the following after adding errors: // 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) // 2) Add message to CSharpResources.resx diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index a2f9c7e5f2faa..f0bbf2ea69e02 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2542,6 +2542,7 @@ or ErrorCode.ERR_ExplicitInterfaceMemberReturnTypeMismatch or ErrorCode.HDN_RedundantPattern or ErrorCode.WRN_RedundantPattern or ErrorCode.HDN_RedundantPatternStackGuard + or ErrorCode.ERR_BadVisBaseType => false, }; #pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 0795d991a5271..bf932c078b30f 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -14976,15 +14976,6 @@ public NullabilityRewriter(ImmutableDictionary - if (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Immutable_ImmutableArray_T, out var arrayElementType)) + if ((object)node.Type.OriginalDefinition == _compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T)) { - // For `[]` try to use `ImmutableArray.Empty` singleton if available - if (node.Elements.IsEmpty && - _compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Immutable_ImmutableArray_T__Empty) is FieldSymbol immutableArrayOfTEmpty) - { - var immutableArrayOfTargetCollectionTypeEmpty = immutableArrayOfTEmpty.AsMember((NamedTypeSymbol)node.Type); - return _factory.Field(receiver: null, immutableArrayOfTargetCollectionTypeEmpty); - } - - // Otherwise try to optimize construction using `ImmutableCollectionsMarshal.AsImmutableArray`. - // Note, that we skip that path if collection expression is just `[.. readOnlySpan]` of the same element type, - // in such cases it is more efficient to emit a direct call of `ImmutableArray.Create` - if (_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray && - !CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out _)) - { - return VisitImmutableArrayCollectionExpression(node, arrayElementType, asImmutableArray); - } + return VisitArrayOrSpanCollectionExpression(node, node.Type); } + return VisitCollectionBuilderCollectionExpression(node); case CollectionExpressionTypeKind.ArrayInterface: return VisitListInterfaceCollectionExpression(node); @@ -231,101 +216,173 @@ private static bool CanOptimizeSingleSpreadAsCollectionBuilderArgument(BoundColl return spreadExpression is not null; } - private BoundExpression VisitImmutableArrayCollectionExpression(BoundCollectionExpression node, TypeWithAnnotations elementType, MethodSymbol asImmutableArray) - { - var arrayCreation = VisitArrayOrSpanCollectionExpression( - node, - CollectionExpressionTypeKind.Array, - ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType), - elementType); - // ImmutableCollectionsMarshal.AsImmutableArray(arrayCreation) - return _factory.StaticCall(asImmutableArray.Construct(ImmutableArray.Create(elementType)), ImmutableArray.Create(arrayCreation)); - } - - private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, CollectionExpressionTypeKind collectionTypeKind, TypeSymbol collectionType, TypeWithAnnotations elementType) + /// + /// Create a collection value which may involve creating an array. + /// Handles types 'T[]', 'Span{T}', 'ReadOnlySpan{T}', 'ImmutableArray{T}'. + /// Note that Span/ROS/ImmutableArray cases, may or may not involve us actually creating an array. + /// Depending on the collection elements, available APIs, or other context, we may use some other strategy. + /// + private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType) { Debug.Assert(!_inExpressionLambda); Debug.Assert(_additionalLocals is { }); Debug.Assert(node.CollectionCreation is null); // shouldn't have generated a constructor call Debug.Assert(node.Placeholder is null); + Debug.Assert(collectionType is ArrayTypeSymbol or NamedTypeSymbol); - var syntax = node.Syntax; - var elements = node.Elements; - MethodSymbol? spanConstructor = null; + if (collectionType is ArrayTypeSymbol arrayType) + { + return createArray(node, arrayType, targetsReadOnlyCollection: false); + } + + var namedType = (NamedTypeSymbol)collectionType; + + if (namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T))) + { + return createImmutableArray(node, namedType); + } + + Debug.Assert(namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T)) || + namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T))); + + return createSpan(node, namedType, isReadOnlySpan: namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T))); + + ArrayTypeSymbol getBackingArrayType(NamedTypeSymbol collectionType) + { + Debug.Assert(collectionType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T) || + collectionType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_Span_T) || + collectionType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T)); + + var elementType = collectionType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; + return ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType); + } + + BoundExpression createImmutableArray(BoundCollectionExpression node, NamedTypeSymbol immutableArrayType) + { + if (node.Elements.IsEmpty && + _factory.WellKnownMember(WellKnownMember.System_Collections_Immutable_ImmutableArray_T__Empty, isOptional: true) is FieldSymbol immutableArrayOfTEmpty) + { + // ImmutableArray value = []; + var immutableArrayOfTargetCollectionTypeEmpty = immutableArrayOfTEmpty.AsMember(immutableArrayType); + return _factory.Field(receiver: null, immutableArrayOfTargetCollectionTypeEmpty); + } + + if (CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out _)) + { + // ImmutableArray array = ImmutableArray.Create(singleSpreadSpan) + return VisitCollectionBuilderCollectionExpression(node); + } + + if (_factory.WellKnownMethod(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T, isOptional: true) is MethodSymbol asImmutableArray) + { + // T[] array = [elems]; + // ImmutableArray value = ImmutableCollectionsMarshal.AsImmutableArray(array); + var arrayType = getBackingArrayType(immutableArrayType); + var arrayValue = createArray(node, arrayType, targetsReadOnlyCollection: true); + return _factory.StaticCall(asImmutableArray.Construct([arrayType.ElementTypeWithAnnotations]), [arrayValue]); + } - var arrayType = collectionType as ArrayTypeSymbol; - if (arrayType is null) + // ReadOnlySpan span = [elems]; + // ImmutableArray array = ImmutableArray.Create(span) + return VisitCollectionBuilderCollectionExpression(node); + } + + BoundExpression? tryCreateNonArrayBackedSpan(BoundCollectionExpression node, NamedTypeSymbol spanType, bool isReadOnlySpan) { - // We're constructing a Span or ReadOnlySpan rather than T[]. - var spanType = (NamedTypeSymbol)collectionType; + Debug.Assert(isReadOnlySpan + ? spanType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T) + : spanType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_Span_T)); - Debug.Assert(collectionTypeKind is CollectionExpressionTypeKind.Span or CollectionExpressionTypeKind.ReadOnlySpan); - Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType( - collectionTypeKind == CollectionExpressionTypeKind.Span ? WellKnownType.System_Span_T : WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)); - Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0], TypeCompareKind.AllIgnoreOptions)); + var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; + var elements = node.Elements; if (elements.Length == 0) { // `default(Span)` is the best way to make empty Spans - return _factory.Default(collectionType); + return _factory.Default(spanType); } - if (collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan && + if (isReadOnlySpan && ShouldUseRuntimeHelpersCreateSpan(node, elementType.Type)) { // Assert that binding layer agrees with lowering layer about whether this collection-expr will allocate. - Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation)); + Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, elementType.Type, _compilation)); var constructor = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Array)).AsMember(spanType); var rewrittenElements = elements.SelectAsArray(static (element, rewriter) => rewriter.VisitExpression((BoundExpression)element), this); + // Use codegen which downstream layer will emit as a "readonly span into assembly data segment" instead of "readonly span into array". return _factory.New(constructor, _factory.Array(elementType.Type, rewrittenElements)); } if (ShouldUseInlineArray(node, _compilation) && _additionalLocals is { }) { - Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation)); + Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, isReadOnlySpan ? CollectionExpressionTypeKind.ReadOnlySpan : CollectionExpressionTypeKind.Span, elementType.Type, _compilation)); return CreateAndPopulateSpanFromInlineArray( - syntax, + node.Syntax, elementType, elements, - asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan); + asReadOnlySpan: isReadOnlySpan); } - Debug.Assert(IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation)); - arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType); - spanConstructor = ((MethodSymbol)_factory.WellKnownMember( - collectionTypeKind == CollectionExpressionTypeKind.Span ? WellKnownMember.System_Span_T__ctor_Array : WellKnownMember.System_ReadOnlySpan_T__ctor_Array)!).AsMember(spanType); + return null; } - BoundExpression array; - if (TryOptimizeSingleSpreadToArray(node, arrayType) is { } optimizedArray) + BoundExpression createSpan(BoundCollectionExpression node, NamedTypeSymbol spanType, bool isReadOnlySpan) { - array = optimizedArray; - } - else if (ShouldUseKnownLength(node, out _)) - { - array = CreateAndPopulateArray(node, arrayType); - } - else - { - // The array initializer has an unknown length, so we'll create an intermediate List instance. - // https://github.com/dotnet/roslyn/issues/68785: Emit Enumerable.TryGetNonEnumeratedCount() and avoid intermediate List at runtime. - var list = CreateAndPopulateList(node, elementType, elements); + Debug.Assert(isReadOnlySpan + ? spanType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T) + : spanType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_Span_T)); + + if (tryCreateNonArrayBackedSpan(node, spanType, isReadOnlySpan) is { } spanValue) + return spanValue; + + var arrayType = getBackingArrayType(spanType); + var arrayValue = createArray(node, arrayType, targetsReadOnlyCollection: isReadOnlySpan); + + var wellKnownMember = isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__ctor_Array : WellKnownMember.System_Span_T__ctor_Array; + var spanConstructor = _factory.WellKnownMethod(wellKnownMember).AsMember(spanType); - Debug.Assert(list.Type is { }); - Debug.Assert(list.Type.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T), TypeCompareKind.AllIgnoreOptions)); + // We can either get the same array type as the target span type or an array of more derived type. + // In the second case reference conversion would happen automatically since we still construct the span + // of the base type, while usually such conversion requires stloc+ldloc with the local of the base type + assertTypesAreCompatible(_compilation, arrayType, spanConstructor.Parameters[0].Type, isReadOnlySpan); + return _factory.New(spanConstructor, arrayValue); - var listToArray = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__ToArray)).AsMember((NamedTypeSymbol)list.Type); - array = _factory.Call(list, listToArray); + [Conditional("DEBUG")] + static void assertTypesAreCompatible(CSharpCompilation compilation, TypeSymbol arrayType, TypeSymbol constructorParameterType, bool isReadOnlySpan) + { + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + var conversionKind = compilation.Conversions.ClassifyConversionFromType(arrayType, constructorParameterType, isChecked: false, ref discardedUseSiteInfo).Kind; + Debug.Assert(conversionKind == ConversionKind.Identity || (isReadOnlySpan && conversionKind == ConversionKind.ImplicitReference)); + } } - if (spanConstructor is null) + BoundExpression createArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType, bool targetsReadOnlyCollection) { + BoundExpression array; + if (TryOptimizeSingleSpreadToArray_NoConversionApplied(node, targetsReadOnlyCollection, arrayType) is { } optimizedArray) + { + array = optimizedArray; + } + else if (ShouldUseKnownLength(node, out _)) + { + array = CreateAndPopulateArray(node, arrayType); + } + else + { + // The array initializer has an unknown length, so we'll create an intermediate List instance. + // https://github.com/dotnet/roslyn/issues/68785: Emit Enumerable.TryGetNonEnumeratedCount() and avoid intermediate List at runtime. + var list = CreateAndPopulateList(node, arrayType.ElementTypeWithAnnotations, node.Elements); + + Debug.Assert(list.Type is { }); + Debug.Assert(list.Type.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T), TypeCompareKind.AllIgnoreOptions)); + + var listToArray = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__ToArray)).AsMember((NamedTypeSymbol)list.Type); + array = _factory.Call(list, listToArray); + } + return array; } - - Debug.Assert(TypeSymbol.Equals(array.Type, spanConstructor.Parameters[0].Type, TypeCompareKind.AllIgnoreOptions)); - return new BoundObjectCreationExpression(syntax, spanConstructor, array); } private BoundExpression VisitCollectionInitializerCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType) @@ -457,7 +514,12 @@ SpecialType.System_Collections_Generic_IReadOnlyCollection_T or BoundExpression createArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType) { - if (TryOptimizeSingleSpreadToArray(node, arrayType) is { } optimizedArray) + Debug.Assert(node.Type.OriginalDefinition.SpecialType is + SpecialType.System_Collections_Generic_IEnumerable_T or + SpecialType.System_Collections_Generic_IReadOnlyCollection_T or + SpecialType.System_Collections_Generic_IReadOnlyList_T); + + if (TryOptimizeSingleSpreadToArray_NoConversionApplied(node, targetsReadOnlyCollection: true, arrayType) is { } optimizedArray) return optimizedArray; return CreateAndPopulateArray(node, arrayType); @@ -479,15 +541,13 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti var spanType = (NamedTypeSymbol)constructMethod.Parameters[0].Type; Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)); - var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; - // If collection expression is of form `[.. anotherReadOnlySpan]` // with `anotherReadOnlySpan` being a ReadOnlySpan of the same type as target collection type // and that span cannot be captured in a returned ref struct // we can directly use `anotherReadOnlySpan` as collection builder argument and skip the copying assignment. BoundExpression span = CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out var spreadExpression) ? VisitExpression(spreadExpression) - : VisitArrayOrSpanCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, spanType, elementType); + : VisitArrayOrSpanCollectionExpression(node, spanType); var invocation = new BoundCall( node.Syntax, @@ -649,21 +709,31 @@ private static bool ShouldUseKnownLength(BoundCollectionExpression node, out int /// Attempt to optimize conversion of a single-spread collection expr to array, even if the spread length is not known. /// /// The following optimizations are tried, in order: - /// 1. 'List.ToArray' if the spread value is a list - /// 2. 'Enumerable.ToArray' if we can convert the spread value to IEnumerable and additional conditions are met - /// 3. 'Span/ReadOnlySpan.ToArray' if we can convert the spread value to Span or ReadOnlySpan + /// + /// List.ToArray if the spread value is a list + /// Enumerable.ToArray if we can convert the spread value to IEnumerable and additional conditions are met + /// Span/ReadOnlySpan.ToArray if we can convert the spread value to Span or ReadOnlySpan + /// + /// Applying conversion to the resulting array is up to the caller /// - private BoundExpression? TryOptimizeSingleSpreadToArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType) + private BoundExpression? TryOptimizeSingleSpreadToArray_NoConversionApplied(BoundCollectionExpression node, bool targetsReadOnlyCollection, ArrayTypeSymbol arrayType) { // Collection-expr is of the form `[..spreadExpression]`. // Optimize to `spreadExpression.ToArray()` if possible. if (node is { Elements: [BoundCollectionExpressionSpreadElement { Expression: { } spreadExpression } spreadElement] } && spreadElement.IteratorBody is BoundExpressionStatement expressionStatement) { - var spreadElementHasIdentityConversion = expressionStatement.Expression is not BoundConversion; + var spreadElementConversion = expressionStatement.Expression is BoundConversion { Conversion: var actualConversion } ? actualConversion : Conversion.Identity; + // Allow implicit reference conversion only if we target readonly collection types like + // ReadOnlySpan, IEnumerable, IReadOnlyList etc. Cause otherwise user may get an array with different + // actual underlying array type which may lead to unexpected behavior, e.g. an exception + // when trying to insert an element of the base type + var spreadElementHasCompatibleConversion = targetsReadOnlyCollection + ? spreadElementConversion.Kind is ConversionKind.Identity or ConversionKind.ImplicitReference + : spreadElementConversion.Kind is ConversionKind.Identity; var spreadTypeOriginalDefinition = spreadExpression.Type!.OriginalDefinition; - if (spreadElementHasIdentityConversion + if (spreadElementHasCompatibleConversion && tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray, out MethodSymbol? listToArrayMethod)) { var rewrittenSpreadExpression = VisitExpression(spreadExpression); @@ -683,7 +753,7 @@ private static bool ShouldUseKnownLength(BoundCollectionExpression node, out int } } - if (spreadElementHasIdentityConversion + if (spreadElementHasCompatibleConversion && TryGetSpanConversion(spreadExpression.Type, writableOnly: false, out var asSpanMethod)) { var spanType = CallAsSpanMethod(spreadExpression, asSpanMethod).Type!.OriginalDefinition; @@ -728,7 +798,8 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A } // Shouldn't call this method if the single spread optimization would work. - Debug.Assert(TryOptimizeSingleSpreadToArray(node, arrayType) is null); + // Passing `targetsReadOnlyCollection` as false since it is more restrictive case. + Debug.Assert(TryOptimizeSingleSpreadToArray_NoConversionApplied(node, targetsReadOnlyCollection: false, arrayType) is null); if (numberIncludingLastSpread == 0) { @@ -942,7 +1013,7 @@ private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, Metho { // Cannot use CopyTo when spread element has non-identity conversion to target element type. // Could do a covariant conversion of ReadOnlySpan in future: https://github.com/dotnet/roslyn/issues/71106 - if (spreadElement.IteratorBody is not BoundExpressionStatement expressionStatement || expressionStatement.Expression is BoundConversion) + if (spreadElement.IteratorBody is not BoundExpressionStatement expressionStatement || expressionStatement.Expression is BoundConversion { ConversionKind: not ConversionKind.Identity }) return null; if (_factory.WellKnownMethod(WellKnownMember.System_Span_T__Slice_Int_Int, isOptional: true) is not { } spanSliceMethod) diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs index 8d4508fffce40..005055b65d1aa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs @@ -43,9 +43,13 @@ internal WellKnownMembersSignatureComparer WellKnownMemberSignatureComparer /// The value is set during binding the symbols that need those attributes, and is frozen on first trial to get it. /// Freezing is needed to make sure that nothing tries to modify the value after the value is read. /// - internal EmbeddableAttributes GetNeedsGeneratedAttributes() + internal EmbeddableAttributes GetNeedsGeneratedAttributes(bool freezeState = true) { - _needsGeneratedAttributes_IsFrozen = true; + if (freezeState) + { + _needsGeneratedAttributes_IsFrozen = true; + } + return (EmbeddableAttributes)_needsGeneratedAttributes; } diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 36afe4b30c710..71e5bb9d8f23a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -69,7 +69,7 @@ public abstract MethodKind MethodKind } /// - /// Returns the arity of this method, or the number of type parameters it takes. + /// Returns the arity of this method. Arity is the number of type parameters a method declares. /// A non-generic method has zero arity. /// public abstract int Arity { get; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ExplicitInterfaceHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ExplicitInterfaceHelpers.cs index cd719570a53b8..f918df07f4dc2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ExplicitInterfaceHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ExplicitInterfaceHelpers.cs @@ -21,18 +21,20 @@ internal static class ExplicitInterfaceHelpers { public static string GetMemberName( Binder binder, + SyntaxTokenList modifiers, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifierOpt, string name) { TypeSymbol discardedExplicitInterfaceType; string discardedAliasOpt; - string methodName = GetMemberNameAndInterfaceSymbol(binder, explicitInterfaceSpecifierOpt, name, BindingDiagnosticBag.Discarded, out discardedExplicitInterfaceType, out discardedAliasOpt); + string methodName = GetMemberNameAndInterfaceSymbol(binder, modifiers, explicitInterfaceSpecifierOpt, name, BindingDiagnosticBag.Discarded, out discardedExplicitInterfaceType, out discardedAliasOpt); return methodName; } public static string GetMemberNameAndInterfaceSymbol( Binder binder, + SyntaxTokenList modifiers, ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifierOpt, string name, BindingDiagnosticBag diagnostics, @@ -50,6 +52,8 @@ public static string GetMemberNameAndInterfaceSymbol( // that might result in a recursive attempt to bind the containing class. binder = binder.WithAdditionalFlags(BinderFlags.SuppressConstraintChecks | BinderFlags.SuppressObsoleteChecks); + binder = binder.SetOrClearUnsafeRegionIfNecessary(modifiers); + NameSyntax explicitInterfaceName = explicitInterfaceSpecifierOpt.Name; explicitInterfaceTypeOpt = binder.BindType(explicitInterfaceName, diagnostics).Type; aliasQualifierOpt = explicitInterfaceName.GetAliasQualifierOpt(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index f5d244f233d06..ad28778f821d1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -846,7 +846,7 @@ public static void ReportParameterErrors( int parameterIndex = ordinal; bool isDefault = syntax is ParameterSyntax { Default: { } }; - if (thisKeyword.Kind() == SyntaxKind.ThisKeyword && parameterIndex != 0) + if (thisKeyword.Kind() == SyntaxKind.ThisKeyword && parameterIndex != 0 && owner?.GetIsNewExtensionMember() != true) { // Report CS1100 on "this". Note that is a change from Dev10 // which reports the error on the type following "this". @@ -962,7 +962,7 @@ internal static bool ReportDefaultParameterErrors( { // Only need to report CS1743 for the first parameter. The caller will // have reported CS1100 if 'this' appeared on another parameter. - if (parameter.Ordinal == 0) + if (parameter.Ordinal == 0 && !parameter.ContainingSymbol.GetIsNewExtensionMember()) { // error CS1743: Cannot specify a default value for the 'this' parameter diagnostics.Add(ErrorCode.ERR_DefaultValueForExtensionParameter, thisKeyword.GetLocation()); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventSymbol.cs index b5faa9b49e15d..6bc34aa9eec87 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceCustomEventSymbol.cs @@ -33,7 +33,7 @@ internal SourceCustomEventSymbol(SourceMemberContainerTypeSymbol containingType, bool isExplicitInterfaceImplementation = interfaceSpecifier != null; string? aliasQualifierOpt; - _name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(binder, interfaceSpecifier, nameToken.ValueText, diagnostics, out _explicitInterfaceType, out aliasQualifierOpt); + _name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(binder, syntax.Modifiers, interfaceSpecifier, nameToken.ValueText, diagnostics, out _explicitInterfaceType, out aliasQualifierOpt); _type = BindEventType(binder, syntax.Type, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs index c162b2e0b9415..a6b232d26e754 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs @@ -386,10 +386,19 @@ static bool containsOnlyOblivious(TypeSymbol type) diagnostics.Add(ErrorCode.ERR_StaticBaseClass, baseTypeLocation, baseType, this); } - if (!this.IsNoMoreVisibleThan(baseType, ref useSiteInfo)) + if (baseType.FindTypeLessVisibleThan(this, ref useSiteInfo) is { } lessVisibleType) { - // Inconsistent accessibility: base class '{1}' is less accessible than class '{0}' - diagnostics.Add(ErrorCode.ERR_BadVisBaseClass, baseTypeLocation, this, baseType); + if (ReferenceEquals(baseType, lessVisibleType)) + { + // Inconsistent accessibility: base class '{1}' is less accessible than class '{0}' + diagnostics.Add(ErrorCode.ERR_BadVisBaseClass, baseTypeLocation, this, lessVisibleType); + } + else + { + // Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + var lessVisibleTypeLocation = lessVisibleType.GetFirstLocation(); + diagnostics.Add(ErrorCode.ERR_BadVisBaseType, baseTypeLocation, this, lessVisibleType); + } } if (baseType.HasFileLocalTypes() && !this.HasFileLocalTypes()) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs index 20595933253b8..fd7811cafc454 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs @@ -31,7 +31,7 @@ public static SourceOrdinaryMethodSymbol CreateMethodSymbol( var nameToken = syntax.Identifier; TypeSymbol explicitInterfaceType; - var name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, interfaceSpecifier, nameToken.ValueText, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); + var name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, syntax.Modifiers, interfaceSpecifier, nameToken.ValueText, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); var location = new SourceLocation(nameToken); var methodKind = interfaceSpecifier == null @@ -99,7 +99,8 @@ private static (DeclarationModifiers, Flags) MakeModifiersAndFlags( hasAnyBody: syntax.HasAnyBody(), isExpressionBodied: syntax.IsExpressionBodied(), isExtensionMethod: syntax.ParameterList.Parameters.FirstOrDefault() is ParameterSyntax firstParam && !firstParam.IsArgList && - firstParam.Modifiers.Any(SyntaxKind.ThisKeyword), + firstParam.Modifiers.Any(SyntaxKind.ThisKeyword) && + !containingType.IsExtension, isNullableAnalysisEnabled: isNullableAnalysisEnabled, isVararg: syntax.IsVarArg(), isExplicitInterfaceImplementation: methodKind == MethodKind.ExplicitInterfaceImplementation, @@ -126,7 +127,7 @@ private static (DeclarationModifiers, Flags) MakeModifiersAndFlags( ImmutableArray parameters = ParameterHelpers.MakeParameters( signatureBinder, this, syntax.ParameterList, out _, allowRefOrOut: true, - allowThis: true, + allowThis: !this.GetIsNewExtensionMember(), addRefReadOnlyModifier: IsVirtual || IsAbstract, diagnostics: diagnostics).Cast(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 9ece43e96e082..07ac7d1a0f7e9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -73,10 +73,9 @@ private static SourcePropertySymbol Create( bool hasAutoPropertyGet = allowAutoPropertyAccessors && getSyntax != null && !hasGetAccessorImplementation; bool hasAutoPropertySet = allowAutoPropertyAccessors && setSyntax != null && !hasSetAccessorImplementation; - binder = binder.SetOrClearUnsafeRegionIfNecessary(modifiersTokenList); TypeSymbol? explicitInterfaceType; string? aliasQualifierOpt; - string memberName = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(binder, explicitInterfaceSpecifier, name, diagnostics, out explicitInterfaceType, out aliasQualifierOpt); + string memberName = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(binder, modifiersTokenList, explicitInterfaceSpecifier, name, diagnostics, out explicitInterfaceType, out aliasQualifierOpt); return new SourcePropertySymbol( containingType, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs index 2d8b6e67cd671..9e7e92e87f362 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs @@ -38,7 +38,7 @@ public static SourceUserDefinedConversionSymbol CreateUserDefinedConversionSymbo var interfaceSpecifier = syntax.ExplicitInterfaceSpecifier; TypeSymbol explicitInterfaceType; - name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, interfaceSpecifier, name, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); + name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, syntax.Modifiers, interfaceSpecifier, name, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); var methodKind = interfaceSpecifier == null ? MethodKind.Conversion diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs index ab820074dd94b..39002a2a9312c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs @@ -44,7 +44,7 @@ public static SourceUserDefinedOperatorSymbol CreateUserDefinedOperatorSymbol( var interfaceSpecifier = syntax.ExplicitInterfaceSpecifier; TypeSymbol explicitInterfaceType; - name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, interfaceSpecifier, name, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); + name = ExplicitInterfaceHelpers.GetMemberNameAndInterfaceSymbol(bodyBinder, syntax.Modifiers, interfaceSpecifier, name, diagnostics, out explicitInterfaceType, aliasQualifierOpt: out _); var methodKind = interfaceSpecifier == null ? MethodKind.UserDefinedOperator diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index d0735e63504f7..ea2a93abf3f50 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -672,6 +672,11 @@ public static SpecialType GetSpecialTypeSafe(this TypeSymbol? type) } public static bool IsAtLeastAsVisibleAs(this TypeSymbol type, Symbol sym, ref CompoundUseSiteInfo useSiteInfo) + { + return type.FindTypeLessVisibleThan(sym, ref useSiteInfo) is null; + } + + public static TypeSymbol? FindTypeLessVisibleThan(this TypeSymbol type, Symbol sym, ref CompoundUseSiteInfo useSiteInfo) { var visitTypeData = s_visitTypeDataPool.Allocate(); @@ -685,7 +690,7 @@ public static bool IsAtLeastAsVisibleAs(this TypeSymbol type, Symbol sym, ref Co canDigThroughNullable: true); // System.Nullable is public useSiteInfo = visitTypeData.UseSiteInfo; - return result is null; + return result; } finally { diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index f65ce130d484d..30b917acea612 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -357,6 +357,11 @@ Typ {0} není platný pro using static. Lze použít pouze třídu, strukturu, rozhraní, výčet, delegáta nebo obor názvů. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block V bloku unsafe nejde použít příkaz yield return. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 95b1c9e02d3a9..18411c2abd606 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -357,6 +357,11 @@ Der Typ "{0}" ist für "using static" ungültig. Nur eine Klasse, Struktur, Schnittstelle, Enumeration, ein Delegat oder ein Namespace kann verwendet werden. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block „yield return“ kann nicht in einem „unsicheren“ Block verwendet werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 4c056d39dc02c..969dcc0de6bc6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -357,6 +357,11 @@ El tipo '{0}' no es válido para 'using static'. Solo se puede usar una clase, estructura, interfaz, enumeración, delegado o espacio de nombres. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block No se puede usar "yield return" en un bloque "unsafe" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 25fd4ece57692..0d9907444e880 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -357,6 +357,11 @@ Le type '{0}' n'est pas valide pour 'en utilisant statique'. Seuls une classe, une structure, une interface, une énumération, un délégué ou un espace de noms peuvent être utilisés. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block Désolé... Nous ne pouvons pas utiliser « yield return » dans un bloc « unsafe » diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 6c72394ce1fef..5d757c9297213 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -357,6 +357,11 @@ '{0}' tipo non valido per 'using static'. È possibile usare solo una classe, una struttura, un'interfaccia, un'enumerazione, un delegato o uno spazio dei nomi. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block Non è possibile usare 'yield return' in un blocco 'unsafe' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index d5b232e38e171..ebcbc0effa379 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -357,6 +357,11 @@ '{0}' 型は 'using static' では無効です。使用できるのは、クラス、構造体、インターフェイス、列挙型、デリゲート、名前空間のみです。 + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block 'unsafe' ブロックでは 'yield return' を使用できません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index e77a168ebe867..5d6e6a0aa679b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -357,6 +357,11 @@ '{0}' 유형은 '정적 사용'에 유효하지 않습니다. 클래스, 구조체, 인터페이스, 열거형, 대리자 또는 네임스페이스만 사용할 수 있습니다. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block 'unsafe' 블록에서 'yield return'을 사용할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 1c305f6830347..3160885d4a70b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -357,6 +357,11 @@ Typ „{0}” jest nieprawidłowy dla „using static”. Można używać tylko klasy, struktury, interfejsu, wyliczenia, delegata lub przestrzeni nazw. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block Nie można użyć instrukcji „yield return” w bloku „unsafe” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 3bb924654bc71..5cac5a6889b27 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -357,6 +357,11 @@ '{0}' tipo não é válido para 'using static'. Somente uma classe, struct, interface, enumeração, delegado ou namespace podem ser usados. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block Não é possível usar "yield return" em um bloco "unsafe" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index c5b8d18fe64ec..cb95f74721abc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -357,6 +357,11 @@ ' {0} ' недопустим для 'использования статики'. Можно использовать только класс, структуру, интерфейс, перечисление, делегат или пространство имен. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block Невозможно использовать "yield return" в блоке "unsafe". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index bd18e7596c70b..67495122974e0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -357,6 +357,11 @@ '{0}' türü 'using static' için geçerli değil. Yalnızca bir sınıf, yapı, arabirim, sabit liste, temsilci veya ad alanı kullanılabilir. + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block 'güvenli yield return' bloğunda 'yield return' kullanılamadı diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 90b2836f06871..3ad4ad5d0618f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -357,6 +357,11 @@ “{0}”类型对于 "using static" 无效。只能使用类、结构、接口、枚举、委托或命名空间。 + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block 不能在 "unsafe" 块中使用 "yield return" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 600bdbf066150..fc33d4373c713 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -357,6 +357,11 @@ '{0}' 類型對 'using static' 無效。只能使用類別、結構、介面、列舉、委派或命名空間。 + + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + Inconsistent accessibility: type '{1}' is less accessible than class '{0}' + + Cannot use 'yield return' in an 'unsafe' block 無法在 'unsafe' 區塊中使用 'yield return' diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 41b3ab5677a5b..0a1989896e29c 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; -using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests @@ -5046,6 +5045,1016 @@ static void Main() Assert.Equal(CollectionExpressionTypeKind.ImplementsIEnumerable, ConversionsBase.GetCollectionExpressionTypeKind(comp, collectionType, out _)); } + [Theory, CombinatorialData] + public void ListInterface_ReadOnly_FromSingleSpread_Covariant_Spans( + [CombinatorialValues("IEnumerable", "IReadOnlyCollection", "IReadOnlyList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [new(), new(), new()]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 13 (0xd) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: call "Derived[] System.{{spanType}}.ToArray()" + IL_0007: newobj "<>z__ReadOnlyArray..ctor(Base[])" + IL_000c: ret + } + """); + } + + [Theory, CombinatorialData] + public void ListInterface_Mutable_FromSingleSpread_Covariant_Spans( + [CombinatorialValues("ICollection", "IList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [new(), new(), new()]; + var b = M(d); + b.Report(); + + b.Add(new Base()); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Derived 0, Derived 1, Derived 2, Base 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 84 (0x54) + .maxstack 2 + .locals init (System.{{spanType}} V_0, + int V_1, + System.Collections.Generic.List V_2, + System.Span V_3, + int V_4, + System.{{spanType}}.Enumerator V_5, + Derived V_6) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call "int System.{{spanType}}.Length.get" + IL_0009: stloc.1 + IL_000a: ldloc.1 + IL_000b: newobj "System.Collections.Generic.List..ctor(int)" + IL_0010: stloc.2 + IL_0011: ldloc.2 + IL_0012: ldloc.1 + IL_0013: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0018: ldloc.2 + IL_0019: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001e: stloc.3 + IL_001f: ldc.i4.0 + IL_0020: stloc.s V_4 + IL_0022: ldloca.s V_0 + IL_0024: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0029: stloc.s V_5 + IL_002b: br.s IL_0049 + IL_002d: ldloca.s V_5 + IL_002f: call "ref {{(readOnlySpan ? "readonly " : "")}}Derived System.{{spanType}}.Enumerator.Current.get" + IL_0034: ldind.ref + IL_0035: stloc.s V_6 + IL_0037: ldloca.s V_3 + IL_0039: ldloc.s V_4 + IL_003b: call "ref Base System.Span.this[int].get" + IL_0040: ldloc.s V_6 + IL_0042: stind.ref + IL_0043: ldloc.s V_4 + IL_0045: ldc.i4.1 + IL_0046: add + IL_0047: stloc.s V_4 + IL_0049: ldloca.s V_5 + IL_004b: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_0050: brtrue.s IL_002d + IL_0052: ldloc.2 + IL_0053: ret + } + """); + } + + [Theory] + [InlineData("IEnumerable")] + [InlineData("IReadOnlyCollection")] + [InlineData("IReadOnlyList")] + public void ListInterface_ReadOnly_FromSingleSpread_Covariant_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [new(), new(), new()]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "Derived[] System.Collections.Generic.List.ToArray()" + IL_0006: newobj "<>z__ReadOnlyArray..ctor(Base[])" + IL_000b: ret + } + """); + } + + [Theory] + [InlineData("ICollection")] + [InlineData("IList")] + public void ListInterface_Mutable_FromSingleSpread_Covariant_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [new(), new(), new()]; + var b = M(d); + b.Report(); + + b.Add(new Base()); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Derived 0, Derived 1, Derived 2, Base 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 91 (0x5b) + .maxstack 3 + .locals init (int V_0, + System.Collections.Generic.List V_1, + System.Span V_2, + int V_3, + System.Collections.Generic.List.Enumerator V_4, + Derived V_5) + IL_0000: ldarg.0 + IL_0001: dup + IL_0002: callvirt "int System.Collections.Generic.List.Count.get" + IL_0007: stloc.0 + IL_0008: ldloc.0 + IL_0009: newobj "System.Collections.Generic.List..ctor(int)" + IL_000e: stloc.1 + IL_000f: ldloc.1 + IL_0010: ldloc.0 + IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0016: ldloc.1 + IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001c: stloc.2 + IL_001d: ldc.i4.0 + IL_001e: stloc.3 + IL_001f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0024: stloc.s V_4 + .try + { + IL_0026: br.s IL_0040 + IL_0028: ldloca.s V_4 + IL_002a: call "Derived System.Collections.Generic.List.Enumerator.Current.get" + IL_002f: stloc.s V_5 + IL_0031: ldloca.s V_2 + IL_0033: ldloc.3 + IL_0034: call "ref Base System.Span.this[int].get" + IL_0039: ldloc.s V_5 + IL_003b: stind.ref + IL_003c: ldloc.3 + IL_003d: ldc.i4.1 + IL_003e: add + IL_003f: stloc.3 + IL_0040: ldloca.s V_4 + IL_0042: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0047: brtrue.s IL_0028 + IL_0049: leave.s IL_0059 + } + finally + { + IL_004b: ldloca.s V_4 + IL_004d: constrained. "System.Collections.Generic.List.Enumerator" + IL_0053: callvirt "void System.IDisposable.Dispose()" + IL_0058: endfinally + } + IL_0059: ldloc.1 + IL_005a: ret + } + """); + } + + [Theory, CombinatorialData] + public void ListInterface_ReadOnly_FromSingleSpread_SameRuntimeType_Spans( + [CombinatorialValues("IEnumerable", "IReadOnlyCollection", "IReadOnlyList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [1, 2, 3]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 13 (0xd) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: call "object[] System.{{spanType}}.ToArray()" + IL_0007: newobj "<>z__ReadOnlyArray..ctor(dynamic[])" + IL_000c: ret + } + """); + } + + [Theory, CombinatorialData] + public void ListInterface_Mutable_FromSingleSpread_SameRuntimeType_Spans( + [CombinatorialValues("ICollection", "IList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [1, 2, 3]; + var b = M(d); + b.Report(); + + b.Add("a"); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3, a], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 65 (0x41) + .maxstack 5 + .locals init (System.{{spanType}} V_0, + int V_1, + System.Span V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call "int System.{{spanType}}.Length.get" + IL_0009: stloc.1 + IL_000a: ldloc.1 + IL_000b: newobj "System.Collections.Generic.List..ctor(int)" + IL_0010: dup + IL_0011: ldloc.1 + IL_0012: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0017: dup + IL_0018: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001d: stloc.2 + IL_001e: ldc.i4.0 + IL_001f: stloc.3 + IL_0020: ldloca.s V_0 + IL_0022: ldloca.s V_2 + IL_0024: ldloc.3 + IL_0025: ldloca.s V_0 + IL_0027: call "int System.{{spanType}}.Length.get" + IL_002c: call "System.Span System.Span.Slice(int, int)" + IL_0031: call "void System.{{spanType}}.CopyTo(System.Span)" + IL_0036: ldloc.3 + IL_0037: ldloca.s V_0 + IL_0039: call "int System.{{spanType}}.Length.get" + IL_003e: add + IL_003f: stloc.3 + IL_0040: ret + } + """); + } + + [Theory] + [InlineData("IEnumerable")] + [InlineData("IReadOnlyCollection")] + [InlineData("IReadOnlyList")] + public void ListInterface_ReadOnly_FromSingleSpread_SameRuntimeType_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [1, 2, 3]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_0006: newobj "<>z__ReadOnlyArray..ctor(dynamic[])" + IL_000b: ret + } + """); + } + + [Theory] + [InlineData("ICollection")] + [InlineData("IList")] + public void ListInterface_Mutable_FromSingleSpread_SameRuntimeType_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [1, 2, 3]; + var b = M(d); + b.Report(); + + b.Add("a"); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3, a], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 72 (0x48) + .maxstack 4 + .locals init (int V_0, + System.Collections.Generic.List V_1, + System.Span V_2, + int V_3, + System.Span V_4) + IL_0000: ldarg.0 + IL_0001: dup + IL_0002: callvirt "int System.Collections.Generic.List.Count.get" + IL_0007: stloc.0 + IL_0008: ldloc.0 + IL_0009: newobj "System.Collections.Generic.List..ctor(int)" + IL_000e: stloc.1 + IL_000f: ldloc.1 + IL_0010: ldloc.0 + IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0016: ldloc.1 + IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001c: stloc.2 + IL_001d: ldc.i4.0 + IL_001e: stloc.3 + IL_001f: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0024: stloc.s V_4 + IL_0026: ldloca.s V_4 + IL_0028: ldloca.s V_2 + IL_002a: ldloc.3 + IL_002b: ldloca.s V_4 + IL_002d: call "int System.Span.Length.get" + IL_0032: call "System.Span System.Span.Slice(int, int)" + IL_0037: call "void System.Span.CopyTo(System.Span)" + IL_003c: ldloc.3 + IL_003d: ldloca.s V_4 + IL_003f: call "int System.Span.Length.get" + IL_0044: add + IL_0045: stloc.3 + IL_0046: ldloc.1 + IL_0047: ret + } + """); + } + + [Theory, CombinatorialData] + public void ListInterface_ReadOnly_FromSingleSpread_Boxing_Spans( + [CombinatorialValues("IEnumerable", "IReadOnlyCollection", "IReadOnlyList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [1, 2, 3]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 67 (0x43) + .maxstack 3 + .locals init (System.{{spanType}} V_0, + int V_1, + object[] V_2, + System.{{spanType}}.Enumerator V_3, + int V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.{{spanType}}.Length.get" + IL_000b: newarr "object" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0033 + IL_001b: ldloca.s V_3 + IL_001d: call "ref {{(readOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_0022: ldind.i4 + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: box "int" + IL_002e: stelem.ref + IL_002f: ldloc.1 + IL_0030: ldc.i4.1 + IL_0031: add + IL_0032: stloc.1 + IL_0033: ldloca.s V_3 + IL_0035: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_003a: brtrue.s IL_001b + IL_003c: ldloc.2 + IL_003d: newobj "<>z__ReadOnlyArray..ctor(object[])" + IL_0042: ret + } + """); + } + + [Theory, CombinatorialData] + public void ListInterface_Mutable_FromSingleSpread_Boxing_Spans( + [CombinatorialValues("ICollection", "IList")] string listInterface, + bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + {{spanType}} d = [1, 2, 3]; + var b = M(d); + b.Report(); + + b.Add("a"); + b.Report(); + } + + static {{listInterface}} M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3, a], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 89 (0x59) + .maxstack 2 + .locals init (System.{{spanType}} V_0, + int V_1, + System.Collections.Generic.List V_2, + System.Span V_3, + int V_4, + System.{{spanType}}.Enumerator V_5, + int V_6) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call "int System.{{spanType}}.Length.get" + IL_0009: stloc.1 + IL_000a: ldloc.1 + IL_000b: newobj "System.Collections.Generic.List..ctor(int)" + IL_0010: stloc.2 + IL_0011: ldloc.2 + IL_0012: ldloc.1 + IL_0013: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0018: ldloc.2 + IL_0019: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001e: stloc.3 + IL_001f: ldc.i4.0 + IL_0020: stloc.s V_4 + IL_0022: ldloca.s V_0 + IL_0024: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0029: stloc.s V_5 + IL_002b: br.s IL_004e + IL_002d: ldloca.s V_5 + IL_002f: call "ref {{(readOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_0034: ldind.i4 + IL_0035: stloc.s V_6 + IL_0037: ldloca.s V_3 + IL_0039: ldloc.s V_4 + IL_003b: call "ref object System.Span.this[int].get" + IL_0040: ldloc.s V_6 + IL_0042: box "int" + IL_0047: stind.ref + IL_0048: ldloc.s V_4 + IL_004a: ldc.i4.1 + IL_004b: add + IL_004c: stloc.s V_4 + IL_004e: ldloca.s V_5 + IL_0050: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_0055: brtrue.s IL_002d + IL_0057: ldloc.2 + IL_0058: ret + } + """); + } + + [Theory] + [InlineData("IEnumerable")] + [InlineData("IReadOnlyCollection")] + [InlineData("IReadOnlyList")] + public void ListInterface_ReadOnly_FromSingleSpread_Boxing_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [1, 2, 3]; + var b = M(d); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 76 (0x4c) + .maxstack 3 + .locals init (int V_0, + object[] V_1, + System.Collections.Generic.List.Enumerator V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + IL_0003: dup + IL_0004: callvirt "int System.Collections.Generic.List.Count.get" + IL_0009: newarr "object" + IL_000e: stloc.1 + IL_000f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0014: stloc.2 + .try + { + IL_0015: br.s IL_002c + IL_0017: ldloca.s V_2 + IL_0019: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_001e: stloc.3 + IL_001f: ldloc.1 + IL_0020: ldloc.0 + IL_0021: ldloc.3 + IL_0022: box "int" + IL_0027: stelem.ref + IL_0028: ldloc.0 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stloc.0 + IL_002c: ldloca.s V_2 + IL_002e: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0033: brtrue.s IL_0017 + IL_0035: leave.s IL_0045 + } + finally + { + IL_0037: ldloca.s V_2 + IL_0039: constrained. "System.Collections.Generic.List.Enumerator" + IL_003f: callvirt "void System.IDisposable.Dispose()" + IL_0044: endfinally + } + IL_0045: ldloc.1 + IL_0046: newobj "<>z__ReadOnlyArray..ctor(object[])" + IL_004b: ret + } + """); + } + + [Theory] + [InlineData("ICollection")] + [InlineData("IList")] + public void ListInterface_Mutable_FromSingleSpread_Boxing_List(string listInterface) + { + var source = $$""" + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = [1, 2, 3]; + var b = M(d); + b.Report(); + + b.Add("a"); + b.Report(); + } + + static {{listInterface}} M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3, a], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 96 (0x60) + .maxstack 3 + .locals init (int V_0, + System.Collections.Generic.List V_1, + System.Span V_2, + int V_3, + System.Collections.Generic.List.Enumerator V_4, + int V_5) + IL_0000: ldarg.0 + IL_0001: dup + IL_0002: callvirt "int System.Collections.Generic.List.Count.get" + IL_0007: stloc.0 + IL_0008: ldloc.0 + IL_0009: newobj "System.Collections.Generic.List..ctor(int)" + IL_000e: stloc.1 + IL_000f: ldloc.1 + IL_0010: ldloc.0 + IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0016: ldloc.1 + IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001c: stloc.2 + IL_001d: ldc.i4.0 + IL_001e: stloc.3 + IL_001f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0024: stloc.s V_4 + .try + { + IL_0026: br.s IL_0045 + IL_0028: ldloca.s V_4 + IL_002a: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_002f: stloc.s V_5 + IL_0031: ldloca.s V_2 + IL_0033: ldloc.3 + IL_0034: call "ref object System.Span.this[int].get" + IL_0039: ldloc.s V_5 + IL_003b: box "int" + IL_0040: stind.ref + IL_0041: ldloc.3 + IL_0042: ldc.i4.1 + IL_0043: add + IL_0044: stloc.3 + IL_0045: ldloca.s V_4 + IL_0047: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_004c: brtrue.s IL_0028 + IL_004e: leave.s IL_005e + } + finally + { + IL_0050: ldloca.s V_4 + IL_0052: constrained. "System.Collections.Generic.List.Enumerator" + IL_0058: callvirt "void System.IDisposable.Dispose()" + IL_005d: endfinally + } + IL_005e: ldloc.1 + IL_005f: ret + } + """); + } + [Fact] public void ListInterfaces_NoInterfaces() { @@ -5378,6 +6387,294 @@ static void Main() Diagnostic(ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, "[[1, 2], [3, 4]]").WithArguments("int[*,*]").WithLocation(5, 20)); } + [Fact] + public void Array_06() + { + var source = """ + using System; + + class Program + { + static void Main() + { + Derived[] d = { new(), new(), new() }; + var bases = M(d); + bases.Report(); + + bases[0] = new Base(); + bases.Report(); + } + + static Base[] M(Span span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Base 3, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 57 (0x39) + .maxstack 3 + .locals init (System.Span V_0, + int V_1, + Base[] V_2, + System.Span.Enumerator V_3, + Derived V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.Span.Length.get" + IL_000b: newarr "Base" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.Span.Enumerator System.Span.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_002e + IL_001b: ldloca.s V_3 + IL_001d: call "ref Derived System.Span.Enumerator.Current.get" + IL_0022: ldind.ref + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: stelem.ref + IL_002a: ldloc.1 + IL_002b: ldc.i4.1 + IL_002c: add + IL_002d: stloc.1 + IL_002e: ldloca.s V_3 + IL_0030: call "bool System.Span.Enumerator.MoveNext()" + IL_0035: brtrue.s IL_001b + IL_0037: ldloc.2 + IL_0038: ret + } + """); + } + + [Fact] + public void Array_07() + { + var source = """ + using System; + + class Program + { + static void Main() + { + Derived[] d = { new(), new(), new() }; + var bases = M(d); + bases.Report(); + + bases[0] = new Base(); + bases.Report(); + } + + static Base[] M(ReadOnlySpan span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Base 3, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 57 (0x39) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, + int V_1, + Base[] V_2, + System.ReadOnlySpan.Enumerator V_3, + Derived V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.ReadOnlySpan.Length.get" + IL_000b: newarr "Base" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_002e + IL_001b: ldloca.s V_3 + IL_001d: call "ref readonly Derived System.ReadOnlySpan.Enumerator.Current.get" + IL_0022: ldind.ref + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: stelem.ref + IL_002a: ldloc.1 + IL_002b: ldc.i4.1 + IL_002c: add + IL_002d: stloc.1 + IL_002e: ldloca.s V_3 + IL_0030: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_0035: brtrue.s IL_001b + IL_0037: ldloc.2 + IL_0038: ret + } + """); + } + + [Theory, CombinatorialData] + public void Array_08(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + dynamic[] d = { 1, 2, 3 }; + var objs = M(d); + objs.Report(); + + objs[0] = "a"; + objs.Report(); + } + + static object[] M({{spanType}} span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: call "dynamic[] System.{{spanType}}.ToArray()" + IL_0007: ret + } + """); + } + + [Theory, CombinatorialData] + public void Array_09(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + int[] d = { 1, 2, 3 }; + var objs = M(d); + objs.Report(); + + objs[0] = "a"; + objs.Report(); + } + + static object[] M({{spanType}} span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 62 (0x3e) + .maxstack 3 + .locals init (System.{{spanType}} V_0, + int V_1, + object[] V_2, + System.{{spanType}}.Enumerator V_3, + int V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.{{spanType}}.Length.get" + IL_000b: newarr "object" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0033 + IL_001b: ldloca.s V_3 + IL_001d: call "ref {{(readOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_0022: ldind.i4 + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: box "int" + IL_002e: stelem.ref + IL_002f: ldloc.1 + IL_0030: ldc.i4.1 + IL_0031: add + IL_0032: stloc.1 + IL_0033: ldloca.s V_3 + IL_0035: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_003a: brtrue.s IL_001b + IL_003c: ldloc.2 + IL_003d: ret + } + """); + } + [Theory] [CombinatorialData] public void Span_01(bool useReadOnlySpan) @@ -5664,6 +6961,548 @@ class Program comp.VerifyEmitDiagnostics(); } + [Theory, CombinatorialData] + public void Span_06(bool useReadOnlySpan) + { + var spanType = useReadOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} d = CreateArray(); + Span b = [.. d]; + b.Report(); + + b[0] = new Base(); + b.Report(); + } + + static Derived[] CreateArray() => [new(), new(), new()]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Base 3, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 118 (0x76) + .maxstack 3 + .locals init (System.Span V_0, //b + System.{{spanType}} V_1, + int V_2, + Base[] V_3, + System.{{spanType}}.Enumerator V_4, + Derived V_5, + System.ReadOnlySpan V_6) + IL_0000: call "Derived[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(Derived[])" + IL_000a: stloc.1 + IL_000b: ldc.i4.0 + IL_000c: stloc.2 + IL_000d: ldloca.s V_1 + IL_000f: call "int System.{{spanType}}.Length.get" + IL_0014: newarr "Base" + IL_0019: stloc.3 + IL_001a: ldloca.s V_1 + IL_001c: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0021: stloc.s V_4 + IL_0023: br.s IL_0038 + IL_0025: ldloca.s V_4 + IL_0027: call "ref {{(useReadOnlySpan ? "readonly " : "")}}Derived System.{{spanType}}.Enumerator.Current.get" + IL_002c: ldind.ref + IL_002d: stloc.s V_5 + IL_002f: ldloc.3 + IL_0030: ldloc.2 + IL_0031: ldloc.s V_5 + IL_0033: stelem.ref + IL_0034: ldloc.2 + IL_0035: ldc.i4.1 + IL_0036: add + IL_0037: stloc.2 + IL_0038: ldloca.s V_4 + IL_003a: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_003f: brtrue.s IL_0025 + IL_0041: ldloca.s V_0 + IL_0043: ldloc.3 + IL_0044: call "System.Span..ctor(Base[])" + IL_0049: ldloc.0 + IL_004a: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_004f: stloc.s V_6 + IL_0051: ldloca.s V_6 + IL_0053: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0058: ldloca.s V_0 + IL_005a: ldc.i4.0 + IL_005b: call "ref Base System.Span.this[int].get" + IL_0060: newobj "Base..ctor()" + IL_0065: stind.ref + IL_0066: ldloc.0 + IL_0067: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_006c: stloc.s V_6 + IL_006e: ldloca.s V_6 + IL_0070: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0075: ret + } + """); + } + + [Fact] + public void Span_07() + { + var source = """ + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = CreateList(); + Span b = [.. d]; + b.Report(); + + b[0] = new Base(); + b.Report(); + } + + static List CreateList() => [new(), new(), new()]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], [Base 3, Derived 1, Derived 2], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 123 (0x7b) + .maxstack 3 + .locals init (System.Span V_0, //b + int V_1, + Base[] V_2, + System.Collections.Generic.List.Enumerator V_3, + Derived V_4, + System.ReadOnlySpan V_5) + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: ldc.i4.0 + IL_0006: stloc.1 + IL_0007: dup + IL_0008: callvirt "int System.Collections.Generic.List.Count.get" + IL_000d: newarr "Base" + IL_0012: stloc.2 + IL_0013: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0018: stloc.3 + .try + { + IL_0019: br.s IL_002d + IL_001b: ldloca.s V_3 + IL_001d: call "Derived System.Collections.Generic.List.Enumerator.Current.get" + IL_0022: stloc.s V_4 + IL_0024: ldloc.2 + IL_0025: ldloc.1 + IL_0026: ldloc.s V_4 + IL_0028: stelem.ref + IL_0029: ldloc.1 + IL_002a: ldc.i4.1 + IL_002b: add + IL_002c: stloc.1 + IL_002d: ldloca.s V_3 + IL_002f: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0034: brtrue.s IL_001b + IL_0036: leave.s IL_0046 + } + finally + { + IL_0038: ldloca.s V_3 + IL_003a: constrained. "System.Collections.Generic.List.Enumerator" + IL_0040: callvirt "void System.IDisposable.Dispose()" + IL_0045: endfinally + } + IL_0046: ldloca.s V_0 + IL_0048: ldloc.2 + IL_0049: call "System.Span..ctor(Base[])" + IL_004e: ldloc.0 + IL_004f: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0054: stloc.s V_5 + IL_0056: ldloca.s V_5 + IL_0058: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_005d: ldloca.s V_0 + IL_005f: ldc.i4.0 + IL_0060: call "ref Base System.Span.this[int].get" + IL_0065: newobj "Base..ctor()" + IL_006a: stind.ref + IL_006b: ldloc.0 + IL_006c: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0071: stloc.s V_5 + IL_0073: ldloca.s V_5 + IL_0075: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_007a: ret + } + """); + } + + [Theory, CombinatorialData] + public void Span_08(bool useReadOnlySpan) + { + var spanType = useReadOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} o = CreateArray(); + Span d = [.. o]; + d.Report(); + + d[0] = "a"; + d.Report(); + } + + static object[] CreateArray() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 68 (0x44) + .maxstack 2 + .locals init (System.{{spanType}} V_0, //o + System.Span V_1, //d + System.ReadOnlySpan V_2) + IL_0000: call "object[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(object[])" + IL_000a: stloc.0 + IL_000b: ldloca.s V_1 + IL_000d: ldloca.s V_0 + IL_000f: call "object[] System.{{spanType}}.ToArray()" + IL_0014: call "System.Span..ctor(dynamic[])" + IL_0019: ldloc.1 + IL_001a: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_001f: stloc.2 + IL_0020: ldloca.s V_2 + IL_0022: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0027: ldloca.s V_1 + IL_0029: ldc.i4.0 + IL_002a: call "ref dynamic System.Span.this[int].get" + IL_002f: ldstr "a" + IL_0034: stind.ref + IL_0035: ldloc.1 + IL_0036: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_003b: stloc.2 + IL_003c: ldloca.s V_2 + IL_003e: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0043: ret + } + """); + } + + [Fact] + public void Span_09() + { + var source = """ + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List o = CreateList(); + Span d = [.. o]; + d.Report(); + + d[0] = "a"; + d.Report(); + } + + static List CreateList() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 62 (0x3e) + .maxstack 2 + .locals init (System.Collections.Generic.List V_0, //o + System.Span V_1, //d + System.ReadOnlySpan V_2) + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldloc.0 + IL_0009: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_000e: call "System.Span..ctor(dynamic[])" + IL_0013: ldloc.1 + IL_0014: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0019: stloc.2 + IL_001a: ldloca.s V_2 + IL_001c: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0021: ldloca.s V_1 + IL_0023: ldc.i4.0 + IL_0024: call "ref dynamic System.Span.this[int].get" + IL_0029: ldstr "a" + IL_002e: stind.ref + IL_002f: ldloc.1 + IL_0030: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0035: stloc.2 + IL_0036: ldloca.s V_2 + IL_0038: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_003d: ret + } + """); + } + + [Theory, CombinatorialData] + public void Span_10(bool useReadOnlySpan) + { + var spanType = useReadOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} i = CreateArray(); + Span o = [.. i]; + o.Report(); + + o[0] = "a"; + o.Report(); + } + + static int[] CreateArray() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 123 (0x7b) + .maxstack 3 + .locals init (System.Span V_0, //o + System.{{spanType}} V_1, + int V_2, + object[] V_3, + System.{{spanType}}.Enumerator V_4, + int V_5, + System.ReadOnlySpan V_6) + IL_0000: call "int[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(int[])" + IL_000a: stloc.1 + IL_000b: ldc.i4.0 + IL_000c: stloc.2 + IL_000d: ldloca.s V_1 + IL_000f: call "int System.{{spanType}}.Length.get" + IL_0014: newarr "object" + IL_0019: stloc.3 + IL_001a: ldloca.s V_1 + IL_001c: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0021: stloc.s V_4 + IL_0023: br.s IL_003d + IL_0025: ldloca.s V_4 + IL_0027: call "ref {{(useReadOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_002c: ldind.i4 + IL_002d: stloc.s V_5 + IL_002f: ldloc.3 + IL_0030: ldloc.2 + IL_0031: ldloc.s V_5 + IL_0033: box "int" + IL_0038: stelem.ref + IL_0039: ldloc.2 + IL_003a: ldc.i4.1 + IL_003b: add + IL_003c: stloc.2 + IL_003d: ldloca.s V_4 + IL_003f: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_0044: brtrue.s IL_0025 + IL_0046: ldloca.s V_0 + IL_0048: ldloc.3 + IL_0049: call "System.Span..ctor(object[])" + IL_004e: ldloc.0 + IL_004f: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0054: stloc.s V_6 + IL_0056: ldloca.s V_6 + IL_0058: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_005d: ldloca.s V_0 + IL_005f: ldc.i4.0 + IL_0060: call "ref object System.Span.this[int].get" + IL_0065: ldstr "a" + IL_006a: stind.ref + IL_006b: ldloc.0 + IL_006c: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0071: stloc.s V_6 + IL_0073: ldloca.s V_6 + IL_0075: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_007a: ret + } + """); + } + + [Fact] + public void Span_11() + { + var source = $$""" + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List i = CreateList(); + Span o = [.. i]; + o.Report(); + + o[0] = "a"; + o.Report(); + } + + static List CreateList() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], [a, 2, 3], "), + verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 128 (0x80) + .maxstack 3 + .locals init (System.Span V_0, //o + int V_1, + object[] V_2, + System.Collections.Generic.List.Enumerator V_3, + int V_4, + System.ReadOnlySpan V_5) + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: ldc.i4.0 + IL_0006: stloc.1 + IL_0007: dup + IL_0008: callvirt "int System.Collections.Generic.List.Count.get" + IL_000d: newarr "object" + IL_0012: stloc.2 + IL_0013: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0018: stloc.3 + .try + { + IL_0019: br.s IL_0032 + IL_001b: ldloca.s V_3 + IL_001d: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_0022: stloc.s V_4 + IL_0024: ldloc.2 + IL_0025: ldloc.1 + IL_0026: ldloc.s V_4 + IL_0028: box "int" + IL_002d: stelem.ref + IL_002e: ldloc.1 + IL_002f: ldc.i4.1 + IL_0030: add + IL_0031: stloc.1 + IL_0032: ldloca.s V_3 + IL_0034: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0039: brtrue.s IL_001b + IL_003b: leave.s IL_004b + } + finally + { + IL_003d: ldloca.s V_3 + IL_003f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0045: callvirt "void System.IDisposable.Dispose()" + IL_004a: endfinally + } + IL_004b: ldloca.s V_0 + IL_004d: ldloc.2 + IL_004e: call "System.Span..ctor(object[])" + IL_0053: ldloc.0 + IL_0054: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0059: stloc.s V_5 + IL_005b: ldloca.s V_5 + IL_005d: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0062: ldloca.s V_0 + IL_0064: ldc.i4.0 + IL_0065: call "ref object System.Span.this[int].get" + IL_006a: ldstr "a" + IL_006f: stind.ref + IL_0070: ldloc.0 + IL_0071: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0076: stloc.s V_5 + IL_0078: ldloca.s V_5 + IL_007a: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_007f: ret + } + """); + } + [Fact] public void Span_MissingConstructor() { @@ -11365,7 +13204,7 @@ .locals init (int V_0, IL_004d: dup IL_004e: ldc.i4.0 IL_004f: call "void CollectionExtensions.Report(object, bool)" - IL_0054: call "object[] System.Linq.Enumerable.ToArray(System.Collections.Generic.IEnumerable)" + IL_0054: callvirt "dynamic[] System.Collections.Generic.List.ToArray()" IL_0059: ldc.i4.0 IL_005a: call "void CollectionExtensions.Report(object, bool)" IL_005f: ret @@ -25880,6 +27719,383 @@ static void Main() verifier.VerifyDiagnostics(); } + [Theory, CombinatorialData] + public void ReadOnlySpan_FromSingleSpread_ToArray_Covariant_Spans(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} d = CreateArray(); + ReadOnlySpan b = [.. d]; + b.Report(); + } + + static Derived[] CreateArray() => [new(), new(), new()]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 33 (0x21) + .maxstack 2 + .locals init (System.{{spanType}} V_0, //d + System.ReadOnlySpan V_1) //b + IL_0000: call "Derived[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(Derived[])" + IL_000a: stloc.0 + IL_000b: ldloca.s V_1 + IL_000d: ldloca.s V_0 + IL_000f: call "Derived[] System.{{spanType}}.ToArray()" + IL_0014: call "System.ReadOnlySpan..ctor(Base[])" + IL_0019: ldloca.s V_1 + IL_001b: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0020: ret + } + """); + } + + [Fact] + public void ReadOnlySpan_FromSingleSpread_ToArray_Covariant_List() + { + var source = """ + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = CreateList(); + ReadOnlySpan b = [.. d]; + b.Report(); + } + + static List CreateList() => [new(), new(), new()]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 27 (0x1b) + .maxstack 2 + .locals init (System.Collections.Generic.List V_0, //d + System.ReadOnlySpan V_1) //b + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldloc.0 + IL_0009: callvirt "Derived[] System.Collections.Generic.List.ToArray()" + IL_000e: call "System.ReadOnlySpan..ctor(Base[])" + IL_0013: ldloca.s V_1 + IL_0015: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_001a: ret + } + """); + } + + [Theory, CombinatorialData] + public void ReadOnlySpan_FromSingleSpread_ToArray_SameRuntimeType_Spans(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} d = CreateArray(); + ReadOnlySpan b = [.. d]; + b.Report(); + } + + static object[] CreateArray() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 33 (0x21) + .maxstack 2 + .locals init (System.{{spanType}} V_0, //d + System.ReadOnlySpan V_1) //b + IL_0000: call "object[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(object[])" + IL_000a: stloc.0 + IL_000b: ldloca.s V_1 + IL_000d: ldloca.s V_0 + IL_000f: call "object[] System.{{spanType}}.ToArray()" + IL_0014: call "System.ReadOnlySpan..ctor(dynamic[])" + IL_0019: ldloca.s V_1 + IL_001b: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0020: ret + } + """); + } + + [Fact] + public void ReadOnlySpan_FromSingleSpread_ToArray_SameRuntimeType_List() + { + var source = """ + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List d = CreateList(); + ReadOnlySpan b = [.. d]; + b.Report(); + } + + static List CreateList() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 27 (0x1b) + .maxstack 2 + .locals init (System.Collections.Generic.List V_0, //d + System.ReadOnlySpan V_1) //b + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldloc.0 + IL_0009: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_000e: call "System.ReadOnlySpan..ctor(dynamic[])" + IL_0013: ldloca.s V_1 + IL_0015: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_001a: ret + } + """); + } + + [Theory, CombinatorialData] + public void ReadOnlySpan_FromSingleSpread_ToArray_Boxing_Spans(bool reaOnlySpan) + { + var spanType = reaOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + + class Program + { + static void Main() + { + {{spanType}} i = CreateArray(); + ReadOnlySpan o = [.. i]; + o.Report(); + } + + static int[] CreateArray() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", $$""" + { + // Code size 86 (0x56) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, //o + System.{{spanType}} V_1, + int V_2, + object[] V_3, + System.{{spanType}}.Enumerator V_4, + int V_5) + IL_0000: call "int[] Program.CreateArray()" + IL_0005: call "System.{{spanType}} System.{{spanType}}.op_Implicit(int[])" + IL_000a: stloc.1 + IL_000b: ldc.i4.0 + IL_000c: stloc.2 + IL_000d: ldloca.s V_1 + IL_000f: call "int System.{{spanType}}.Length.get" + IL_0014: newarr "object" + IL_0019: stloc.3 + IL_001a: ldloca.s V_1 + IL_001c: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0021: stloc.s V_4 + IL_0023: br.s IL_003d + IL_0025: ldloca.s V_4 + IL_0027: call "ref {{(reaOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_002c: ldind.i4 + IL_002d: stloc.s V_5 + IL_002f: ldloc.3 + IL_0030: ldloc.2 + IL_0031: ldloc.s V_5 + IL_0033: box "int" + IL_0038: stelem.ref + IL_0039: ldloc.2 + IL_003a: ldc.i4.1 + IL_003b: add + IL_003c: stloc.2 + IL_003d: ldloca.s V_4 + IL_003f: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_0044: brtrue.s IL_0025 + IL_0046: ldloca.s V_0 + IL_0048: ldloc.3 + IL_0049: call "System.ReadOnlySpan..ctor(object[])" + IL_004e: ldloca.s V_0 + IL_0050: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0055: ret + } + """); + } + + [Fact] + public void ReadOnlySpan_FromSingleSpread_ToArray_Boxing_List() + { + var source = """ + using System; + using System.Collections.Generic; + + class Program + { + static void Main() + { + List i = CreateList(); + ReadOnlySpan o = [.. i]; + o.Report(); + } + + static List CreateList() => [1, 2, 3]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 91 (0x5b) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, //o + int V_1, + object[] V_2, + System.Collections.Generic.List.Enumerator V_3, + int V_4) + IL_0000: call "System.Collections.Generic.List Program.CreateList()" + IL_0005: ldc.i4.0 + IL_0006: stloc.1 + IL_0007: dup + IL_0008: callvirt "int System.Collections.Generic.List.Count.get" + IL_000d: newarr "object" + IL_0012: stloc.2 + IL_0013: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0018: stloc.3 + .try + { + IL_0019: br.s IL_0032 + IL_001b: ldloca.s V_3 + IL_001d: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_0022: stloc.s V_4 + IL_0024: ldloc.2 + IL_0025: ldloc.1 + IL_0026: ldloc.s V_4 + IL_0028: box "int" + IL_002d: stelem.ref + IL_002e: ldloc.1 + IL_002f: ldc.i4.1 + IL_0030: add + IL_0031: stloc.1 + IL_0032: ldloca.s V_3 + IL_0034: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0039: brtrue.s IL_001b + IL_003b: leave.s IL_004b + } + finally + { + IL_003d: ldloca.s V_3 + IL_003f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0045: callvirt "void System.IDisposable.Dispose()" + IL_004a: endfinally + } + IL_004b: ldloca.s V_0 + IL_004d: ldloc.2 + IL_004e: call "System.ReadOnlySpan..ctor(object[])" + IL_0053: ldloca.s V_0 + IL_0055: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_005a: ret + } + """); + } + [Fact] public void RuntimeHelpers_CreateSpan_MissingCreateSpan() { @@ -30824,6 +33040,369 @@ .locals init (System.ReadOnlySpan V_0, """); } + [Theory, CombinatorialData] + public void ImmutableArray_11(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([new(), new(), new()]).Report(); + } + + static ImmutableArray M({{spanType}} span) => [.. span]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 13 (0xd) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: call "Derived[] System.{{spanType}}.ToArray()" + IL_0007: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(Base[])" + IL_000c: ret + } + """); + } + + [Fact] + public void ImmutableArray_12() + { + var source = """ + using System.Collections.Generic; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([new(), new(), new()]).Report(); + } + + static ImmutableArray M(List list) => [.. list]; + } + + class Base + { + private static int _totalCount; + private readonly int _id; + + public Base() + { + _id = _totalCount++; + } + + public override string ToString() => $"{GetType().Name} {_id}"; + } + + class Derived : Base; + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[Derived 0, Derived 1, Derived 2], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "Derived[] System.Collections.Generic.List.ToArray()" + IL_0006: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(Base[])" + IL_000b: ret + } + """); + } + + [Fact] + public void ImmutableArray_13() + { + var source = """ + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(Span span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 13 (0xd) + .maxstack 1 + IL_0000: ldarga.s V_0 + IL_0002: call "object[] System.Span.ToArray()" + IL_0007: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(dynamic[])" + IL_000c: ret + } + """); + } + + [Fact] + public void ImmutableArray_14() + { + var source = """ + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.Collections.Immutable.ImmutableArray System.Collections.Immutable.ImmutableArray.Create(params System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + + [Fact] + public void ImmutableArray_15() + { + var source = """ + using System.Collections.Generic; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(List list) => [.. list]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_0006: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(dynamic[])" + IL_000b: ret + } + """); + } + + [Theory, CombinatorialData] + public void ImmutableArray_16(bool readOnlySpan) + { + var spanType = readOnlySpan ? "ReadOnlySpan" : "Span"; + + var source = $$""" + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M({{spanType}} span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", $$""" + { + // Code size 67 (0x43) + .maxstack 3 + .locals init (System.{{spanType}} V_0, + int V_1, + object[] V_2, + System.{{spanType}}.Enumerator V_3, + int V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.{{spanType}}.Length.get" + IL_000b: newarr "object" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.{{spanType}}.Enumerator System.{{spanType}}.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0033 + IL_001b: ldloca.s V_3 + IL_001d: call "ref {{(readOnlySpan ? "readonly " : "")}}int System.{{spanType}}.Enumerator.Current.get" + IL_0022: ldind.i4 + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: box "int" + IL_002e: stelem.ref + IL_002f: ldloc.1 + IL_0030: ldc.i4.1 + IL_0031: add + IL_0032: stloc.1 + IL_0033: ldloca.s V_3 + IL_0035: call "bool System.{{spanType}}.Enumerator.MoveNext()" + IL_003a: brtrue.s IL_001b + IL_003c: ldloc.2 + IL_003d: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(object[])" + IL_0042: ret + } + """); + } + + [Fact] + public void ImmutableArray_17() + { + var source = """ + using System.Collections.Generic; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(List span) => [.. span]; + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensions], + targetFramework: TargetFramework.Net90, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 76 (0x4c) + .maxstack 3 + .locals init (int V_0, + object[] V_1, + System.Collections.Generic.List.Enumerator V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + IL_0003: dup + IL_0004: callvirt "int System.Collections.Generic.List.Count.get" + IL_0009: newarr "object" + IL_000e: stloc.1 + IL_000f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0014: stloc.2 + .try + { + IL_0015: br.s IL_002c + IL_0017: ldloca.s V_2 + IL_0019: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_001e: stloc.3 + IL_001f: ldloc.1 + IL_0020: ldloc.0 + IL_0021: ldloc.3 + IL_0022: box "int" + IL_0027: stelem.ref + IL_0028: ldloc.0 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stloc.0 + IL_002c: ldloca.s V_2 + IL_002e: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0033: brtrue.s IL_0017 + IL_0035: leave.s IL_0045 + } + finally + { + IL_0037: ldloca.s V_2 + IL_0039: constrained. "System.Collections.Generic.List.Enumerator" + IL_003f: callvirt "void System.IDisposable.Dispose()" + IL_0044: endfinally + } + IL_0045: ldloc.1 + IL_0046: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(object[])" + IL_004b: ret + } + """); + } + [Fact] public void SpanImplicitAllocationWarning_01() { @@ -35008,7 +37587,11 @@ public void SingleSpread_ListToArray_Covariant() using System; using System.Collections.Generic; - Console.Write(C.M(["a"])[0]); + List l = ["a"]; + var o = C.M(l); + Console.Write(o[0]); + o[0] = 1; + Console.Write(o[0]); class C { @@ -35016,7 +37599,7 @@ class C } """; - var verifier = CompileAndVerify(source, expectedOutput: "a"); + var verifier = CompileAndVerify(source, expectedOutput: "a1"); verifier.VerifyIL("C.M", """ { // Code size 66 (0x42) @@ -35066,6 +37649,111 @@ .locals init (int V_0, """); } + [Fact] + public void SingleSpread_ListToArray_SameRuntimeTypes() + { + var source = """ + using System; + using System.Collections.Generic; + + List l = ["a"]; + var d = C.M(l); + Console.Write(d[0]); + d[0] = 1; + Console.Write(d[0]); + + class C + { + public static dynamic[] M(List list) => [..list]; + } + """; + + var verifier = CompileAndVerify(source, + targetFramework: TargetFramework.Net80, + expectedOutput: IncludeExpectedOutput("a1"), + verify: Verification.Skipped); + + verifier.VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_0006: ret + } + """); + } + + [Fact] + public void SingleSpread_ListToArray_Boxing() + { + var source = """ + using System; + using System.Collections.Generic; + + List l = [1]; + var o = C.M(l); + Console.Write(o[0]); + o[0] = "a"; + Console.Write(o[0]); + + class C + { + public static object[] M(List list) => [..list]; + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1a"); + verifier.VerifyIL("C.M", """ + { + // Code size 71 (0x47) + .maxstack 3 + .locals init (int V_0, + object[] V_1, + System.Collections.Generic.List.Enumerator V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + IL_0003: dup + IL_0004: callvirt "int System.Collections.Generic.List.Count.get" + IL_0009: newarr "object" + IL_000e: stloc.1 + IL_000f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0014: stloc.2 + .try + { + IL_0015: br.s IL_002c + IL_0017: ldloca.s V_2 + IL_0019: call "int System.Collections.Generic.List.Enumerator.Current.get" + IL_001e: stloc.3 + IL_001f: ldloc.1 + IL_0020: ldloc.0 + IL_0021: ldloc.3 + IL_0022: box "int" + IL_0027: stelem.ref + IL_0028: ldloc.0 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stloc.0 + IL_002c: ldloca.s V_2 + IL_002e: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0033: brtrue.s IL_0017 + IL_0035: leave.s IL_0045 + } + finally + { + IL_0037: ldloca.s V_2 + IL_0039: constrained. "System.Collections.Generic.List.Enumerator" + IL_003f: callvirt "void System.IDisposable.Dispose()" + IL_0044: endfinally + } + IL_0045: ldloc.1 + IL_0046: ret + } + """); + } + [Fact] public void SingleSpread_ListToIEnumerable_Covariant() { @@ -35085,50 +37773,113 @@ class C var verifier = CompileAndVerify(source, expectedOutput: "a"); verifier.VerifyIL("C.M", """ { - // Code size 71 (0x47) + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "string[] System.Collections.Generic.List.ToArray()" + IL_0006: newobj "<>z__ReadOnlyArray..ctor(object[])" + IL_000b: ret + } + """); + } + + [Fact] + public void SingleSpread_ListToIEnumerable_SameRuntimeType() + { + var source = """ + using System; + using System.Collections.Generic; + + foreach (var item in C.M(["a"])) + Console.Write(item); + + class C + { + public static IEnumerable M(List list) => [..list]; + } + """; + + var verifier = CompileAndVerify(source, + targetFramework: TargetFramework.Net80, + expectedOutput: IncludeExpectedOutput("a"), + verify: Verification.Skipped); + + verifier.VerifyIL("C.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: callvirt "object[] System.Collections.Generic.List.ToArray()" + IL_0006: newobj "<>z__ReadOnlyArray..ctor(dynamic[])" + IL_000b: ret + } + """); + } + + [Fact] + public void SingleSpread_ListToIEnumerable_Boxing() + { + var source = """ + using System; + using System.Collections.Generic; + + foreach (var item in C.M([1])) + Console.Write(item); + + class C + { + public static IEnumerable M(List list) => [..list]; + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyIL("C.M", """ + { + // Code size 76 (0x4c) .maxstack 3 .locals init (int V_0, object[] V_1, - System.Collections.Generic.List.Enumerator V_2, - string V_3) + System.Collections.Generic.List.Enumerator V_2, + int V_3) IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: dup - IL_0004: callvirt "int System.Collections.Generic.List.Count.get" + IL_0004: callvirt "int System.Collections.Generic.List.Count.get" IL_0009: newarr "object" IL_000e: stloc.1 - IL_000f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_000f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" IL_0014: stloc.2 .try { - IL_0015: br.s IL_0027 + IL_0015: br.s IL_002c IL_0017: ldloca.s V_2 - IL_0019: call "string System.Collections.Generic.List.Enumerator.Current.get" + IL_0019: call "int System.Collections.Generic.List.Enumerator.Current.get" IL_001e: stloc.3 IL_001f: ldloc.1 IL_0020: ldloc.0 IL_0021: ldloc.3 - IL_0022: stelem.ref - IL_0023: ldloc.0 - IL_0024: ldc.i4.1 - IL_0025: add - IL_0026: stloc.0 - IL_0027: ldloca.s V_2 - IL_0029: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_002e: brtrue.s IL_0017 - IL_0030: leave.s IL_0040 + IL_0022: box "int" + IL_0027: stelem.ref + IL_0028: ldloc.0 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stloc.0 + IL_002c: ldloca.s V_2 + IL_002e: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0033: brtrue.s IL_0017 + IL_0035: leave.s IL_0045 } finally { - IL_0032: ldloca.s V_2 - IL_0034: constrained. "System.Collections.Generic.List.Enumerator" - IL_003a: callvirt "void System.IDisposable.Dispose()" - IL_003f: endfinally + IL_0037: ldloca.s V_2 + IL_0039: constrained. "System.Collections.Generic.List.Enumerator" + IL_003f: callvirt "void System.IDisposable.Dispose()" + IL_0044: endfinally } - IL_0040: ldloc.1 - IL_0041: newobj "<>z__ReadOnlyArray..ctor(object[])" - IL_0046: ret + IL_0045: ldloc.1 + IL_0046: newobj "<>z__ReadOnlyArray..ctor(object[])" + IL_004b: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index b051a0a83c9c3..ff610f803a3dc 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -1897,17 +1897,17 @@ void M(this int i) { } """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (5,14): error CS1109: Extension methods must be defined in a top level static class; is a nested class + // (5,16): error CS0027: Keyword 'this' is not available in the current context // void M(this int i) { } - Diagnostic(ErrorCode.ERR_ExtensionMethodsDecl, "M").WithArguments("").WithLocation(5, 14)); + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(5, 16)); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var method = tree.GetRoot().DescendantNodes().OfType().Single(); var symbol = model.GetDeclaredSymbol(method); - AssertEx.Equal("void Extensions.$C43E2675C7BBF9284AF22FB8A9BF0280.M(this System.Int32 i)", symbol.ToTestDisplayString()); - Assert.True(symbol.IsExtensionMethod); + AssertEx.Equal("void Extensions.$C43E2675C7BBF9284AF22FB8A9BF0280.M(System.Int32 i)", symbol.ToTestDisplayString()); + Assert.False(symbol.IsExtensionMethod); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 4a100bd2eb61e..ea3cd51eea115 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -35085,5 +35085,350 @@ public void SyntaxFacts_01() Assert.Equal("extension", SyntaxFacts.GetText(SyntaxKind.ExtensionKeyword)); } + + [Theory, WorkItem("https://developercommunity.visualstudio.com/t/NRE-in-Roslyn-v500-225451107/10979295")] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.Latest)] + public void InvalidReceiverWithOldExtensionInFileClass(LanguageVersion languageVersion) + { + var code = """ + #nullable enable + using System.Threading.Tasks; + using N2; + namespace N1 + { + class C + { + async Task M(object o) + { + await using var test = await nonExistent.ExtensionMethod(o); + } + } + } + """; + + var code2 = """ + namespace N2; + + file static class E + { + public static void ExtensionMethod(this object o) { } + } + """; + + var comp = CreateCompilation([(code, "file1.cs"), (code2, "file2.cs")], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + + if (languageVersion == LanguageVersion.CSharp10) + { + comp.VerifyEmitDiagnostics( + // file2.cs(3,19): error CS8936: Feature 'file types' is not available in C# 10.0. Please use language version 11.0 or greater. + // file static class E + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "E").WithArguments("file types", "11.0").WithLocation(3, 19), + // file1.cs(10,42): error CS0103: The name 'nonExistent' does not exist in the current context + // await using var test = await nonExistent.ExtensionMethod(o); + Diagnostic(ErrorCode.ERR_NameNotInContext, "nonExistent").WithArguments("nonExistent").WithLocation(10, 42) + ); + } + else + { + comp.VerifyEmitDiagnostics( + // file1.cs(10,42): error CS0103: The name 'nonExistent' does not exist in the current context + // await using var test = await nonExistent.ExtensionMethod(o); + Diagnostic(ErrorCode.ERR_NameNotInContext, "nonExistent").WithArguments("nonExistent").WithLocation(10, 42) + ); + } + } + + [Fact, WorkItem("https://developercommunity.visualstudio.com/t/NRE-in-Roslyn-v500-225451107/10979295")] + public void InvalidReceiverWithNewExtensionInFileClass() + { + var code = """ + #nullable enable + using System.Threading.Tasks; + using N2; + namespace N1 + { + class C + { + async Task M(object o) + { + await using var test = await nonExistent.ExtensionMethod(o); + } + } + } + """; + + var code2 = """ + namespace N2; + + file static class E + { + extension(object o) + { + public void ExtensionMethod(object o2) { } + } + } + """; + + var comp = CreateCompilation([(code, "file1.cs"), (code2, "file2.cs")]); + comp.VerifyEmitDiagnostics( + // file1.cs(10,42): error CS0103: The name 'nonExistent' does not exist in the current context + // await using var test = await nonExistent.ExtensionMethod(o); + Diagnostic(ErrorCode.ERR_NameNotInContext, "nonExistent").WithArguments("nonExistent").WithLocation(10, 42) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80512")] + public void Params_01() + { + var src = """ +var c = new C(); +c.Add(1); + +interface I { } +class C : I { } + +static class E +{ + extension(I node) + { + public void Add(T1 value) { System.Console.Write("ran"); } + public void Add(params T1[] values) => throw null; + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + src = """ +var c = new C(); +c.Add(1); + +interface I { } +class C : I { } + +static class E +{ + public static void Add(this I node, T1 value) { System.Console.Write("ran"); } + public static void Add(this I node, params T1[] values) => throw null; +} +"""; + comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void Params_02() + { + var src = """ +var c = new C(); +c.Add(x: 1, y: 2); + +interface I { } +class C : I { } + +static class E +{ + extension(I node) where T : struct + { + public void Add(T x, params T[] y) { } + public void Add(T y, params System.Span x) { } + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (2,3): error CS0121: The call is ambiguous between the following methods or properties: 'E.extension(I).Add(int, params int[])' and 'E.extension(I).Add(int, params System.Span)' + // c.Add(x: 1, y: 2); + Diagnostic(ErrorCode.ERR_AmbigCall, "Add").WithArguments("E.extension(I).Add(int, params int[])", "E.extension(I).Add(int, params System.Span)").WithLocation(2, 3)); + } + + [Fact] + public void Params_03() + { + var src = """ +var c = new C(); +c.Add(1, 2); + +interface I { } +class C : I { } + +static class E +{ + extension(I node) where T : struct + { + public void Add(T x, params T[] y) { } + public void Add(T y, params System.Span x) { System.Console.Write("ran"); } + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("ran"), verify: Verification.FailsPEVerify).VerifyDiagnostics(); + } + + [Fact] + public void ConversionInParamsArguments_02() + { + var code = """ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +object.M("", $"test"); + +public static class E +{ + extension(object) + { + public static void M(string s, [InterpolatedStringHandlerArgument(nameof(s))] params CustomHandler[] handlers) + { + } + } +} +"""; + + var comp = CreateCompilation([code, InterpolatedStringHandlerArgumentAttribute, GetInterpolatedStringCustomHandlerType("CustomHandler", "struct", useBoolReturns: false)]); + comp.VerifyEmitDiagnostics( + // (11,41): error CS8946: 'CustomHandler[]' is not an interpolated string handler type. + // public static void M(string s, [InterpolatedStringHandlerArgument(nameof(s))] params CustomHandler[] handlers) + Diagnostic(ErrorCode.ERR_TypeIsNotAnInterpolatedStringHandlerType, "InterpolatedStringHandlerArgument(nameof(s))").WithArguments("CustomHandler[]").WithLocation(11, 41)); + + code = """ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +"".M($"test"); + +public static class E +{ + extension(string s) + { + public void M([InterpolatedStringHandlerArgument(nameof(s))] params CustomHandler[] handlers) + { + } + } +} +"""; + + comp = CreateCompilation([code, InterpolatedStringHandlerArgumentAttribute, GetInterpolatedStringCustomHandlerType("CustomHandler", "struct", useBoolReturns: false)]); + comp.VerifyEmitDiagnostics( + // (11,24): error CS8946: 'CustomHandler[]' is not an interpolated string handler type. + // public void M([InterpolatedStringHandlerArgument(nameof(s))] params CustomHandler[] handlers) + Diagnostic(ErrorCode.ERR_TypeIsNotAnInterpolatedStringHandlerType, "InterpolatedStringHandlerArgument(nameof(s))").WithArguments("CustomHandler[]").WithLocation(11, 24)); + } + + [Fact] + public void OptionalArguments_01() + { + var src = """ +new object().M1(); +object.M2(); + +static class E +{ + extension(object o) + { + public void M1(int x = 4, int y = 2) { System.Console.Write($"ran1 {x}{y} "); } + public static void M2(int x = 4, int y = 2) { System.Console.Write($"ran2 {x}{y} "); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran1 42 ran2 42").VerifyDiagnostics(); + } + + [Fact] + public void OptionalArguments_02() + { + var src = """ +new object().M1(y: 3); +object.M2(y: 3); + +static class E +{ + extension(object o) + { + public void M1(int x = 4, int y = 2) { System.Console.Write($"ran1 {x}{y} "); } + public static void M2(int x = 4, int y = 2) { System.Console.Write($"ran2 {x}{y} "); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran1 43 ran2 43").VerifyDiagnostics(); + } + + [Fact] + public void OptionalArguments_03() + { + var src = """ +new object().M1(z: 3, x: 1); +object.M2(z: 3, x: 1); + +static class E +{ + extension(object o) + { + public void M1(int x, int y = 2, params int[] z) { System.Console.Write($"ran1 {x}{y}{z[0]} "); } + public static void M2(int x, int y = 2, params int[] z) { System.Console.Write($"ran2 {x}{y}{z[0]} "); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran1 123 ran2 123").VerifyDiagnostics(); + } + + [Fact] + public void ThisModifier_01() + { + var src = """ +static class E +{ + extension(object o) + { + public void M1(this int i = 0) => throw null; + public static void M2(this int i = 0) => throw null; + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,24): error CS0027: Keyword 'this' is not available in the current context + // public void M1(this int i = 0) => throw null; + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(5, 24), + // (6,31): error CS0027: Keyword 'this' is not available in the current context + // public static void M2(this int i = 0) => throw null; + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(6, 31)); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers("").Single(); + var m1 = extension.GetMember("M1"); + Assert.False(m1.IsExtensionMethod); + + var m2 = extension.GetMember("M2"); + Assert.False(m2.IsExtensionMethod); + } + + [Fact] + public void ThisModifier_02() + { + var src = """ +static class E +{ + extension(object o) + { + public void M1(int i, this int j) => throw null; + public static void M2(int i, this int j) => throw null; + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,31): error CS0027: Keyword 'this' is not available in the current context + // public void M1(int i, this int j) => throw null; + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(5, 31), + // (6,38): error CS0027: Keyword 'this' is not available in the current context + // public static void M2(int i, this int j) => throw null; + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(6, 38)); + } } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs index cc001d58ca554..f704ed0086a7e 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs @@ -25777,9 +25777,9 @@ public record E { } if (c.Assembly.RuntimeSupportsCovariantReturnsOfClasses) { c.VerifyDiagnostics( - // (8,12): error CS0060: Inconsistent accessibility: base type 'X' is less accessible than class 'B.C' + // (8,12): error CS9338: Inconsistent accessibility: type 'B.C.D' is less accessible than class 'B.C' // record C : X - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("B.C", "X").WithLocation(8, 12), + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("B.C", "B.C.D").WithLocation(8, 12), // (8,12): error CS0051: Inconsistent accessibility: parameter type 'X' is less accessible than method 'B.C.Equals(X?)' // record C : X Diagnostic(ErrorCode.ERR_BadVisParamType, "C").WithArguments("B.C.Equals(X?)", "X").WithLocation(8, 12) @@ -25788,9 +25788,9 @@ public record E { } else { c.VerifyDiagnostics( - // (8,12): error CS0060: Inconsistent accessibility: base type 'X' is less accessible than class 'B.C' + // (8,12): error CS9338: Inconsistant accessibility: type 'B.C.D' is less accessible than class 'B.C' // record C : X - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("B.C", "X").WithLocation(8, 12), + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("B.C", "B.C.D").WithLocation(8, 12), // (8,12): error CS0050: Inconsistent accessibility: return type 'X' is less accessible than method 'B.C.$()' // record C : X Diagnostic(ErrorCode.ERR_BadVisReturnType, "C").WithArguments("B.C.$()", "X").WithLocation(8, 12), diff --git a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs index 9b82c44d634d1..98c9f3251ac2e 100644 --- a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs +++ b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs @@ -837,7 +837,7 @@ class XAttribute : System.Attribute { } [Theory] [InlineData("or", "1")] [InlineData("and not", "0")] - public void ManyBinaryPatterns(string pattern, string expectedOutput) + public void ManyBinaryPatterns_01(string pattern, string expectedOutput) { const string preamble = $""" int i = 2; @@ -887,5 +887,85 @@ public void ManyBinaryPatterns(string pattern, string expectedOutput) Assert.NotNull(ControlFlowGraph.Create((IMethodBodyOperation)operation)); }); } + + [Fact] + public void ManyBinaryPatterns_02() + { + const int numOfEnumMembers = 5_000; + const int capacity = 97973; + + var builder = new StringBuilder(capacity); + + builder.Append(""" +#nullable enable + +class ErrorFacts +{ + static bool Test(E code) + { + return code switch + { + E._0 +"""); + + for (int i = 1; i < numOfEnumMembers; i++) + { + builder.Append($""" + +or E._{i} +"""); + } + + builder.Append(""" + => false, + }; + } +} + +enum E +{ + _0, +"""); + + for (int i = 1; i < numOfEnumMembers; i++) + { + builder.Append($""" + +_{i}, +"""); + } + + builder.Append(""" +} +"""); + + Assert.Equal(capacity, builder.Length); + + var source = builder.ToString(); + RunInThread(() => + { + var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var node1 = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("E._0", model.GetSymbolInfo(node1).Symbol.ToTestDisplayString()); + var node2 = tree.GetRoot().DescendantNodes().OfType().Last(); + Assert.Equal($"E._{numOfEnumMembers - 1}", model.GetSymbolInfo(node2).Symbol.ToTestDisplayString()); + + var operation = model.GetOperation(node1); + Assert.NotNull(operation); + + for (; operation.Parent is not null; operation = operation.Parent) { } + + Assert.NotNull(ControlFlowGraph.Create((IMethodBodyOperation)operation)); + + model.GetDiagnostics().Verify( + // (7,21): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(E)5000' is not covered. + // return code switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(E)" + numOfEnumMembers).WithLocation(7, 21) + ); + }); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/AccessCheckTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/AccessCheckTests.cs index 0efdcad68676c..050d89f40a991 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/AccessCheckTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/AccessCheckTests.cs @@ -429,9 +429,9 @@ public class E { } "); c.VerifyDiagnostics( - // (8,11): error CS0060: Inconsistent accessibility: base type 'X' is less accessible than class 'B.C' + // (8,11): error CS9338: Inconsistent accessibility: type 'B.C.D' is less accessible than class 'B.C' // class C : X - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("B.C", "X").WithLocation(8, 11)); + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("B.C", "B.C.D").WithLocation(8, 11)); } [Fact] @@ -593,9 +593,9 @@ public class E { } "); c.VerifyDiagnostics( - // (8,11): error CS0060: Inconsistent accessibility: base type 'X' is less accessible than class 'B.C' + // (8,11): error CS9338: Inconsistent accessibility: type 'B.C.D' is less accessible than class 'B.C' // class C : X - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("B.C", "X").WithLocation(8, 11)); + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("B.C", "B.C.D").WithLocation(8, 11)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs index 9ca1ad18190eb..31c7dd63d85f8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs @@ -13942,5 +13942,361 @@ void M2() comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics(); } + + [Fact] + public void ExplicitImplementation_01() + { + var csharp = @" +interface ITest +{ + void Method(); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + unsafe void ITest.Method() + { + + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void ExplicitImplementation_02() + { + var csharp = @" +interface ITest +{ + int P1 {get;} +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + unsafe int ITest.P1 + { + get; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + ); + } + + [Fact] + public void ExplicitImplementation_03() + { + var csharp = @" +interface ITest +{ + int this[int i] {get;} +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + unsafe int ITest.this[int i] + { + get => 0; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + ); + } + + [Fact] + public void ExplicitImplementation_04() + { + var csharp = @" +interface ITest +{ + event System.Action E1; +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + unsafe event System.Action ITest.E1 + { + add{} + remove{} + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void ExplicitImplementation_05() + { + var csharp = @" +interface ITest +{ + abstract static ITest operator-(ITest x); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + static unsafe ITest ITest.operator-(ITest x) + { + return null; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.NetCoreApp); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void ExplicitImplementation_06() + { + var csharp = @" +interface ITest +{ + abstract static implicit operator int(ITest x); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + static unsafe implicit ITest.operator int(ITest x) + { + return 0; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.NetCoreApp); + comp.VerifyEmitDiagnostics( + // (4,39): error CS0552: 'ITest.implicit operator int(ITest)': user-defined conversions to or from an interface are not allowed + // abstract static implicit operator int(ITest x); + Diagnostic(ErrorCode.ERR_ConversionWithInterface, "int").WithArguments("ITest.implicit operator int(ITest)").WithLocation(4, 39) + ); + } + + [Fact] + public void ExplicitImplementation_07() + { + var csharp = @" +interface ITest +{ + void Method(); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + void ITest.Method() + { + + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (14,16): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // void ITest.Method() + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 16) + ); + } + + [Fact] + public void ExplicitImplementation_08() + { + var csharp = @" +interface ITest +{ + int P1 {get;} +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + int ITest.P1 + { + get; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (14,15): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // int ITest.P1 + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 15) + ); + } + + [Fact] + public void ExplicitImplementation_09() + { + var csharp = @" +interface ITest +{ + int this[int i] {get;} +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + int ITest.this[int i] + { + get => 0; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (14,15): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // int ITest.this[int i] + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 15) + ); + } + + [Fact] + public void ExplicitImplementation_10() + { + var csharp = @" +interface ITest +{ + event System.Action E1; +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + event System.Action ITest.E1 + { + add{} + remove{} + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (14,31): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // event System.Action ITest.E1 + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 31) + ); + } + + [Fact] + public void ExplicitImplementation_11() + { + var csharp = @" +interface ITest +{ + abstract static ITest operator-(ITest x); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + static ITest ITest.operator-(ITest x) + { + return null; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.NetCoreApp); + comp.VerifyEmitDiagnostics( + // (14,18): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // static ITest ITest.operator-(ITest x) + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 18), + // (14,33): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // static ITest ITest.operator-(ITest x) + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 33), + // (14,58): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // static ITest ITest.operator-(ITest x) + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 58) + ); + } + + [Fact] + public void ExplicitImplementation_12() + { + var csharp = @" +interface ITest +{ + abstract static implicit operator int(ITest x); +} + +unsafe interface IPointerTest : ITest +{ + +} + +class PointerImpl : IPointerTest +{ + static implicit ITest.operator int(ITest x) + { + return 0; + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.NetCoreApp); + comp.VerifyEmitDiagnostics( + // (4,39): error CS0552: 'ITest.implicit operator int(ITest)': user-defined conversions to or from an interface are not allowed + // abstract static implicit operator int(ITest x); + Diagnostic(ErrorCode.ERR_ConversionWithInterface, "int").WithArguments("ITest.implicit operator int(ITest)").WithLocation(4, 39), + // (14,27): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // static implicit ITest.operator int(ITest x) + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 27), + // (14,55): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context + // static implicit ITest.operator int(ITest x) + Diagnostic(ErrorCode.ERR_UnsafeNeeded, "void*").WithLocation(14, 55) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/BaseClassTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/BaseClassTests.cs index 3c2db5bf9e99e..f24f5c1d4cd8c 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/BaseClassTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/BaseClassTests.cs @@ -337,9 +337,9 @@ public class E : C.X { } }"; var comp = CreateCompilation(text); comp.VerifyDiagnostics( - // (16,22): error CS0060: Inconsistent accessibility: base type 'A.B.C.X' is less accessible than class 'F.D.E' + // (16,22): error CS9338: Inconsistent accessibility: type 'A.B.C' is less accessible than class 'F.D.E' // public class E : C.X { } - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "E").WithArguments("F.D.E", "A.B.C.X") + Diagnostic(ErrorCode.ERR_BadVisBaseType, "E").WithArguments("F.D.E", "A.B.C") ); } @@ -2408,5 +2408,81 @@ class A : I> // class A : I> Diagnostic(ErrorCode.ERR_CircularBase, "B").WithArguments("A", "A").WithLocation(4, 41)); } + + [Fact] + public void TestBadBaseClassVisibility() + { + var source = @" +class A { } +public class B : A { }"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,14): error CS0060: Inconsistent accessibility: base class 'A' is less accessible than class 'B' + // public class B : A { } + Diagnostic(ErrorCode.ERR_BadVisBaseClass, "B").WithArguments("B", "A").WithLocation(3, 14)); + } + + [Fact] + public void TestBadTypeParameterVisibility() + { + var source = @" +class A { } +public class B { } +public class C : B { }"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C' + // public class C : B { } + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("C", "A").WithLocation(4, 14)); + } + + [Fact] + public void TestBadContainedTypeVisibility() + { + var source = @" +public class A { internal class B { } } +public class C : A.B { }"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,14): error CS0060: Inconsistent accessibility: base class 'A.B' is less accessible than class 'C' + // public class C : A.B { } + Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("C", "A.B").WithLocation(3, 14)); + } + + [Fact] + public void TestBadContainingTypeVisibility() + { + var source = @" +class A { public class B { } } +public class C : A.B { }"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C' + // public class C : A.B { } + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("C", "A").WithLocation(3, 14)); + } + + [Fact] + public void TestBadContainingTypeVisibilityWithoutExplicitReference() + { + var source = @" +using static A; + +static class A +{ + public class B { } +} + +public class C : B { }"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (9,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C' + // public class C : B { } + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("C", "A").WithLocation(9, 14)); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index 9c279b9a2e8f6..5e7eb5affeea3 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -636,7 +636,7 @@ protected class D { } } "; var comp = DiagnosticsUtils.VerifyErrorsAndGetCompilationWithMscorlib(text, - new ErrorDescription { Code = (int)ErrorCode.ERR_BadVisBaseClass, Line = 4, Column = 18 }); + new ErrorDescription { Code = (int)ErrorCode.ERR_BadVisBaseType, Line = 4, Column = 18 }); } [WorkItem(539512, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/539512")] @@ -681,7 +681,7 @@ private class C { } } "; var comp = DiagnosticsUtils.VerifyErrorsAndGetCompilationWithMscorlib(text, - new ErrorDescription { Code = (int)ErrorCode.ERR_BadVisBaseClass, Line = 4, Column = 19 }); + new ErrorDescription { Code = (int)ErrorCode.ERR_BadVisBaseType, Line = 4, Column = 19 }); } [WorkItem(539562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/539562")] @@ -739,14 +739,14 @@ public class C2 : B.C { } public class C3 : B.C { } public class C4 : B>.C { }"; CreateCompilation(source).VerifyDiagnostics( - // (6,14): error CS0060: Inconsistent accessibility: base type 'B' is less accessible than class 'C1' - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C1").WithArguments("C1", "B").WithLocation(6, 14), - // (7,14): error CS0060: Inconsistent accessibility: base type 'B.C' is less accessible than class 'C2' - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C2").WithArguments("C2", "B.C").WithLocation(7, 14), - // (8,14): error CS0060: Inconsistent accessibility: base type 'B.C' is less accessible than class 'C3' - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C3").WithArguments("C3", "B.C").WithLocation(8, 14), - // (9,14): error CS0060: Inconsistent accessibility: base type 'B>.C' is less accessible than class 'C4' - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C4").WithArguments("C4", "B>.C").WithLocation(9, 14)); + // (6,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C1' + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C1").WithArguments("C1", "A").WithLocation(6, 14), + // (7,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C2' + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C2").WithArguments("C2", "A").WithLocation(7, 14), + // (8,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C3' + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C3").WithArguments("C3", "A").WithLocation(8, 14), + // (9,14): error CS9338: Inconsistent accessibility: type 'A' is less accessible than class 'C4' + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C4").WithArguments("C4", "A").WithLocation(9, 14)); } [Fact] @@ -763,8 +763,8 @@ public interface C { } public class B : A { } public class C : B { }"; CreateCompilation(source).VerifyDiagnostics( - // (9,14): error CS0060: Inconsistent accessibility: base type 'B' is less accessible than class 'C' - Diagnostic(ErrorCode.ERR_BadVisBaseClass, "C").WithArguments("C", "B").WithLocation(9, 14)); + // (9,14): error CS9338: Inconsistent accessibility: type 'A.B' is less accessible than class 'C' + Diagnostic(ErrorCode.ERR_BadVisBaseType, "C").WithArguments("C", "A.B").WithLocation(9, 14)); } [Fact] diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets b/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets index 50ad256227afd..c3a98f057cd1c 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets +++ b/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets @@ -71,6 +71,11 @@ $(NoWarn);2008 + + + $(NoWarn);8002 + + diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.VisualBasic.Core.targets b/src/Compilers/Core/MSBuildTask/Microsoft.VisualBasic.Core.targets index 768655d8e4f96..6b97be64b9bcd 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.VisualBasic.Core.targets +++ b/src/Compilers/Core/MSBuildTask/Microsoft.VisualBasic.Core.targets @@ -35,6 +35,11 @@ <_NoWarnings Condition="'$(WarningLevel)' == '1'">false + + + $(NoWarn);BC41997 + + $(IntermediateOutputPath)$(TargetName).compile.pdb diff --git a/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj b/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj index be45ea70de129..0fefe035a4eb6 100644 --- a/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj +++ b/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj @@ -40,6 +40,8 @@ + + diff --git a/src/Compilers/Core/MSBuildTaskTests/SdkIntegrationTests.cs b/src/Compilers/Core/MSBuildTaskTests/SdkIntegrationTests.cs new file mode 100644 index 0000000000000..760b9e7c966f2 --- /dev/null +++ b/src/Compilers/Core/MSBuildTaskTests/SdkIntegrationTests.cs @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; +using Xunit.Abstractions; +using Basic.CompilerLog.Util; + +namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests; + +public sealed class SdkIntegrationTests : IDisposable +{ + public const string NetCoreTfm = "net9.0"; + + public ITestOutputHelper TestOutputHelper { get; } + public TempRoot Temp { get; } + public TempDirectory ProjectDir { get; } + public ArtifactUploadUtil ArtifactUploadUtil { get; } + + public SdkIntegrationTests(ITestOutputHelper testOutputHelper) + { + Assert.NotNull(DotNetSdkTestBase.DotNetInstallDir); + TestOutputHelper = testOutputHelper; + Temp = new TempRoot(); + ProjectDir = Temp.CreateDirectory(); + ArtifactUploadUtil = new ArtifactUploadUtil(testOutputHelper); + } + + public void Dispose() + { + Temp.Dispose(); + ArtifactUploadUtil.Dispose(); + } + + public static string GetRoslynTargetsPath() + { + var p = typeof(SdkIntegrationTests).Assembly.Location!; + var dir = Path.GetDirectoryName(p)!; + var targets = Path.Combine(dir, "Microsoft.CSharp.Core.targets"); + Assert.True(File.Exists(targets)); + return dir; + } + + /// + /// Runs the build and returns the path to the binary log file + /// + private string RunBuild( + string projectFilePath, + string? additionalArguments = null, + IEnumerable>? additionalEnvironmentVars = null, + bool succeeds = true) + { + var workingDirectory = Path.GetDirectoryName(projectFilePath)!; + ArtifactUploadUtil.AddDirectory(workingDirectory); + + var args = new StringBuilder(); + args.Append("build /bl "); + args.Append($"/p:RoslynTargetsPath={GetRoslynTargetsPath()} "); + args.Append($"/p:RoslynTasksAssembly={typeof(Csc).Assembly.Location} "); + args.Append($"/p:RoslynCompilerType=Custom "); + if (additionalArguments is not null) + { + args.Append(additionalArguments); + } + + var result = ProcessUtilities.Run(DotNetSdkTestBase.DotNetExeName, args.ToString(), workingDirectory, additionalEnvironmentVars); + if (succeeds) + { + Assert.True(result.ExitCode == 0, $"MSBuild failed with exit code {result.ExitCode}: {result.Output}"); + } + else + { + Assert.False(result.ExitCode == 0, $"MSBuild failed with exit code {result.ExitCode}: {result.Output}"); + } + + return Path.Combine(workingDirectory, "msbuild.binlog"); + } + + private static List ReadCompilations(string binaryLogPath) + { + using var reader = BinaryLogReader.Create(binaryLogPath, BasicAnalyzerKind.None); + var list = new List(); + foreach (var compilerCall in reader.ReadAllCompilerCalls()) + { + var compilation = reader.ReadCompilationData(compilerCall).GetCompilationAfterGenerators(); + list.Add(compilation); + } + + return list; + } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void Console() + { + var projectFile = ProjectDir.CreateFile("console.csproj"); + projectFile.WriteAllText($""" + + + + Exe + {NetCoreTfm} + enable + enable + + + """); + + ProjectDir.CreateFile("hello.cs").WriteAllText(""" + Console.WriteLine("Hello, World!"); + """); + + var binlogPath = RunBuild(projectFile.Path); + var compilations = ReadCompilations(binlogPath); + Assert.Single(compilations); + Assert.True(compilations[0].SyntaxTrees.Any(x => Path.GetFileName(x.FilePath) == "hello.cs")); + ArtifactUploadUtil.SetSucceeded(); + } + + [ConditionalTheory(typeof(DotNetSdkAvailable))] + [InlineData(NetCoreTfm, true)] + [InlineData("net6.0", true)] + [InlineData("netstandard2.0", false)] + [InlineData("net472", false)] + public void StrongNameWarningCSharp(string tfm, bool expectStrongNameSuppression) + { + var projectFile = ProjectDir.CreateFile("console.csproj"); + projectFile.WriteAllText($""" + + + + Library + {tfm} + + + """); + + ProjectDir.CreateFile("hello.cs").WriteAllText(""" + class C { } + """); + + var binlogPath = RunBuild(projectFile.Path); + var compilation = ReadCompilations(binlogPath).Single(); + var options = compilation.Options; + if (expectStrongNameSuppression) + { + Assert.True(options.SpecificDiagnosticOptions.TryGetValue("CS8002", out ReportDiagnostic d)); + Assert.Equal(ReportDiagnostic.Suppress, d); + } + else + { + Assert.False(options.SpecificDiagnosticOptions.TryGetValue("CS8002", out _)); + } + + ArtifactUploadUtil.SetSucceeded(); + } + + [ConditionalTheory(typeof(DotNetSdkAvailable))] + [InlineData(NetCoreTfm, true)] + [InlineData("net6.0", true)] + [InlineData("netstandard2.0", false)] + [InlineData("net472", false)] + public void StrongNameWarningVisualBasic(string tfm, bool expectStrongNameSuppression) + { + var projectFile = ProjectDir.CreateFile("console.vbproj"); + projectFile.WriteAllText($""" + + + + Library + {tfm} + + + """); + + ProjectDir.CreateFile("hello.vb").WriteAllText(""" + Module M + End Module + """); + + var binlogPath = RunBuild(projectFile.Path); + var compilation = ReadCompilations(binlogPath).Single(); + var options = compilation.Options; + if (expectStrongNameSuppression) + { + Assert.True(options.SpecificDiagnosticOptions.TryGetValue("BC41997", out ReportDiagnostic d)); + Assert.Equal(ReportDiagnostic.Suppress, d); + } + else + { + Assert.False(options.SpecificDiagnosticOptions.TryGetValue("BC41997", out _)); + } + + ArtifactUploadUtil.SetSucceeded(); + } +} diff --git a/src/Compilers/Core/MSBuildTaskTests/TestUtilities/DotNetSdkTestBase.cs b/src/Compilers/Core/MSBuildTaskTests/TestUtilities/DotNetSdkTestBase.cs index 8d5b9d8768cec..61e4aca2c4cdc 100644 --- a/src/Compilers/Core/MSBuildTaskTests/TestUtilities/DotNetSdkTestBase.cs +++ b/src/Compilers/Core/MSBuildTaskTests/TestUtilities/DotNetSdkTestBase.cs @@ -16,18 +16,18 @@ namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests { - public abstract partial class DotNetSdkTestBase : TestBase + public sealed class DotNetSdkAvailable : ExecutionCondition { - public sealed class DotNetSdkAvailable : ExecutionCondition - { - public override bool ShouldSkip => s_dotnetSdkPath == null; - public override string SkipReason => "The location of dotnet SDK can't be determined (DOTNET_INSTALL_DIR environment variable is unset)"; - } + public override bool ShouldSkip => DotNetSdkTestBase.DotNetSdkPath == null; + public override string SkipReason => "The location of dotnet SDK can't be determined (DOTNET_INSTALL_DIR environment variable is unset)"; + } - private static readonly string s_dotnetExeName; - private static readonly string? s_dotnetInstallDir; - private static readonly string s_dotnetSdkVersion; - private static readonly string? s_dotnetSdkPath; + public abstract partial class DotNetSdkTestBase : TestBase + { + public static string DotNetExeName { get; } + public static string? DotNetInstallDir { get; } + public static string DotNetSdkVersion { get; } + public static string? DotNetSdkPath { get; } private static readonly string s_projectSource = @" @@ -65,12 +65,12 @@ private static string GetSdkPath(string dotnetInstallDir, string version) static DotNetSdkTestBase() { - s_dotnetExeName = "dotnet" + (Path.DirectorySeparatorChar == '/' ? "" : ".exe"); - s_dotnetSdkVersion = typeof(DotNetSdkTests).Assembly.GetCustomAttribute()?.Version + DotNetExeName = "dotnet" + (Path.DirectorySeparatorChar == '/' ? "" : ".exe"); + DotNetSdkVersion = typeof(DotNetSdkTests).Assembly.GetCustomAttribute()?.Version ?? throw new InvalidOperationException($"Couldn't find {nameof(DotNetSdkVersionAttribute)}"); static bool isMatchingDotNetInstance(string? dotnetDir) - => dotnetDir != null && File.Exists(Path.Combine(dotnetDir, s_dotnetExeName)) && Directory.Exists(GetSdkPath(dotnetDir, s_dotnetSdkVersion)); + => dotnetDir != null && File.Exists(Path.Combine(dotnetDir, DotNetExeName)) && Directory.Exists(GetSdkPath(dotnetDir, DotNetSdkVersion)); var dotnetInstallDir = Environment.GetEnvironmentVariable("DOTNET_INSTALL_DIR"); if (!isMatchingDotNetInstance(dotnetInstallDir)) @@ -80,8 +80,8 @@ static bool isMatchingDotNetInstance(string? dotnetDir) if (dotnetInstallDir != null) { - s_dotnetInstallDir = dotnetInstallDir; - s_dotnetSdkPath = GetSdkPath(dotnetInstallDir, s_dotnetSdkVersion); + DotNetInstallDir = dotnetInstallDir; + DotNetSdkPath = GetSdkPath(dotnetInstallDir, DotNetSdkVersion); } } @@ -145,12 +145,12 @@ private static void EmitTestHelperTargets( public DotNetSdkTestBase(ITestOutputHelper testOutputHelper) { - Assert.True(s_dotnetInstallDir is object, $"SDK not found. Use {nameof(ConditionalFactAttribute)}(typeof({nameof(DotNetSdkAvailable)})) to skip the test if the SDK is not found."); - Debug.Assert(s_dotnetInstallDir is object); + Assert.True(DotNetInstallDir is object, $"SDK not found. Use {nameof(ConditionalFactAttribute)}(typeof({nameof(DotNetSdkAvailable)})) to skip the test if the SDK is not found."); + Debug.Assert(DotNetInstallDir is object); - DotNetPath = Path.Combine(s_dotnetInstallDir, s_dotnetExeName); + DotNetPath = Path.Combine(DotNetInstallDir, DotNetExeName); var testBinDirectory = Path.GetDirectoryName(typeof(DotNetSdkTests).Assembly.Location) ?? string.Empty; - var sdksDir = Path.Combine(s_dotnetSdkPath ?? string.Empty, "Sdks"); + var sdksDir = Path.Combine(DotNetSdkPath ?? string.Empty, "Sdks"); TestOutputHelper = testOutputHelper; ProjectName = "test"; diff --git a/src/Compilers/Core/Portable/InternalUtilities/DocumentationCommentXmlNames.cs b/src/Compilers/Core/Portable/InternalUtilities/DocumentationCommentXmlNames.cs index 8eb9b7511ca80..d47d50c1e7fd7 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/DocumentationCommentXmlNames.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/DocumentationCommentXmlNames.cs @@ -11,12 +11,15 @@ namespace Roslyn.Utilities /// internal static class DocumentationCommentXmlNames { + public const string BElementName = "b"; public const string CElementName = "c"; public const string CodeElementName = "code"; public const string CompletionListElementName = "completionlist"; public const string DescriptionElementName = "description"; + public const string EmElementName = "em"; public const string ExampleElementName = "example"; public const string ExceptionElementName = "exception"; + public const string IElementName = "i"; public const string IncludeElementName = "include"; public const string InheritdocElementName = "inheritdoc"; public const string ItemElementName = "item"; @@ -32,9 +35,11 @@ internal static class DocumentationCommentXmlNames public const string ReturnsElementName = "returns"; public const string SeeElementName = "see"; public const string SeeAlsoElementName = "seealso"; + public const string StrongElementName = "strong"; public const string SummaryElementName = "summary"; public const string TermElementName = "term"; public const string ThreadSafetyElementName = "threadsafety"; + public const string TtElementName = "tt"; public const string TypeParameterElementName = "typeparam"; public const string TypeParameterReferenceElementName = "typeparamref"; public const string ValueElementName = "value"; diff --git a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs index 285c8f01a9ce5..d30bdf1682a41 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs @@ -46,8 +46,16 @@ internal IncrementalGeneratorInitializationContext( CatchAnalyzerExceptions = catchAnalyzerExceptions; } + /// + /// Gets a that can be used to create syntax-based input nodes for the incremental generator pipeline. + /// Use this to register callbacks that filter and transform syntax nodes in the compilation. + /// public SyntaxValueProvider SyntaxProvider => new(this, _syntaxInputBuilder, RegisterOutput, SyntaxHelper); + /// + /// Gets an that provides access to the being processed. + /// The value of this provider changes whenever the compilation changes (e.g., source files, references, or options are modified). + /// public IncrementalValueProvider CompilationProvider => new IncrementalValueProvider(SharedInputNodes.Compilation.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.Compilation), CatchAnalyzerExceptions); // Use a ReferenceEqualityComparer as we want to rerun this stage whenever the CompilationOptions changes at all @@ -58,27 +66,100 @@ internal IncrementalValueProvider CompilationOptionsProvider .WithComparer(ReferenceEqualityComparer.Instance) .WithTrackingName(WellKnownGeneratorInputs.CompilationOptions), CatchAnalyzerExceptions); + /// + /// Gets an that provides access to the for the compilation. + /// The value of this provider changes whenever parse options change (e.g., language version or preprocessor symbols). + /// public IncrementalValueProvider ParseOptionsProvider => new IncrementalValueProvider(SharedInputNodes.ParseOptions.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.ParseOptions), CatchAnalyzerExceptions); + /// + /// Gets an that provides access to all files included in the compilation. + /// Additional texts are typically non-code files (like .txt, .json, .xml) that can be used as input for source generation. + /// Each additional text that is added, removed, or modified will trigger a new value in the provider. + /// public IncrementalValuesProvider AdditionalTextsProvider => new IncrementalValuesProvider(SharedInputNodes.AdditionalTexts.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.AdditionalTexts), CatchAnalyzerExceptions); + /// + /// Gets an that provides access to the for the compilation. + /// This can be used to read .editorconfig settings and other analyzer configuration options. + /// public IncrementalValueProvider AnalyzerConfigOptionsProvider => new IncrementalValueProvider(SharedInputNodes.AnalyzerConfigOptions.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.AnalyzerConfigOptions), CatchAnalyzerExceptions); + /// + /// Gets an that provides access to all s in the compilation. + /// Each metadata reference (e.g., referenced assemblies) that is added, removed, or modified will trigger a new value in the provider. + /// public IncrementalValuesProvider MetadataReferencesProvider => new IncrementalValuesProvider(SharedInputNodes.MetadataReferences.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.MetadataReferences), CatchAnalyzerExceptions); + /// + /// Registers an output node that will produce source code to be added to the compilation. + /// The provided action will be invoked with the value from the provider whenever it changes. + /// + /// The type of the value provided by the source provider + /// An that provides the input value + /// An action that receives a and the input value, and can add source files or report diagnostics public void RegisterSourceOutput(IncrementalValueProvider source, Action action) => RegisterSourceOutput(source.Node, action, IncrementalGeneratorOutputKind.Source, _sourceExtension); + /// + /// Registers an output node that will produce source code to be added to the compilation. + /// The provided action will be invoked once for each value from the provider whenever they change. + /// + /// The type of each value provided by the source provider + /// An that provides input values + /// An action that receives a and an input value, and can add source files or report diagnostics public void RegisterSourceOutput(IncrementalValuesProvider source, Action action) => RegisterSourceOutput(source.Node, action, IncrementalGeneratorOutputKind.Source, _sourceExtension); + /// + /// Registers an output node that will produce implementation source code to be added to the compilation. + /// Implementation sources are treated differently from regular sources in some scenarios and may be excluded from certain compilation outputs. + /// The provided action will be invoked with the value from the provider whenever it changes. + /// + /// The type of the value provided by the source provider + /// An that provides the input value + /// An action that receives a and the input value, and can add source files or report diagnostics public void RegisterImplementationSourceOutput(IncrementalValueProvider source, Action action) => RegisterSourceOutput(source.Node, action, IncrementalGeneratorOutputKind.Implementation, _sourceExtension); + /// + /// Registers an output node that will produce implementation source code to be added to the compilation. + /// Implementation sources are treated differently from regular sources in some scenarios and may be excluded from certain compilation outputs. + /// The provided action will be invoked once for each value from the provider whenever they change. + /// + /// The type of each value provided by the source provider + /// An that provides input values + /// An action that receives a and an input value, and can add source files or report diagnostics public void RegisterImplementationSourceOutput(IncrementalValuesProvider source, Action action) => RegisterSourceOutput(source.Node, action, IncrementalGeneratorOutputKind.Implementation, _sourceExtension); + /// + /// Registers a callback that will be invoked once, before any other source generation occurs. + /// This is typically used to add source code that should be available for subsequent generation steps, such as attribute definitions. + /// Use to add the EmbeddedAttribute which marks generated types as internal to the current assembly. + /// + /// A callback that receives an and can add initial source files public void RegisterPostInitializationOutput(Action callback) => _outputNodes.Add(new PostInitOutputNode(callback.WrapUserAction(CatchAnalyzerExceptions), _embeddedAttributeDefinition)); + /// + /// Registers an output node that will produce host-specific outputs that are not added to the compilation. + /// Host outputs have no defined use and do not contribute to the final compilation. They are made available to the host + /// (i.e., the development environment or build system running the generator, such as Visual Studio, dotnet build, etc.) + /// via and it is up to the host to decide how to use them. + /// The provided action will be invoked with the value from the provider whenever it changes. + /// + /// The type of the value provided by the source provider + /// An that provides the input value + /// An action that receives a and the input value, and can add host-specific outputs [Experimental(RoslynExperiments.GeneratorHostOutputs, UrlFormat = RoslynExperiments.GeneratorHostOutputs_Url)] public void RegisterHostOutput(IncrementalValueProvider source, Action action) => source.Node.RegisterOutput(new HostOutputNode(source.Node, action.WrapUserAction(CatchAnalyzerExceptions))); + /// + /// Registers an output node that will produce host-specific outputs that are not added to the compilation. + /// Host outputs have no defined use and do not contribute to the final compilation. They are made available to the host + /// (i.e., the development environment or build system running the generator, such as Visual Studio, dotnet build, etc.) + /// via and it is up to the host to decide how to use them. + /// The provided action will be invoked once for each value from the provider whenever they change. + /// + /// The type of each value provided by the source provider + /// An that provides input values + /// An action that receives a and an input value, and can add host-specific outputs [Experimental(RoslynExperiments.GeneratorHostOutputs, UrlFormat = RoslynExperiments.GeneratorHostOutputs_Url)] public void RegisterHostOutput(IncrementalValuesProvider source, Action action) => source.Node.RegisterOutput(new HostOutputNode(source.Node, action.WrapUserAction(CatchAnalyzerExceptions))); diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/ValueSourceExtensions.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/ValueSourceExtensions.cs index 5e7b8cd583431..3f14dce468794 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/ValueSourceExtensions.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/ValueSourceExtensions.cs @@ -12,39 +12,154 @@ namespace Microsoft.CodeAnalysis { public static class IncrementalValueProviderExtensions { - // 1 => 1 transform + /// + /// Transforms an into a new by applying a transform function to the value. + /// This is a 1-to-1 transformation where each input value produces exactly one output value. + /// + /// The type of the input value + /// The type of the output value + /// The input provider + /// A function that transforms a into a + /// A new that provides the transformed value public static IncrementalValueProvider Select(this IncrementalValueProvider source, Func selector) => new IncrementalValueProvider(new TransformNode(source.Node, selector, wrapUserFunc: source.CatchAnalyzerExceptions), source.CatchAnalyzerExceptions); + /// + /// Transforms an into a new by applying a transform function to each value. + /// This is a 1-to-1 transformation where each input value produces exactly one output value. + /// + /// The type of each input value + /// The type of each output value + /// The input provider + /// A function that transforms each into a + /// A new that provides the transformed values public static IncrementalValuesProvider Select(this IncrementalValuesProvider source, Func selector) => new IncrementalValuesProvider(new TransformNode(source.Node, selector, wrapUserFunc: source.CatchAnalyzerExceptions), source.CatchAnalyzerExceptions); - // 1 => many (or none) transform + /// + /// Transforms an into a new by applying a transform function that returns zero or more results for the input value. + /// This is a 1-to-many transformation where each input value can produce zero, one, or multiple output values. + /// + /// The type of the input value + /// The type of each output value + /// The input provider + /// A function that transforms a into an + /// A new that provides the transformed values public static IncrementalValuesProvider SelectMany(this IncrementalValueProvider source, Func> selector) => new IncrementalValuesProvider(new TransformNode(source.Node, selector, wrapUserFunc: source.CatchAnalyzerExceptions), source.CatchAnalyzerExceptions); + /// + /// Transforms an into a new by applying a transform function that returns zero or more results for the input value. + /// This is a 1-to-many transformation where each input value can produce zero, one, or multiple output values. + /// + /// The type of the input value + /// The type of each output value + /// The input provider + /// A function that transforms a into an + /// A new that provides the transformed values public static IncrementalValuesProvider SelectMany(this IncrementalValueProvider source, Func> selector) => new IncrementalValuesProvider(new TransformNode(source.Node, selector.WrapUserFunctionAsImmutableArray(source.CatchAnalyzerExceptions)), source.CatchAnalyzerExceptions); + /// + /// Transforms an into a new by applying a transform function that returns zero or more results for each input value. + /// This is a many-to-many transformation where each input value can produce zero, one, or multiple output values. + /// + /// The type of each input value + /// The type of each output value + /// The input provider + /// A function that transforms each into an + /// A new that provides the transformed values public static IncrementalValuesProvider SelectMany(this IncrementalValuesProvider source, Func> selector) => new IncrementalValuesProvider(new TransformNode(source.Node, selector, wrapUserFunc: source.CatchAnalyzerExceptions), source.CatchAnalyzerExceptions); + /// + /// Transforms an into a new by applying a transform function that returns zero or more results for each input value. + /// This is a many-to-many transformation where each input value can produce zero, one, or multiple output values. + /// + /// The type of each input value + /// The type of each output value + /// The input provider + /// A function that transforms each into an + /// A new that provides the transformed values public static IncrementalValuesProvider SelectMany(this IncrementalValuesProvider source, Func> selector) => new IncrementalValuesProvider(new TransformNode(source.Node, selector.WrapUserFunctionAsImmutableArray(source.CatchAnalyzerExceptions)), source.CatchAnalyzerExceptions); + /// + /// Collects all values from an into a single containing an . + /// This is useful when you need to aggregate multiple values into a single collection to process them together. + /// + /// The type of each value in the input provider + /// The input provider with multiple values + /// A new that provides an containing all values public static IncrementalValueProvider> Collect(this IncrementalValuesProvider source) => new IncrementalValueProvider>(new BatchNode(source.Node), source.CatchAnalyzerExceptions); + /// + /// Combines an with an to create a new of tuples. + /// Each value from the left provider is paired with the single value from the right provider. + /// + /// The type of each value in the left provider + /// The type of the value in the right provider + /// The left provider with multiple values + /// The right provider with a single value + /// A new that provides tuples of (TLeft, TRight) public static IncrementalValuesProvider<(TLeft Left, TRight Right)> Combine(this IncrementalValuesProvider provider1, IncrementalValueProvider provider2) => new IncrementalValuesProvider<(TLeft, TRight)>(new CombineNode(provider1.Node, provider2.Node), provider1.CatchAnalyzerExceptions); + /// + /// Combines two s into a new of a tuple. + /// The single values from both providers are paired together. + /// + /// The type of the value in the left provider + /// The type of the value in the right provider + /// The left provider with a single value + /// The right provider with a single value + /// A new that provides a tuple of (TLeft, TRight) public static IncrementalValueProvider<(TLeft Left, TRight Right)> Combine(this IncrementalValueProvider provider1, IncrementalValueProvider provider2) => new IncrementalValueProvider<(TLeft, TRight)>(new CombineNode(provider1.Node, provider2.Node), provider1.CatchAnalyzerExceptions); - // helper for filtering + /// + /// Filters values from an based on a predicate, producing a new containing only values that satisfy the predicate. + /// + /// The type of each value + /// The input provider + /// A function that determines whether a value should be included in the output + /// A new that provides only values where the predicate returns true public static IncrementalValuesProvider Where(this IncrementalValuesProvider source, Func predicate) => source.SelectMany((item, _) => predicate(item) ? ImmutableArray.Create(item) : ImmutableArray.Empty); internal static IncrementalValuesProvider Where(this IncrementalValuesProvider source, Func predicate) => source.SelectMany((item, c) => predicate(item, c) ? ImmutableArray.Create(item) : ImmutableArray.Empty); - // custom comparer for given node + /// + /// Specifies a custom to use when comparing values from this provider for caching purposes. + /// By default, the generator infrastructure uses to determine if values have changed. + /// Use this method when you need custom equality logic, such as for complex objects or when you want to control when transformations are re-executed. + /// + /// The type of the value + /// The input provider + /// The custom equality comparer to use + /// A new that uses the specified comparer public static IncrementalValueProvider WithComparer(this IncrementalValueProvider source, IEqualityComparer comparer) => new IncrementalValueProvider(source.Node.WithComparer(comparer.WrapUserComparer(source.CatchAnalyzerExceptions)), source.CatchAnalyzerExceptions); + /// + /// Specifies a custom to use when comparing values from this provider for caching purposes. + /// By default, the generator infrastructure uses to determine if values have changed. + /// Use this method when you need custom equality logic, such as for complex objects or when you want to control when transformations are re-executed. + /// + /// The type of each value + /// The input provider + /// The custom equality comparer to use + /// A new that uses the specified comparer public static IncrementalValuesProvider WithComparer(this IncrementalValuesProvider source, IEqualityComparer comparer) => new IncrementalValuesProvider(source.Node.WithComparer(comparer.WrapUserComparer(source.CatchAnalyzerExceptions)), source.CatchAnalyzerExceptions); - // custom node name for incremental testing support + /// + /// Assigns a name to this provider step for tracking and debugging purposes. + /// This name can be used in testing and diagnostic scenarios to understand the execution pipeline. + /// + /// The type of the value + /// The input provider + /// The tracking name to assign + /// A new with the specified tracking name public static IncrementalValueProvider WithTrackingName(this IncrementalValueProvider source, string name) => new IncrementalValueProvider(source.Node.WithTrackingName(name), source.CatchAnalyzerExceptions); + /// + /// Assigns a name to this provider step for tracking and debugging purposes. + /// This name can be used in testing and diagnostic scenarios to understand the execution pipeline. + /// + /// The type of each value + /// The input provider + /// The tracking name to assign + /// A new with the specified tracking name public static IncrementalValuesProvider WithTrackingName(this IncrementalValuesProvider source, string name) => new IncrementalValuesProvider(source.Node.WithTrackingName(name), source.CatchAnalyzerExceptions); } } diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 55a66b31e3f44..5d2d639076d3a 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -26,7 +26,7 @@ public interface IMethodSymbol : ISymbol MethodKind MethodKind { get; } /// - /// Returns the arity of this method, or the number of type parameters it takes. + /// Returns the arity of this method. Arity is the number of type parameters a method declares. /// A non-generic method has zero arity. /// int Arity { get; } diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index c85130b736d2b..b06e179ffa986 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -56,7 +56,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Property ''' - ''' Returns the arity of this method, or the number of type parameters it takes. + ''' Returns the arity of this method. Arity is the number of type parameters a method declares. ''' A non-generic method has zero arity. ''' Public MustOverride ReadOnly Property Arity As Integer diff --git a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedDelegateMethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedDelegateMethodSymbol.vb index 1e3cdc7ac17a6..40b26294408b1 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedDelegateMethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/SynthesizedSymbols/SynthesizedDelegateMethodSymbol.vb @@ -59,7 +59,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Sub ''' - ''' Returns the arity of this method, or the number of type parameters it takes. + ''' Returns the arity of this method. Arity is the number of type parameters a method declares. ''' A non-generic method has zero arity. ''' Public Overrides ReadOnly Property Arity As Integer diff --git a/src/Compilers/VisualBasic/Portable/Syntax/Syntax.xml b/src/Compilers/VisualBasic/Portable/Syntax/Syntax.xml index 42f8db6444095..e39471be79ea5 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/Syntax.xml +++ b/src/Compilers/VisualBasic/Portable/Syntax/Syntax.xml @@ -1,6 +1,9 @@  + - $(NoWarn);NETSDK1138 - - <_MsbuildVersion>17.3.4 - - true + <_MsbuildVersion>17.11.31 diff --git a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs index 042bf7b41b698..07a5c3d170a70 100644 --- a/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -156,9 +157,7 @@ private DiagnosticReportingMode GetReportingModeForUnrecognizedProjects() public async Task LoadSolutionInfoAsync( string solutionFilePath, IProgress? progress = null, -#pragma warning disable IDE0060 // TODO: decide what to do with this unusued ILogger, since we can't reliabily use it if we're sending builds out of proc ILogger? msbuildLogger = null, -#pragma warning restore IDE0060 CancellationToken cancellationToken = default) { if (solutionFilePath == null) @@ -180,7 +179,11 @@ public async Task LoadSolutionInfoAsync( SetSolutionProperties(absoluteSolutionPath); } - var buildHostProcessManager = new BuildHostProcessManager(Properties, loggerFactory: _loggerFactory); + var binLogPathProvider = IsBinaryLogger(msbuildLogger, out var fileName) + ? new BinLogPathProvider(fileName) + : null; + + var buildHostProcessManager = new BuildHostProcessManager(Properties, binLogPathProvider, _loggerFactory); await using var _ = buildHostProcessManager.ConfigureAwait(false); var worker = new Worker( @@ -223,9 +226,7 @@ public async Task> LoadProjectInfoAsync( string projectFilePath, ProjectMap? projectMap = null, IProgress? progress = null, -#pragma warning disable IDE0060 // TODO: decide what to do with this unusued ILogger, since we can't reliabily use it if we're sending builds out of proc ILogger? msbuildLogger = null, -#pragma warning restore IDE0060 CancellationToken cancellationToken = default) { if (projectFilePath == null) @@ -241,7 +242,11 @@ public async Task> LoadProjectInfoAsync( onPathFailure: reportingMode, onLoaderFailure: reportingMode); - var buildHostProcessManager = new BuildHostProcessManager(Properties, loggerFactory: _loggerFactory); + var binLogPathProvider = IsBinaryLogger(msbuildLogger, out var fileName) + ? new BinLogPathProvider(fileName) + : null; + + var buildHostProcessManager = new BuildHostProcessManager(Properties, binLogPathProvider, _loggerFactory); await using var _ = buildHostProcessManager.ConfigureAwait(false); var worker = new Worker( @@ -260,4 +265,56 @@ public async Task> LoadProjectInfoAsync( return await worker.LoadAsync(cancellationToken).ConfigureAwait(false); } + + private static bool IsBinaryLogger([NotNullWhen(returnValue: true)] ILogger? logger, out string? fileName) + { + // We validate the type name to avoid taking a dependency on the Microsoft.Build package + // because it brings along additional dependencies and servicing requirements. + if (logger?.GetType().FullName != "Microsoft.Build.Logging.BinaryLogger") + { + fileName = null; + return false; + } + + // The logger.Parameters could contain more than just the filename, such as "ProjectImports" or "OmitInitialInfo". + // Attempt to get the parsed filname directly from the logger if possible. + var fileNameProperty = logger.GetType().GetProperty("FileName"); + fileName = fileNameProperty?.GetValue(logger) as string ?? logger.Parameters; + return true; + } + + internal sealed class BinLogPathProvider : IBinLogPathProvider + { + private const string DefaultFileName = "msbuild"; + private const string DefaultExtension = ".binlog"; + + private readonly string _directory; + private readonly string _filename; + private readonly string _extension; + private int _suffix = -1; + + public BinLogPathProvider(string? logFilePath) + { + logFilePath ??= DefaultFileName + DefaultExtension; + + _directory = Path.GetDirectoryName(logFilePath) ?? "."; + _filename = Path.GetFileNameWithoutExtension(logFilePath) is { Length: > 0 } fileName + ? fileName + : DefaultFileName; + _extension = Path.GetExtension(logFilePath) is { Length: > 0 } extension + ? extension + : DefaultExtension; + } + + public string? GetNewLogPath() + { + var suffix = Interlocked.Increment(ref _suffix); + + var newPath = suffix == 0 + ? Path.Combine(_directory, _filename + _extension) + : Path.Combine(_directory, $"{_filename}-{suffix}{_extension}"); + + return Path.GetFullPath(newPath); + } + } } diff --git a/src/Workspaces/MSBuild/Test/DefaultBinLogPathProviderTests.cs b/src/Workspaces/MSBuild/Test/DefaultBinLogPathProviderTests.cs new file mode 100644 index 0000000000000..6dd268476fa3b --- /dev/null +++ b/src/Workspaces/MSBuild/Test/DefaultBinLogPathProviderTests.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Xunit; +using static Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader; + +namespace Microsoft.CodeAnalysis.MSBuild.UnitTests; + +public sealed class DefaultBinLogPathProviderTests +{ + private const string DefaultFileName = "msbuild"; + private const string DefaultExtension = ".binlog"; + + private static string RelativeLogDirectory => $".{Path.DirectorySeparatorChar}logs"; + private static string LogDirectory => Path.GetFullPath(RelativeLogDirectory); + private static string LogFileName => "mylog"; + private static string LogExtension => ".mylog"; + + [Fact] + public void DefaultBinLogPathProvider_ExpandsRelativePath() + { + var logPath = Path.Combine(RelativeLogDirectory, LogFileName + LogExtension); + var provider = new BinLogPathProvider(logPath); + AssertUniquePaths(provider, LogDirectory, LogFileName, LogExtension); + } + + [Fact] + public void DefaultBinLogPathProvider_UsesDefaultExtension() + { + var logPath = Path.Combine(LogDirectory, LogFileName); + var provider = new BinLogPathProvider(logPath); + AssertUniquePaths(provider, LogDirectory, LogFileName, DefaultExtension); + } + + [Fact] + public void DefaultBinLogPathProvider_UsesDefaultFileName() + { + var provider = new BinLogPathProvider(LogDirectory + Path.DirectorySeparatorChar); + AssertUniquePaths(provider, LogDirectory, DefaultFileName, DefaultExtension); + } + + private static void AssertUniquePaths(BinLogPathProvider provider, string expectedDirectory, string expectedFilePrefix, string expectedExtension) + { + var newLogPaths = Enumerable.Range(0, 10) + .Select(_ => provider.GetNewLogPath()) + .ToImmutableHashSet(); + Assert.Equal(10, newLogPaths.Count); + + foreach (var newLogPath in newLogPaths) + { + var newLogDirectory = Path.GetDirectoryName(newLogPath); + var newLogFileName = Path.GetFileNameWithoutExtension(newLogPath); + var newLogExtension = Path.GetExtension(newLogPath); + + Assert.Equal(expectedDirectory, newLogDirectory); + Assert.StartsWith(expectedFilePrefix, newLogFileName); + Assert.Equal(expectedExtension, newLogExtension); + } + } +} diff --git a/src/Workspaces/MSBuild/Test/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj b/src/Workspaces/MSBuild/Test/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj index aecbc94604a02..28559303b82a4 100644 --- a/src/Workspaces/MSBuild/Test/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj +++ b/src/Workspaces/MSBuild/Test/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj @@ -12,6 +12,9 @@ + + + diff --git a/src/Workspaces/MSBuild/Test/NetCoreTests.cs b/src/Workspaces/MSBuild/Test/NetCoreTests.cs index be8a43e6214fb..571170f6eb392 100644 --- a/src/Workspaces/MSBuild/Test/NetCoreTests.cs +++ b/src/Workspaces/MSBuild/Test/NetCoreTests.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Build.Logging; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.UnitTests; @@ -95,6 +96,29 @@ public async Task TestOpenProject_NetCoreApp() Assert.Empty(diagnostics); } + [ConditionalFact(typeof(DotNetSdkMSBuildInstalled))] + [Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] + [Trait(Traits.Feature, Traits.Features.NetCore)] + public async Task TestOpenProject_BinaryLogger() + { + CreateFiles(GetNetCoreAppFiles()); + + var projectFilePath = GetSolutionFileName("Project.csproj"); + var projectDir = Path.GetDirectoryName(projectFilePath); + var binLogPath = Path.Combine(projectDir, "build.binlog"); + + DotNetRestore("Project.csproj"); + + using var workspace = CreateMSBuildWorkspace(); + var project = await workspace.OpenProjectAsync(projectFilePath, new BinaryLogger() { Parameters = binLogPath }); + + // The binarylog could have been given a suffix to avoid filename collisions when used by multiple buildhosts. + var buildLogPaths = Directory.EnumerateFiles(projectDir, "build*.binlog").ToImmutableArray(); + var buildLogPath = Assert.Single(buildLogPaths); + var buildLogInfo = new FileInfo(buildLogPath); + Assert.True(buildLogInfo.Length > 0); + } + [ConditionalFact(typeof(DotNetSdkMSBuildInstalled))] [Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] [Trait(Traits.Feature, Traits.Features.NetCore)] @@ -260,7 +284,7 @@ public async Task TestOpenProject_NetCoreMultiTFM_ExtensionWithConditionOnTFM() Assert.Equal(3, workspace.CurrentSolution.ProjectIds.Count); // Assert the TFM is accessible from project extensions. - // The test project extension sets the default namespace based on the TFM. + // The test project extension sets the default namespace based on the TFM. foreach (var project in workspace.CurrentSolution.Projects) { switch (project.Name) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index bf99178b68716..96af97ff2b68f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -62,6 +62,12 @@ public bool CanReplaceWithRValue(SemanticModel semanticModel, [NotNullWhen(true) foreach (var ancestor in token.GetAncestors()) { + // In a conversion declaration, you can have `public static implicit operator X` Being inside the type + // argument is a reference location, and not a token we want to think of as declaring the conversion + // operator. + if (ancestor is TypeArgumentListSyntax) + return null; + var symbol = semanticModel.GetDeclaredSymbol(ancestor, cancellationToken); if (symbol != null) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpDocumentationCommentService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpDocumentationCommentService.cs index 9fb0c2cd69e63..7c267b1ed6d38 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpDocumentationCommentService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpDocumentationCommentService.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.CSharp.LanguageService; -internal sealed class CSharpDocumentationCommentService : AbstractDocumentationCommentService< +internal sealed class CSharpDocumentationCommentService() : AbstractDocumentationCommentService< DocumentationCommentTriviaSyntax, XmlNodeSyntax, XmlAttributeSyntax, @@ -19,13 +19,8 @@ internal sealed class CSharpDocumentationCommentService : AbstractDocumentationC XmlEmptyElementSyntax, XmlCrefAttributeSyntax, XmlNameAttributeSyntax, - XmlTextAttributeSyntax> + XmlTextAttributeSyntax>(CSharpSyntaxFacts.Instance) { - private CSharpDocumentationCommentService() - : base(CSharpSyntaxFacts.Instance) - { - } - public static readonly IDocumentationCommentService Instance = new CSharpDocumentationCommentService(); protected override SyntaxList GetAttributes(XmlEmptyElementSyntax xmlEmpty) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 8de265504025a..b89f612deef4a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -195,6 +195,8 @@ + + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharSequenceSourceText.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharSequenceSourceText.cs new file mode 100644 index 0000000000000..8a785e980927f --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharSequenceSourceText.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; + +/// +/// Trivial implementation of a that directly maps over a . +/// +internal sealed class VirtualCharSequenceSourceText : SourceText +{ + private readonly ImmutableSegmentedList _virtualChars; + + public override Encoding? Encoding { get; } + + public VirtualCharSequenceSourceText(ImmutableSegmentedList virtualChars, Encoding? encoding) + { + _virtualChars = virtualChars; + Encoding = encoding; + } + + public override int Length => _virtualChars.Count; + + public override char this[int position] => _virtualChars[position]; + + public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) + { + for (int i = sourceIndex, n = sourceIndex + count; i < n; i++) + destination[destinationIndex + i] = this[i]; + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharUtilities.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharUtilities.cs new file mode 100644 index 0000000000000..7b0060db97624 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/VirtualChars/VirtualCharUtilities.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; + +internal static class VirtualCharUtilities +{ + public static TextSpan FromBounds(VirtualChar vc1, VirtualChar vc2) + => TextSpan.FromBounds(vc1.Span.Start, vc2.Span.End); + + /// + /// Takes a and returns the same characters from it, without any characters + /// corresponding to test markup (e.g. $$ and the like). Because the virtual chars contain their + /// original text span, these final virtual chars can be used both as the underlying source of a (which only cares about their value), as well as the way to then map + /// positions/spans within that to actual full virtual char spans in the original + /// document for classification. + /// + public static (ImmutableSegmentedList sourceCode, ImmutableArray markdownSpans) StripMarkupCharacters( + ArrayBuilder virtualChars, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var markdownSpans); + var builder = ImmutableSegmentedList.CreateBuilder(); + + var nestedAnonymousSpanCount = 0; + var nestedNamedSpanCount = 0; + + for (int i = 0, n = virtualChars.Count; i < n;) + { + var vc1 = virtualChars[i]; + var vc2 = i + 1 < n ? virtualChars[i + 1] : default; + + // These casts are safe because we disallowed virtual chars whose Value doesn't fit in a char in + // RegisterClassifications. + // + // TODO: this algorithm is not actually the one used in roslyn or the roslyn-sdk for parsing a + // markup file. for example it will get `[|]` wrong (as that depends on knowing if we're starting + // or ending an existing span). Fix this up to follow the actual algorithm we use. + switch ((vc1.Value, vc2.Value)) + { + case ('$', '$'): + markdownSpans.Add(FromBounds(vc1, vc2)); + i += 2; + continue; + case ('|', ']'): + nestedAnonymousSpanCount = Math.Max(0, nestedAnonymousSpanCount - 1); + markdownSpans.Add(FromBounds(vc1, vc2)); + i += 2; + continue; + case ('|', '}'): + markdownSpans.Add(FromBounds(vc1, vc2)); + nestedNamedSpanCount = Math.Max(0, nestedNamedSpanCount - 1); + i += 2; + continue; + + // We have a slight ambiguity with cases like these: + // + // [|] [|} + // + // Is it starting a new match, or ending an existing match. As a workaround, we special case + // these and consider it ending a match if we have something on the stack already. + + case ('[', '|'): + var vc3 = i + 2 < n ? virtualChars[i + 2] : default; + if ((vc3 == ']' && nestedAnonymousSpanCount > 0) || + (vc3 == '}' && nestedNamedSpanCount > 0)) + { + // not the start of a span, don't classify this '[' specially. + break; + } + + nestedAnonymousSpanCount++; + markdownSpans.Add(FromBounds(vc1, vc2)); + i += 2; + continue; + + case ('{', '|'): + if (TryConsumeNamedSpanStart(ref i, n)) + continue; + + // didn't find the colon. don't classify these specially. + break; + } + + // Nothing special, add character as is. + builder.Add(vc1); + i++; + } + + cancellationToken.ThrowIfCancellationRequested(); + return (builder.ToImmutable(), markdownSpans.ToImmutableAndClear()); + + bool TryConsumeNamedSpanStart(ref int i, int n) + { + var start = i; + var seekPoint = i; + while (seekPoint < n) + { + var colonChar = virtualChars[seekPoint]; + if (colonChar == ':') + { + markdownSpans.Add(FromBounds(virtualChars[start], colonChar)); + nestedNamedSpanCount++; + i = seekPoint + 1; + return true; + } + + seekPoint++; + } + + return false; + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Symbols/IMethodSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Symbols/IMethodSymbolExtensions.cs index df9edc61a3bda..e3a8d463de519 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Symbols/IMethodSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Extensions/Symbols/IMethodSymbolExtensions.cs @@ -123,4 +123,33 @@ public static bool IsAsyncReturningVoidTask(this IMethodSymbol method, Compilati return method.ReturnType.Equals(compilation.TaskType()) || method.ReturnType.HasAttribute(compilation.AsyncMethodBuilderAttribute()); } + + /// + /// Returns true if the method is a primary constructor. + /// Primary constructors are not implicitly declared and have their declaring syntax reference + /// on the type declaration itself (not a separate constructor declaration). + /// + public static bool IsPrimaryConstructor(this IMethodSymbol constructor) + { + if (constructor.IsImplicitlyDeclared) + return false; + + if (constructor.DeclaringSyntaxReferences is not [{ SyntaxTree: var constructorSyntaxTree, Span: var constructorSpan }]) + return false; + + // Primary constructors have their declaring syntax on the containing type's declaration + var containingType = constructor.ContainingType; + if (containingType.DeclaringSyntaxReferences.Length == 0) + return false; + + // Check if any of the containing type's syntax references match the constructor's syntax tree + // and are at the same location (same syntax node) + foreach (var typeRef in containingType.DeclaringSyntaxReferences) + { + if (typeRef.SyntaxTree == constructorSyntaxTree && typeRef.Span == constructorSpan) + return true; + } + + return false; + } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSymbolDeclarationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSymbolDeclarationService.cs index 25456e628a9da..e98f6923a1746 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSymbolDeclarationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSymbolDeclarationService.cs @@ -11,14 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp; [ExportLanguageService(typeof(ISymbolDeclarationService), LanguageNames.CSharp), Shared] -internal sealed class CSharpSymbolDeclarationService : ISymbolDeclarationService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpSymbolDeclarationService() : ISymbolDeclarationService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpSymbolDeclarationService() - { - } - public ImmutableArray GetDeclarations(ISymbol symbol) => symbol != null ? symbol.DeclaringSyntaxReferences diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs index 0e64984f4b6a9..92278a4753057 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSyntaxGeneratorInternal.cs @@ -59,6 +59,17 @@ public override SyntaxNode LocalDeclarationStatement(SyntaxNode? type, SyntaxTok public override SyntaxNode WithInitializer(SyntaxNode variableDeclarator, SyntaxNode initializer) => ((VariableDeclaratorSyntax)variableDeclarator).WithInitializer((EqualsValueClauseSyntax)initializer); + public override SyntaxNode WithPropertyInitializer(SyntaxNode propertyDeclaration, SyntaxNode initializer) + { + var property = (PropertyDeclarationSyntax)propertyDeclaration; + return property + .WithInitializer((EqualsValueClauseSyntax)initializer) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + } + + public override SyntaxNode EqualsValueClause(SyntaxNode value) + => EqualsValueClause(EqualsToken, value); + public override SyntaxNode EqualsValueClause(SyntaxToken operatorToken, SyntaxNode value) => SyntaxFactory.EqualsValueClause(operatorToken, (ExpressionSyntax)value); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs index 01f6344bf9fe6..f2543bdddf2a6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SyntaxGeneratorInternalExtensions/SyntaxGeneratorInternal.cs @@ -45,6 +45,12 @@ public SyntaxNode LocalDeclarationStatement(SyntaxToken name, SyntaxNode initial public abstract SyntaxNode WithInitializer(SyntaxNode variableDeclarator, SyntaxNode initializer); + /// + /// Adds an initializer to a property declaration. + /// + public abstract SyntaxNode WithPropertyInitializer(SyntaxNode propertyDeclaration, SyntaxNode initializer); + + public abstract SyntaxNode EqualsValueClause(SyntaxNode value); public abstract SyntaxNode EqualsValueClause(SyntaxToken operatorToken, SyntaxNode value); public abstract SyntaxToken Identifier(string identifier); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb index 9652189dc1b72..953f2ef09839a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSyntaxGeneratorInternal.vb @@ -66,6 +66,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return DirectCast(variableDeclarator, VariableDeclaratorSyntax).WithInitializer(DirectCast(initializer, EqualsValueSyntax)) End Function + Public Overrides Function WithPropertyInitializer(propertyDeclaration As SyntaxNode, initializer As SyntaxNode) As SyntaxNode + Throw New NotSupportedException("VB does not support primary constructors.") + End Function + + Public Overrides Function EqualsValueClause(value As SyntaxNode) As SyntaxNode + Return EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), value) + End Function + Public Overrides Function EqualsValueClause(operatorToken As SyntaxToken, value As SyntaxNode) As SyntaxNode Return SyntaxFactory.EqualsValue(operatorToken, DirectCast(value, ExpressionSyntax)) End Function diff --git a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb index 8f9ffd507349b..19f00774d175f 100644 --- a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb +++ b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb @@ -764,12 +764,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename ElseIf renamedSymbol.Kind = SymbolKind.Method Then conflicts.AddRange( - DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IMethodSymbol), trimOptionalParameters:=True) _ + DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IMethodSymbol), trimOptionalParameters:=True, distinguishRefKind:=False) _ .Select(Function(loc) reverseMappedLocations(loc))) ElseIf renamedSymbol.Kind = SymbolKind.Property Then conflicts.AddRange( - DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IPropertySymbol), trimOptionalParameters:=True) _ + DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IPropertySymbol), trimOptionalParameters:=True, distinguishRefKind:=False) _ .Select(Function(loc) reverseMappedLocations(loc))) AddConflictingParametersOfProperties( referencedSymbols.Concat(renameSymbol).Where(Function(sym) sym.Kind = SymbolKind.Property),