diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0288911f9e964d69d90ae36e938941cb3e27a5d6..c39e4f27f1bcb1fbf79569c0ae9d654023f7f0fa 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,9 +3,9 @@ - + https://github.com/dotnet/arcade - 9d34fd008e754e1ada35c8b6bc3694e7a90b4ed7 + b902fd6b6948e689a5128fa6d94dc7de13e6af84 diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 6662e4bc1ece65de8196a7603ae71b6c487dde28..a5a1e711d79c67feda4ff9725ae7df54abf851da 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -16,7 +16,7 @@ # condition: eq(variables['Agent.OS'], 'Windows_NT') # inputs: # filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 -# arguments: -ConfigFile ${Env:BUILD_SOURCESDIRECTORY}/NuGet.config -Password $Env:Token +# arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token # env: # Token: $(dn-bot-dnceng-artifact-feeds-rw) @@ -94,41 +94,48 @@ function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Password) { } } -try { - if (!(Test-Path $ConfigFile -PathType Leaf)) { - Write-PipelineTelemetryError -Category 'Build' -Message "Couldn't find the file NuGet config file: $ConfigFile" +if (!(Test-Path $ConfigFile -PathType Leaf)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +} + +if (!$Password) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Please supply a valid PAT' ExitWithExitCode 1 - } +} - # Load NuGet.config - $doc = New-Object System.Xml.XmlDocument - $filename = (Get-Item $ConfigFile).FullName - $doc.Load($filename) +# Load NuGet.config +$doc = New-Object System.Xml.XmlDocument +$filename = (Get-Item $ConfigFile).FullName +$doc.Load($filename) - # Get reference to or create one if none exist already - $sources = $doc.DocumentElement.SelectSingleNode("packageSources") - if ($sources -eq $null) { - $sources = $doc.CreateElement("packageSources") - $doc.DocumentElement.AppendChild($sources) | Out-Null - } +# Get reference to or create one if none exist already +$sources = $doc.DocumentElement.SelectSingleNode("packageSources") +if ($sources -eq $null) { + $sources = $doc.CreateElement("packageSources") + $doc.DocumentElement.AppendChild($sources) | Out-Null +} - # Looks for a node. Create it if none is found. - $creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") - if ($creds -eq $null) { - $creds = $doc.CreateElement("packageSourceCredentials") - $doc.DocumentElement.AppendChild($creds) | Out-Null - } +# Looks for a node. Create it if none is found. +$creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") +if ($creds -eq $null) { + $creds = $doc.CreateElement("packageSourceCredentials") + $doc.DocumentElement.AppendChild($creds) | Out-Null +} - # Insert credential nodes for Maestro's private feeds - InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Password $Password +# Insert credential nodes for Maestro's private feeds +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Password $Password +$dotnet3Source = $sources.SelectSingleNode("add[@key='dotnet3']") +if ($dotnet3Source -ne $null) { AddPackageSource -Sources $sources -SourceName "dotnet3-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password AddPackageSource -Sources $sources -SourceName "dotnet3-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3-internal-transport/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password - - $doc.Save($filename) } -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ - ExitWithExitCode 1 + +$dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") +if ($dotnet31Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username "dn-bot" -Password $Password } + +$doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index 55ad70e3663161702265b03025131fcd9fc664f7..7d6fef27fe495e2c3470b81c9d872e8f470f6c42 100644 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -17,7 +17,7 @@ # displayName: Setup Private Feeds Credentials # inputs: # filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh -# arguments: $BUILD_SOURCESDIRECTORY/NuGet.config $Token +# arguments: $(Build.SourcesDirectory)/NuGet.config $Token # condition: ne(variables['Agent.OS'], 'Windows_NT') # env: # Token: $(dn-bot-dnceng-artifact-feeds-rw) @@ -42,7 +42,12 @@ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ ! -f "$ConfigFile" ]; then - Write-PipelineTelemetryError -Category 'Build' -Message "Couldn't find the file NuGet config file: $ConfigFile" + Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +fi + +if [ -z "$CredToken" ]; then + Write-PipelineTelemetryError -category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Please supply a valid PAT" ExitWithExitCode 1 fi @@ -52,7 +57,7 @@ if [[ `uname -s` == "Darwin" ]]; then fi # Ensure there is a ... section. -grep -i "" $ConfigFile +grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding ... section." ConfigNodeHeader="" @@ -62,7 +67,7 @@ if [ "$?" != "0" ]; then fi # Ensure there is a ... section. -grep -i "" $ConfigFile +grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding ... section." @@ -72,37 +77,64 @@ if [ "$?" != "0" ]; then sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" NuGet.config fi -# Ensure dotnet3-internal and dotnet3-internal-transport is in the packageSources -grep -i "" $ConfigFile -if [ "$?" != "0" ]; then - echo "Adding dotnet3-internal to the packageSources." +PackageSources=() - PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" +# Ensure dotnet3-internal and dotnet3-internal-transport are in the packageSources if the public dotnet3 feeds are present +grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet3-internal to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3-internal') + + grep -i "" - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" NuGet.config + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3-internal-transport') fi -# Ensure dotnet3-internal and dotnet3-internal-transport is in the packageSources -grep -i "" $ConfigFile -if [ "$?" != "0" ]; then - echo "Adding dotnet3-internal-transport to the packageSources." +# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present +grep -i "" - PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal') + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet3.1-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" NuGet.config + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal-transport') fi # I want things split line by line PrevIFS=$IFS IFS=$'\n' -PackageSources=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') +PackageSources+="$IFS" +PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') IFS=$PrevIFS -PackageSources+=('dotnet3-internal') -PackageSources+=('dotnet3-internal-transport') - for FeedName in ${PackageSources[@]} ; do # Check if there is no existing credential for this FeedName grep -i "<$FeedName>" $ConfigFile @@ -112,6 +144,6 @@ for FeedName in ${PackageSources[@]} ; do PackageSourceCredentialsNodeFooter="" NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" - sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" NuGet.config + sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi done diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 0fd4d9c7f231655e048fa05b6d54116c618a9273..88814514d828b96adc8a82f9bf271fba80b95e76 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -62,6 +62,8 @@ function Print-Usage() { Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." } +. $PSScriptRoot\tools.ps1 + function InitializeCustomToolset { if (-not $restore) { return @@ -113,8 +115,6 @@ function Build { } try { - . $PSScriptRoot\tools.ps1 - if ($clean) { if (Test-Path $ArtifactsDir) { Remove-Item -Recurse -Force $ArtifactsDir @@ -122,12 +122,7 @@ try { } exit 0 } - - if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message 'Eng/common/tools.ps1 returned a non-zero exit code.' - ExitWithExitCode $LastExitCode - } - + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index f4409f0835acb2dd6edef9ba4cc88f44a4c0b81b..db830c00a6f8d895705454c3507171b2f0f94705 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -113,14 +113,16 @@ try { } $toolInstallationFailure = $true } else { - Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $errMsg + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg exit 1 } } } if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { - Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools bootstrap failed' + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' exit 1 } } diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 index ca180d03ba189ea72e8af0f2a10562cfe5c68660..f397e1c75d411eaa405647641acdb0f75fd4b1d1 100644 --- a/eng/common/native/install-tool.ps1 +++ b/eng/common/native/install-tool.ps1 @@ -105,7 +105,7 @@ try { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { - Write-Error "$ToolName was not found in $ToolFilePath." + Write-Host "$ToolName was not found in $ToolFilePath." exit 1 } diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 index e0656185886fbcaac183ef55ac6e20a2bc3f6e3d..2688c389bd96693d5b2059cee2ff34cda2f9ebb8 100644 --- a/eng/common/pipeline-logging-functions.ps1 +++ b/eng/common/pipeline-logging-functions.ps1 @@ -85,7 +85,7 @@ function Write-PipelineTaskError { [switch]$AsOutput, [bool]$IsMultiJobVariable=$true) - if(-Not (Test-Path variable:ci) -Or !$ci) { + if((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ 'variable' = $Name 'isSecret' = $Secret diff --git a/global.json b/global.json index 5820271a032f54aebe8ac7bbfb0bfcf8b7ec60de..25b5c5b2dd3a9229e71f9226204eb34f6ffb96a0 100644 --- a/global.json +++ b/global.json @@ -7,6 +7,6 @@ "xcopy-msbuild": "16.3.0-alpha" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.19602.4" + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.19603.17" } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 7319f4c701be6fff25f1a426eb96b844d8fbfa94..aafdce6837720aaca3f08c257f7dc392740c60ff 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -78,6 +78,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display); // error: operator can't be applied on dynamic and a type that is not convertible to dynamic: + left = BindToTypeForErrorRecovery(left); + right = BindToTypeForErrorRecovery(right); return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, LookupResultKind.Empty, CreateErrorType(), hasErrors: true); } @@ -90,6 +92,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, // be used here. // NOTE: no overload resolution candidates. + left = BindToTypeForErrorRecovery(left); + right = BindToTypeForErrorRecovery(right); return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, LookupResultKind.NotAVariable, CreateErrorType(), hasErrors: true); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 986369928f2a747dbccdc3a8c7551c2bd2362864..b7bc03906ad07b2079fae1333c52e9ae2ca040e4 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1865,14 +1865,14 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre if (initializerOpt != null) { - VisitObjectCreationInitializer(null, slot, initializerOpt); + VisitObjectCreationInitializer(containingSymbol: null, slot, initializerOpt, leftAnnotations: FlowAnalysisAnnotations.None); } SetResultType(node, TypeWithState.Create(type, resultState)); } #nullable restore - private void VisitObjectCreationInitializer(Symbol containingSymbol, int containingSlot, BoundExpression node) + private void VisitObjectCreationInitializer(Symbol containingSymbol, int containingSlot, BoundExpression node, FlowAnalysisAnnotations leftAnnotations) { TakeIncrementalSnapshot(node); switch (node) @@ -1913,7 +1913,7 @@ private void VisitObjectCreationInitializer(Symbol containingSymbol, int contain Debug.Assert((object)containingSymbol != null); if ((object)containingSymbol != null) { - var type = containingSymbol.GetTypeOrReturnType(); + var type = ApplyLValueAnnotations(containingSymbol.GetTypeOrReturnType(), leftAnnotations); TypeWithState resultType = VisitOptionalImplicitConversion(node, type, useLegacyWarnings: false, trackMembers: true, AssignmentKind.Assignment); TrackNullableStateForAssignment(node, type, containingSlot, resultType, MakeSlot(node)); } @@ -1951,7 +1951,7 @@ private void VisitObjectElementInitializer(int containingSlot, BoundAssignmentOp if ((object)symbol != null) { int slot = (containingSlot < 0) ? -1 : GetOrCreateSlot(symbol, containingSlot); - VisitObjectCreationInitializer(symbol, slot, node.Right); + VisitObjectCreationInitializer(symbol, slot, node.Right, GetLValueAnnotations(node.Left)); // https://github.com/dotnet/roslyn/issues/35040: Should likely be setting _resultType in VisitObjectCreationInitializer // and using that value instead of reconstructing here } @@ -6010,6 +6010,8 @@ private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr) BoundPropertyAccess property => getSetterAnnotations(property.PropertySymbol), BoundIndexerAccess indexer => getSetterAnnotations(indexer.Indexer), BoundFieldAccess field => getFieldAnnotations(field.FieldSymbol), + BoundObjectInitializerMember { MemberSymbol: PropertySymbol prop } => getSetterAnnotations(prop), + BoundObjectInitializerMember { MemberSymbol: FieldSymbol field } => getFieldAnnotations(field), _ => FlowAnalysisAnnotations.None }; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 992912bcebffbeefbef999de5164662fccc66049..c5517c4488e286edbf16fae3a56e13d000338210 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -28032,6 +28032,132 @@ static void M(C c) comp.VerifyDiagnostics(); } + [Fact, WorkItem(39966, "https://github.com/dotnet/roslyn/issues/39966")] + public void AllowNull_Property_InObjectInitializer() + { + var source = @" +using System.Diagnostics.CodeAnalysis; +public class C +{ + [AllowNull] public C P { get; set; } // 1 +} + +class Program +{ + static void M(C c1) + { + c1.P = null; + + new C + { + P = null + }; + } +}"; + + var comp = CreateNullableCompilation(new[] { source, AllowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (5,26): warning CS8618: Non-nullable property 'P' is uninitialized. Consider declaring the property as nullable. + // [AllowNull] public C P { get; set; } // 1 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P").WithArguments("property", "P").WithLocation(5, 26)); + } + + [Fact, WorkItem(39966, "https://github.com/dotnet/roslyn/issues/39966")] + public void AllowNull_Field_InObjectInitializer() + { + var source = @" +using System.Diagnostics.CodeAnalysis; +public class C +{ + [AllowNull] public C F; // 1 +} + +class Program +{ + static void M(C c1) + { + c1.F = null; + + new C + { + F = null + }; + } +}"; + + var comp = CreateNullableCompilation(new[] { source, AllowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (5,26): warning CS8618: Non-nullable field 'F' is uninitialized. Consider declaring the field as nullable. + // [AllowNull] public C F; // 1 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "F").WithArguments("field", "F").WithLocation(5, 26)); + } + + [Fact, WorkItem(39966, "https://github.com/dotnet/roslyn/issues/39966")] + public void DisallowNull_Property_InObjectInitializer() + { + var source = @" +using System.Diagnostics.CodeAnalysis; +public class C +{ + [DisallowNull] public C? P { get; set; } +} + +class Program +{ + static void M(C c1) + { + c1.P = null; // 1 + + new C + { + P = null // 2 + }; + } +}"; + + var comp = CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,16): warning CS8625: Cannot convert null literal to non-nullable reference type. + // c1.P = null; // 1 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(12, 16), + // (16,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // P = null // 2 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(16, 17)); + } + + [Fact, WorkItem(39966, "https://github.com/dotnet/roslyn/issues/39966")] + public void DisallowNull_Field_InObjectInitializer() + { + var source = @" +using System.Diagnostics.CodeAnalysis; +public class C +{ + [DisallowNull] public C? F; +} + +class Program +{ + static void M(C c1) + { + c1.F = null; // 1 + + new C + { + F = null // 2 + }; + } +}"; + + var comp = CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,16): warning CS8625: Cannot convert null literal to non-nullable reference type. + // c1.F = null; // 1 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(12, 16), + // (16,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // F = null // 2 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(16, 17)); + } + [Fact] public void AllowNull_Property_InDeconstructionAssignment() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index 5551c717805fe881f7863ae20528fe2e84db012a..49e88344e3bdb774c273be27aee9ed73cd8be6b0 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -10904,5 +10904,24 @@ static void Main() Diagnostic(ErrorCode.ERR_ConstantStringTooLong, "C1").WithLocation(28, 68) ); } + + [Fact, WorkItem(39975, "https://github.com/dotnet/roslyn/issues/39975")] + public void EnsureOperandsConvertedInErrorExpression_01() + { + string source = +@"class C +{ + static unsafe void M(dynamic d, int* p) + { + d += p; + } +} +"; + CreateCompilation(source, options: TestOptions.ReleaseDll.WithAllowUnsafe(true)).VerifyDiagnostics( + // (5,9): error CS0019: Operator '+=' cannot be applied to operands of type 'dynamic' and 'int*' + // d += p; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "d += p").WithArguments("+=", "dynamic", "int*").WithLocation(5, 9) + ); + } } }