From 4d0af36500e90653f5086866acccf2a73567aaa0 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 20 Jan 2023 16:19:29 -0800 Subject: [PATCH] Preserve changes that are still relevant after revert of #66311 (#66440) Related to #63221. --- .../CSharp/Portable/BoundTree/BoundNodes.xml | 2 +- .../CSharp/Portable/CodeGen/CodeGenerator.cs | 4 +- .../CSharp/Portable/CodeGen/EmitExpression.cs | 70 +++- .../CSharp/Portable/CodeGen/Optimizer.cs | 23 +- .../NullableWalker.PlaceholderLocal.cs | 10 +- .../LocalRewriter/LocalRewriter_Call.cs | 20 +- .../Portable/Lowering/SpillSequenceSpiller.cs | 51 +-- .../CSharp/Portable/Symbols/LocalSymbol.cs | 10 +- .../Symbols/Source/SourceLocalSymbol.cs | 10 +- .../Symbols/Synthesized/SynthesizedLocal.cs | 17 +- .../Synthesized/TypeSubstitutedLocalSymbol.cs | 19 +- .../Symbols/UpdatedContainingSymbolLocal.cs | 11 +- .../Test/Emit2/CodeGen/CodeGenCallTests.cs | 326 ++++++++++++++++-- .../Symbols/EELocalSymbolBase.cs | 10 +- 14 files changed, 483 insertions(+), 100 deletions(-) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 4e606d47e1df6..35aff8616f744 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1717,7 +1717,7 @@ diff --git a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs index 2fc345eb025a8..00281e39e2914 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs @@ -224,14 +224,14 @@ public void Generate( hasStackAlloc = _sawStackalloc; Debug.Assert(_asyncCatchHandlerOffset >= 0); - asyncCatchHandlerOffset = _builder.GetILOffsetFromMarker(_asyncCatchHandlerOffset); + asyncCatchHandlerOffset = _diagnostics.HasAnyErrors() ? -1 : _builder.GetILOffsetFromMarker(_asyncCatchHandlerOffset); ArrayBuilder yieldPoints = _asyncYieldPoints; ArrayBuilder resumePoints = _asyncResumePoints; Debug.Assert((yieldPoints == null) == (resumePoints == null)); - if (yieldPoints == null) + if (yieldPoints == null || _diagnostics.HasAnyErrors()) { asyncYieldPoints = ImmutableArray.Empty; asyncResumePoints = ImmutableArray.Empty; diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index f507d3dc9285c..f676195b93dbf 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -365,7 +365,11 @@ private void EmitComplexConditionalReceiver(BoundComplexConditionalReceiver expr EmitExpression(expression.ReferenceTypeReceiver, used); _builder.EmitBranch(ILOpCode.Br, doneLabel); - _builder.AdjustStack(-1); + + if (used) + { + _builder.AdjustStack(-1); + } _builder.MarkLabel(whenValueTypeLabel); EmitExpression(expression.ValueTypeReceiver, used); @@ -412,7 +416,8 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces ((TypeParameterSymbol)receiverType).EffectiveInterfacesNoUseSiteDiagnostics.IsEmpty) || // This could be a nullable value type, which must be copied in order to not mutate the original value LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) || (receiverType.IsReferenceType && receiverType.TypeKind == TypeKind.TypeParameter) || - (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)); + (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) || + (notConstrained && IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker.Analyze(expression)); // ===== RECEIVER if (nullCheckOnCopy) @@ -564,6 +569,64 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces } } + /// + /// We must use a temp when there is a chance that evaluation of the call arguments + /// could actually modify value of the reference type reciever. The call must use + /// the original (unmodified) receiver. + /// + private sealed class IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator + { + private readonly BoundLoweredConditionalAccess _conditionalAccess; + private bool? _result; + + private IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker(BoundLoweredConditionalAccess conditionalAccess) + { + _conditionalAccess = conditionalAccess; + } + + public static bool Analyze(BoundLoweredConditionalAccess conditionalAccess) + { + var walker = new IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker(conditionalAccess); + walker.Visit(conditionalAccess.WhenNotNull); + Debug.Assert(walker._result.HasValue); + return walker._result.GetValueOrDefault(); + } + + public override BoundNode Visit(BoundNode node) + { + if (_result.HasValue) + { + return null; + } + + return base.Visit(node); + } + + public override BoundNode VisitCall(BoundCall node) + { + if (node.ReceiverOpt is BoundConditionalReceiver { Id: var id } && id == _conditionalAccess.Id) + { + Debug.Assert(!_result.HasValue); + _result = !IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(node.Arguments); + return null; + } + + return base.VisitCall(node); + } + + public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node) + { + if (node.Id == _conditionalAccess.Id) + { + Debug.Assert(!_result.HasValue); + _result = false; + return null; + } + + return base.VisitConditionalReceiver(node); + } + } + private void EmitConditionalReceiver(BoundConditionalReceiver expression, bool used) { Debug.Assert(!expression.Type.IsValueType); @@ -1809,7 +1872,8 @@ internal static bool ReceiverIsKnownToReferToTempIfReferenceType(BoundExpression if (receiver is BoundLocal { LocalSymbol.IsKnownToReferToTempIfReferenceType: true } or - BoundComplexConditionalReceiver) + BoundComplexConditionalReceiver or + BoundConditionalReceiver { Type: { IsReferenceType: false, IsValueType: false } }) { return true; } diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index c24414d48b676..384581fa61947 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -1556,6 +1557,19 @@ public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditiona EnsureStackState(cookie); // implicit label here SetStackDepth(origStack); // alternative is evaluated with original stack + + var unwrappedSequence = node.ReferenceTypeReceiver; + + while (unwrappedSequence is BoundSequence sequence) + { + unwrappedSequence = sequence.Value; + } + + if (unwrappedSequence is BoundLocal { LocalSymbol: { } localSymbol }) + { + ShouldNotSchedule(localSymbol); + } + var referenceTypeReceiver = (BoundExpression)this.Visit(node.ReferenceTypeReceiver); EnsureStackState(cookie); // implicit label here @@ -2214,7 +2228,14 @@ internal override SyntaxNode ScopeDesignatorOpt get { return null; } } - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) { throw new NotImplementedException(); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs index 91680904b3ea3..75b434c276c6f 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; @@ -60,7 +61,14 @@ public override bool Equals(Symbol obj, TypeCompareKind compareKind) internal override ConstantValue GetConstantValue(SyntaxNode node, LocalSymbol inProgress, BindingDiagnosticBag diagnostics = null) => null; internal override ImmutableBindingDiagnostic GetConstantValueDiagnostics(BoundExpression boundInitValue) => ImmutableBindingDiagnostic.Empty; internal override SyntaxNode GetDeclaratorSyntax() => throw ExceptionUtilities.Unreachable(); - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) => throw ExceptionUtilities.Unreachable(); + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) => throw ExceptionUtilities.Unreachable(); internal override ScopedKind Scope => ScopedKind.None; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 1f624b66a01f7..bdabe974f18c4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -694,29 +694,23 @@ private void ReferToTempIfReferenceTypeReceiver(BoundLocal receiverTemp, ref Bou temps.Add(intermediateRef.LocalSymbol); extraRefInitialization = assignmentToTemp.Update(intermediateRef, assignmentToTemp.Right, assignmentToTemp.IsRef, assignmentToTemp.Type); - // If condition `(object)default(T) != null` is true at execution time, - // the T is a value type. And it is a reference type otherwise. - var isValueTypeCheck = _factory.ObjectNotEqual( - _factory.Convert(_factory.SpecialType(SpecialType.System_Object), _factory.Default(receiverType)), - _factory.Null(_factory.SpecialType(SpecialType.System_Object))); - // `receiverTemp` initialization is adjusted as follows: // If we are dealing with a value type, use value of the intermediate ref. // Otherwise, use an address of a temp where we store the underlying reference type instance. assignmentToTemp = assignmentToTemp.Update( assignmentToTemp.Left, - _factory.Conditional( - isValueTypeCheck, - intermediateRef, - _factory.Sequence(new BoundExpression[] { _factory.AssignmentExpression(cache, intermediateRef) }, cache), - receiverType, - isRef: true), +#pragma warning disable format + new BoundComplexConditionalReceiver(receiverTemp.Syntax, + intermediateRef, + _factory.Sequence(new BoundExpression[] { _factory.AssignmentExpression(cache, intermediateRef) }, cache), + receiverType) { WasCompilerGenerated = true }, +#pragma warning restore format assignmentToTemp.IsRef, assignmentToTemp.Type); // SpillSequenceSpiller should be able to recognize this node in order to handle its spilling. - Debug.Assert(SpillSequenceSpiller.IsComplexConditionalInitializationOfReceiverRef(assignmentToTemp, out _, out _, out _, out _, out _)); + Debug.Assert(SpillSequenceSpiller.IsComplexConditionalInitializationOfReceiverRef(assignmentToTemp, out _, out _, out _, out _)); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 2047188a2cbb3..af2827dfe042b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -356,15 +356,14 @@ private BoundExpression Spill( IsComplexConditionalInitializationOfReceiverRef( assignment, out LocalSymbol receiverRefLocal, - out BoundBinaryOperator isValueTypeCheck, - out BoundAssignmentOperator referenceTypeReceiverCloning, + out BoundComplexConditionalReceiver complexReceiver, out BoundLocal valueTypeReceiver, out BoundLocal referenceTypeReceiver)) { Debug.Assert(receiverRefLocal.IsKnownToReferToTempIfReferenceType); - builder.AddStatement(_F.If(_F.Not(isValueTypeCheck), _F.ExpressionStatement(referenceTypeReceiverCloning))); + builder.AddStatement(_F.ExpressionStatement(complexReceiver)); - _receiverSubstitution.Add(receiverRefLocal, _F.ComplexConditionalReceiver(valueTypeReceiver, referenceTypeReceiver)); + _receiverSubstitution.Add(receiverRefLocal, complexReceiver.Update(valueTypeReceiver, referenceTypeReceiver, complexReceiver.Type)); return null; } else @@ -458,8 +457,7 @@ private BoundExpression Spill( internal static bool IsComplexConditionalInitializationOfReceiverRef( BoundAssignmentOperator assignment, out LocalSymbol outReceiverRefLocal, - out BoundBinaryOperator outIsValueTypeCheck, - out BoundAssignmentOperator outReferenceTypeReceiverCloning, + out BoundComplexConditionalReceiver outComplexReceiver, out BoundLocal outValueTypeReceiver, out BoundLocal outReferenceTypeReceiver) { @@ -467,24 +465,10 @@ internal static bool IsComplexConditionalInitializationOfReceiverRef( { IsRef: true, Left: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.Ref } receiverRefLocal }, - Right: BoundConditionalOperator + Right: BoundComplexConditionalReceiver { - IsRef: true, - Condition: BoundBinaryOperator - { - OperatorKind: BinaryOperatorKind.ObjectNotEqual, - Left: BoundConversion - { - Conversion: { IsUserDefined: false }, - Operand: BoundDefaultExpression { Type: var typeOfDefault }, - Type.SpecialType: SpecialType.System_Object - }, - Right: BoundLiteral { ConstantValueOpt.IsNull: true, Type.SpecialType: SpecialType.System_Object } - } isValueTypeCheck, - - Consequence: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.Ref } } valueTypeReceiver, - - Alternative: BoundSequence + ValueTypeReceiver: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.Ref } } valueTypeReceiver, + ReferenceTypeReceiver: BoundSequence { Locals.IsEmpty: true, SideEffects: @@ -494,11 +478,11 @@ internal static bool IsComplexConditionalInitializationOfReceiverRef( IsRef: false, Left: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.None } referenceTypeClone }, Right: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.Ref } originalReceiverReference } - } referenceTypeReceiverCloning + } ], Value: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.None } } referenceTypeReceiver } - } + } complexReceiver, } && (object)referenceTypeClone == referenceTypeReceiver.LocalSymbol && (object)originalReceiverReference == valueTypeReceiver.LocalSymbol @@ -507,22 +491,19 @@ internal static bool IsComplexConditionalInitializationOfReceiverRef( && receiverRefLocal.Type.IsTypeParameter() && !receiverRefLocal.Type.IsReferenceType && !receiverRefLocal.Type.IsValueType - && typeOfDefault.Equals(receiverRefLocal.Type, TypeCompareKind.AllIgnoreOptions) && valueTypeReceiver.Type.Equals(receiverRefLocal.Type, TypeCompareKind.AllIgnoreOptions) && referenceTypeReceiver.Type.Equals(receiverRefLocal.Type, TypeCompareKind.AllIgnoreOptions) ) { outReceiverRefLocal = receiverRefLocal; - outIsValueTypeCheck = isValueTypeCheck; - outReferenceTypeReceiverCloning = referenceTypeReceiverCloning; + outComplexReceiver = complexReceiver; outValueTypeReceiver = valueTypeReceiver; outReferenceTypeReceiver = referenceTypeReceiver; return true; } outReceiverRefLocal = null; - outIsValueTypeCheck = null; - outReferenceTypeReceiverCloning = null; + outComplexReceiver = null; outValueTypeReceiver = null; outReferenceTypeReceiver = null; return false; @@ -998,17 +979,15 @@ public override BoundNode VisitCall(BoundCall node) // reference to the stack. So, for a class we need to emit a reference to a temporary // location, rather than to the original location - // If condition `(object)default(T) != null` is true at execution time, - // the T is a value type. And it is a reference type otherwise. - var isValueTypeCheck = _F.ObjectNotEqual( - _F.Convert(_F.SpecialType(SpecialType.System_Object), _F.Default(receiverType)), - _F.Null(_F.SpecialType(SpecialType.System_Object))); + var save_Syntax = _F.Syntax; + _F.Syntax = node.Syntax; var cache = _F.Local(_F.SynthesizedLocal(receiverType)); receiverBuilder.AddLocal(cache.LocalSymbol); - receiverBuilder.AddStatement(_F.If(_F.Not(isValueTypeCheck), _F.Assignment(cache, receiver))); + receiverBuilder.AddStatement(_F.ExpressionStatement(new BoundComplexConditionalReceiver(node.Syntax, cache, _F.Sequence(new[] { _F.AssignmentExpression(cache, receiver) }, cache), receiverType) { WasCompilerGenerated = true })); receiver = _F.ComplexConditionalReceiver(receiver, cache); + _F.Syntax = save_Syntax; } receiverBuilder.Include(builder); diff --git a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs index 393aeeb4e46c0..6b245b6162c3b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Symbols; @@ -37,7 +38,14 @@ internal abstract SynthesizedLocalKind SynthesizedKind /// internal abstract SyntaxNode ScopeDesignatorOpt { get; } - internal abstract LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax); + internal abstract LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ); internal abstract bool IsImportedFromMetadata { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index d9c16fc5e7e90..ccbacc8bf13bd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; @@ -229,7 +230,14 @@ internal override SynthesizedLocalKind SynthesizedKind get { return SynthesizedLocalKind.UserDefined; } } - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) { throw ExceptionUtilities.Unreachable(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs index dcb449f2e3030..604f3b675b906 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs @@ -70,7 +70,14 @@ public SyntaxNode SyntaxOpt get { return _syntaxOpt; } } - internal sealed override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) + internal sealed override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) { return new SynthesizedLocal( _containingMethodOpt, @@ -79,7 +86,13 @@ internal sealed override LocalSymbol WithSynthesizedLocalKindAndSyntax(Synthesiz syntax, _isPinned, _isKnownToReferToTempIfReferenceType, - _refKind); + _refKind +#if DEBUG + , + createdAtLineNumber, + createdAtFilePath +#endif + ); } public sealed override RefKind RefKind diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs index f395f4d1cf4da..a0cd6788629cd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.CompilerServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -119,11 +120,25 @@ internal override ImmutableBindingDiagnostic GetConstantValueDia return _originalVariable.GetConstantValueDiagnostics(boundInitValue); } - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) { var origSynthesized = (SynthesizedLocal)_originalVariable; return new TypeSubstitutedLocalSymbol( - origSynthesized.WithSynthesizedLocalKindAndSyntax(kind, syntax), + origSynthesized.WithSynthesizedLocalKindAndSyntax( + kind, syntax +#if DEBUG + , + createdAtLineNumber, + createdAtFilePath +#endif + ), _type, _containingSymbol ); diff --git a/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs b/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs index 34d0959712354..dc1292c702d08 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.CompilerServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -95,8 +96,14 @@ internal override ImmutableBindingDiagnostic GetConstantValueDia _underlyingLocal.GetConstantValueDiagnostics(boundInitValue); internal override SyntaxNode GetDeclaratorSyntax() => _underlyingLocal.GetDeclaratorSyntax(); - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) => - throw ExceptionUtilities.Unreachable(); + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string? createdAtFilePath = null +#endif + ) => throw ExceptionUtilities.Unreachable(); #endregion } } diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs index 4156bee331030..e32211ab4e9f9 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs @@ -1912,26 +1912,28 @@ .maxstack 2 verifier.VerifyIL("Program.Call2", @" { - // Code size 53 (0x35) + // Code size 55 (0x37) .maxstack 2 .locals init (T V_0) - IL_0000: ldarg.0 - IL_0001: box ""T"" - IL_0006: brfalse.s IL_0034 - IL_0008: ldarga.s V_0 - IL_000a: ldloca.s V_0 - IL_000c: initobj ""T"" - IL_0012: ldloc.0 - IL_0013: box ""T"" - IL_0018: brtrue.s IL_0022 - IL_001a: ldobj ""T"" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: ldarga.s V_0 - IL_0024: call ""int Program.GetOffset(ref T)"" - IL_0029: constrained. ""T"" - IL_002f: callvirt ""void IMoveable.GetName(int)"" - IL_0034: ret + IL_0000: ldarga.s V_0 + IL_0002: ldloca.s V_0 + IL_0004: initobj ""T"" + IL_000a: ldloc.0 + IL_000b: box ""T"" + IL_0010: brtrue.s IL_0024 + IL_0012: ldobj ""T"" + IL_0017: stloc.0 + IL_0018: ldloca.s V_0 + IL_001a: ldloc.0 + IL_001b: box ""T"" + IL_0020: brtrue.s IL_0024 + IL_0022: pop + IL_0023: ret + IL_0024: ldarga.s V_0 + IL_0026: call ""int Program.GetOffset(ref T)"" + IL_002b: constrained. ""T"" + IL_0031: callvirt ""void IMoveable.GetName(int)"" + IL_0036: ret } "); } @@ -2064,10 +2066,9 @@ .maxstack 2 verifier.VerifyIL("Program.Call2", @" { - // Code size 77 (0x4d) + // Code size 53 (0x35) .maxstack 2 - .locals init (T V_0, - T V_1) + .locals init (T V_0) IL_0000: ldarg.0 IL_0001: ldloca.s V_0 IL_0003: initobj ""T"" @@ -2082,19 +2083,11 @@ .locals init (T V_0, IL_001f: brtrue.s IL_0023 IL_0021: pop IL_0022: ret - IL_0023: ldloca.s V_1 - IL_0025: initobj ""T"" - IL_002b: ldloc.1 - IL_002c: box ""T"" - IL_0031: brtrue.s IL_003b - IL_0033: ldobj ""T"" - IL_0038: stloc.1 - IL_0039: ldloca.s V_1 - IL_003b: ldarg.0 - IL_003c: call ""int Program.GetOffset(ref T)"" - IL_0041: constrained. ""T"" - IL_0047: callvirt ""void IMoveable.GetName(int)"" - IL_004c: ret + IL_0023: ldarg.0 + IL_0024: call ""int Program.GetOffset(ref T)"" + IL_0029: constrained. ""T"" + IL_002f: callvirt ""void IMoveable.GetName(int)"" + IL_0034: ret } "); } @@ -30623,6 +30616,271 @@ .locals init (int V_0, IL_00ea: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" IL_00ef: ret } +"); + } + + [Fact] + [WorkItem(66162, "https://github.com/dotnet/roslyn/issues/66162")] + public void GenericTypeParameterAsReceiver_Call_Nullable() + { + var source = @" +using System; + +#pragma warning disable CS0659 // 'Item' overrides Object.Equals(object o) but does not override Object.GetHashCode() + +struct Item +{ + public int Count; + + public override bool Equals(object obj) + { + Console.WriteLine(""Position Equals for item '{0}'"", Count); + return base.Equals(obj); + } +} + +class Program +{ + static void Main() + { + Item? item1 = new Item {Count = 1}; + Call1(item1); + Item? item2 = new Item {Count = 2}; + Call2(ref item2); + } + + static void Call1(T item) + { + item.Equals(GetOffset(ref item)); + } + + static void Call2(ref T item) + { + item.Equals(GetOffset(ref item)); + } + + static int value = 0; + static int GetOffset(ref T item) + { + item = (T)(object)new Item {Count = --value}; + return 0; + } +} +"; + // The output doesn't match the expectation, see https://github.com/dotnet/roslyn/issues/66162 + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: @" +Position Equals for item '1' +Position Equals for item '2' +").VerifyDiagnostics(); + + verifier.VerifyIL("Program.Call1", +@" +{ + // Code size 51 (0x33) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarga.s V_0 + IL_0002: ldloca.s V_0 + IL_0004: initobj ""T"" + IL_000a: ldloc.0 + IL_000b: box ""T"" + IL_0010: brtrue.s IL_001a + IL_0012: ldobj ""T"" + IL_0017: stloc.0 + IL_0018: ldloca.s V_0 + IL_001a: ldarga.s V_0 + IL_001c: call ""int Program.GetOffset(ref T)"" + IL_0021: box ""int"" + IL_0026: constrained. ""T"" + IL_002c: callvirt ""bool object.Equals(object)"" + IL_0031: pop + IL_0032: ret +} +"); + + verifier.VerifyIL("Program.Call2", +@" +{ + // Code size 49 (0x31) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: ldloca.s V_0 + IL_0003: initobj ""T"" + IL_0009: ldloc.0 + IL_000a: box ""T"" + IL_000f: brtrue.s IL_0019 + IL_0011: ldobj ""T"" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: ldarg.0 + IL_001a: call ""int Program.GetOffset(ref T)"" + IL_001f: box ""int"" + IL_0024: constrained. ""T"" + IL_002a: callvirt ""bool object.Equals(object)"" + IL_002f: pop + IL_0030: ret +} +"); + } + + [Fact] + [WorkItem(66162, "https://github.com/dotnet/roslyn/issues/66162")] + public void GenericTypeParameterAsReceiver_Call_Nullable_Async() + { + var source = @" +using System; +using System.Threading.Tasks; + +#pragma warning disable CS0659 // 'Item' overrides Object.Equals(object o) but does not override Object.GetHashCode() + +struct Item +{ + public int Count; + + public override bool Equals(object obj) + { + Console.WriteLine(""Position Equals for item '{0}'"", Count); + return base.Equals(obj); + } +} + +class Program +{ + static async Task Main() + { + Item? item1 = new Item {Count = 1}; + await Call1(item1); + } + + static async Task Call1(T item) + { + item.Equals(await GetOffsetAsync(GetOffset(ref item))); + } + + static int value = 0; + static int GetOffset(ref T item) + { + item = (T)(object)new Item {Count = --value}; + return 0; + } + +#pragma warning disable CS1998 // This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + static async Task GetOffsetAsync(int i) + { + return i; + } +} +"; + + // The output doesn't match the expectation, see https://github.com/dotnet/roslyn/issues/66162 + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: @" +Position Equals for item '1' +").VerifyDiagnostics(); + + verifier.VerifyIL("Program.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", +@" +{ + // Code size 247 (0xf7) + .maxstack 3 + .locals init (int V_0, + int V_1, + T V_2, + System.Runtime.CompilerServices.TaskAwaiter V_3, + System.Exception V_4) + IL_0000: ldarg.0 + IL_0001: ldfld ""int Program.d__1.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0068 + IL_000a: ldloca.s V_2 + IL_000c: initobj ""T"" + IL_0012: ldloc.2 + IL_0013: box ""T"" + IL_0018: brtrue.s IL_0026 + IL_001a: ldarg.0 + IL_001b: ldarg.0 + IL_001c: ldfld ""T Program.d__1.item"" + IL_0021: stfld ""T Program.d__1.<>7__wrap1"" + IL_0026: ldarg.0 + IL_0027: ldflda ""T Program.d__1.item"" + IL_002c: call ""int Program.GetOffset(ref T)"" + IL_0031: call ""System.Threading.Tasks.Task Program.GetOffsetAsync(int)"" + IL_0036: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" + IL_003b: stloc.3 + IL_003c: ldloca.s V_3 + IL_003e: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" + IL_0043: brtrue.s IL_0084 + IL_0045: ldarg.0 + IL_0046: ldc.i4.0 + IL_0047: dup + IL_0048: stloc.0 + IL_0049: stfld ""int Program.d__1.<>1__state"" + IL_004e: ldarg.0 + IL_004f: ldloc.3 + IL_0050: stfld ""System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1"" + IL_0055: ldarg.0 + IL_0056: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__1.<>t__builder"" + IL_005b: ldloca.s V_3 + IL_005d: ldarg.0 + IL_005e: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Program.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Program.d__1)"" + IL_0063: leave IL_00f6 + IL_0068: ldarg.0 + IL_0069: ldfld ""System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1"" + IL_006e: stloc.3 + IL_006f: ldarg.0 + IL_0070: ldflda ""System.Runtime.CompilerServices.TaskAwaiter Program.d__1.<>u__1"" + IL_0075: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" + IL_007b: ldarg.0 + IL_007c: ldc.i4.m1 + IL_007d: dup + IL_007e: stloc.0 + IL_007f: stfld ""int Program.d__1.<>1__state"" + IL_0084: ldloca.s V_3 + IL_0086: call ""int System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" + IL_008b: stloc.1 + IL_008c: ldloca.s V_2 + IL_008e: initobj ""T"" + IL_0094: ldloc.2 + IL_0095: box ""T"" + IL_009a: brtrue.s IL_00a4 + IL_009c: ldarg.0 + IL_009d: ldflda ""T Program.d__1.<>7__wrap1"" + IL_00a2: br.s IL_00aa + IL_00a4: ldarg.0 + IL_00a5: ldflda ""T Program.d__1.item"" + IL_00aa: ldloc.1 + IL_00ab: box ""int"" + IL_00b0: constrained. ""T"" + IL_00b6: callvirt ""bool object.Equals(object)"" + IL_00bb: pop + IL_00bc: ldarg.0 + IL_00bd: ldflda ""T Program.d__1.<>7__wrap1"" + IL_00c2: initobj ""T"" + IL_00c8: leave.s IL_00e3 + } + catch System.Exception + { + IL_00ca: stloc.s V_4 + IL_00cc: ldarg.0 + IL_00cd: ldc.i4.s -2 + IL_00cf: stfld ""int Program.d__1.<>1__state"" + IL_00d4: ldarg.0 + IL_00d5: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__1.<>t__builder"" + IL_00da: ldloc.s V_4 + IL_00dc: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00e1: leave.s IL_00f6 + } + IL_00e3: ldarg.0 + IL_00e4: ldc.i4.s -2 + IL_00e6: stfld ""int Program.d__1.<>1__state"" + IL_00eb: ldarg.0 + IL_00ec: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__1.<>t__builder"" + IL_00f1: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_00f6: ret +} "); } } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs index ff60fdb56cdb4..992deaa441e9b 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; @@ -52,7 +53,14 @@ internal override SyntaxNode ScopeDesignatorOpt get { return null; } } - internal sealed override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) + internal sealed override LocalSymbol WithSynthesizedLocalKindAndSyntax( + SynthesizedLocalKind kind, SyntaxNode syntax +#if DEBUG + , + [CallerLineNumber] int createdAtLineNumber = 0, + [CallerFilePath] string createdAtFilePath = null +#endif + ) { throw ExceptionUtilities.Unreachable(); }