diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 1bf76dd56b127..189ddfa152e36 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -4389,16 +4389,33 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext localScopeDe if (compound.Operator.Method is { } compoundMethod) { - return GetInvocationEscapeScope( - MethodInfo.Create(compoundMethod), - receiver: null, - receiverIsSubjectToCloning: ThreeState.Unknown, - compoundMethod.Parameters, - argsOpt: [compound.Left, compound.Right], - argRefKindsOpt: default, - argsToParamsOpt: default, - localScopeDepth: localScopeDepth, - isRefEscape: false); + if (compoundMethod.IsStatic) + { + return GetInvocationEscapeScope( + MethodInfo.Create(compoundMethod), + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + compoundMethod.Parameters, + argsOpt: [compound.Left, compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + localScopeDepth: localScopeDepth, + isRefEscape: false); + } + else + { + // PROTOTYPE: Follow-up and test this code path + return GetInvocationEscapeScope( + MethodInfo.Create(compoundMethod), + receiver: compound.Left, + receiverIsSubjectToCloning: ThreeState.False, + compoundMethod.Parameters, + argsOpt: [compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + localScopeDepth: localScopeDepth, + isRefEscape: false); + } } return GetValEscape(compound.Left, localScopeDepth) @@ -5176,20 +5193,41 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext if (compound.Operator.Method is { } compoundMethod) { - return CheckInvocationEscape( - compound.Syntax, - MethodInfo.Create(compoundMethod), - receiver: null, - receiverIsSubjectToCloning: ThreeState.Unknown, - compoundMethod.Parameters, - argsOpt: [compound.Left, compound.Right], - argRefKindsOpt: default, - argsToParamsOpt: default, - checkingReceiver: checkingReceiver, - escapeFrom: escapeFrom, - escapeTo: escapeTo, - diagnostics, - isRefEscape: false); + if (compoundMethod.IsStatic) + { + return CheckInvocationEscape( + compound.Syntax, + MethodInfo.Create(compoundMethod), + receiver: null, + receiverIsSubjectToCloning: ThreeState.Unknown, + compoundMethod.Parameters, + argsOpt: [compound.Left, compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } + else + { + // PROTOTYPE: Follow-up and test this code path + return CheckInvocationEscape( + compound.Syntax, + MethodInfo.Create(compoundMethod), + receiver: compound.Left, + receiverIsSubjectToCloning: ThreeState.Unknown, + compoundMethod.Parameters, + argsOpt: [compound.Right], + argRefKindsOpt: default, + argsToParamsOpt: default, + checkingReceiver: checkingReceiver, + escapeFrom: escapeFrom, + escapeTo: escapeTo, + diagnostics, + isRefEscape: false); + } } return CheckValEscape(compound.Left.Syntax, compound.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) && diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 4487f8d46fc1d..f47042ebfdb1f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -19,6 +19,8 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class Binder { +#nullable enable + private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, BindingDiagnosticBag diagnostics) { node.Left.CheckDeconstructionCompatibleArgument(diagnostics); @@ -60,6 +62,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, if (IsLegalDynamicOperand(right) && IsLegalDynamicOperand(left) && kind != BinaryOperatorKind.UnsignedRightShift) { left = BindToNaturalType(left, diagnostics); + Debug.Assert(left.Type is { }); + right = BindToNaturalType(right, diagnostics); var placeholder = new BoundValuePlaceholder(right.Syntax, left.HasDynamicType() ? left.Type : right.Type).MakeCompilerGenerated(); var finalDynamicConversion = this.Compilation.Conversions.ClassifyConversionFromExpression(placeholder, left.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); @@ -110,6 +114,13 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, LookupResultKind.NotAVariable, CreateErrorType(), hasErrors: true); } + // Try an in-place user-defined operator + BoundCompoundAssignmentOperator? inPlaceResult = tryApplyUserDefinedInstanceOperator(node, node.OperatorToken, kind, left, right, diagnostics); + if (inPlaceResult is not null) + { + return inPlaceResult; + } + // A compound operator, say, x |= y, is bound as x = (X)( ((T)x) | ((T)y) ). We must determine // the binary operator kind, the type conversions from each side to the types expected by // the operator, and the type conversion from the return type of the operand to the left hand side. @@ -196,10 +207,11 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, bool isPredefinedOperator = !bestSignature.Kind.IsUserDefined(); var leftType = left.Type; + Debug.Assert(leftType is { }); var finalPlaceholder = new BoundValuePlaceholder(node, bestSignature.ReturnType); - BoundExpression finalConversion = GenerateConversionForAssignment(leftType, finalPlaceholder, diagnostics, + BoundExpression? finalConversion = GenerateConversionForAssignment(leftType, finalPlaceholder, diagnostics, ConversionForAssignmentFlags.CompoundAssignment | (isPredefinedOperator ? ConversionForAssignmentFlags.PredefinedOperator : ConversionForAssignmentFlags.None)); @@ -246,8 +258,135 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); + + BoundCompoundAssignmentOperator? tryApplyUserDefinedInstanceOperator(ExpressionSyntax node, SyntaxToken operatorToken, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics) + { + var leftType = left.Type; + Debug.Assert(!left.HasDynamicType()); + + if (leftType is null || + !SyntaxFacts.IsOverloadableCompoundAssignmentOperator(operatorToken.Kind()) || + leftType.SpecialType.IsNumericType() || + !node.IsFeatureEnabled(MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators)) + { + return null; + } + + if (!CheckValueKind(node, left, BindValueKind.RefersToLocation | BindValueKind.Assignable, checkingReceiver: false, BindingDiagnosticBag.Discarded)) + { + return null; + } + + bool checkOverflowAtRuntime = CheckOverflowAtRuntime; + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + string? checkedName = null; + + if (checkOverflowAtRuntime) + { + checkedName = OperatorFacts.CompoundAssignmentOperatorNameFromSyntaxKind(operatorToken.Kind(), isChecked: true); + if (!SyntaxFacts.IsCheckedOperator(checkedName)) + { + checkedName = null; + } + } + + ArrayBuilder? methods = LookupUserDefinedInstanceOperators( + leftType, + checkedName: checkedName, + ordinaryName: OperatorFacts.CompoundAssignmentOperatorNameFromSyntaxKind(operatorToken.Kind(), isChecked: false), + parameterCount: 1, + ref useSiteInfo); + + if (methods?.IsEmpty != false) + { + diagnostics.Add(node, useSiteInfo); + methods?.Free(); + return null; + } + + Debug.Assert(!methods.IsEmpty); + + var overloadResolutionResult = OverloadResolutionResult.GetInstance(); + var typeArguments = ArrayBuilder.GetInstance(); + var analyzedArguments = AnalyzedArguments.GetInstance(); + + analyzedArguments.Arguments.Add(right); + + OverloadResolution.MethodInvocationOverloadResolution( + methods, + typeArguments, + left, + analyzedArguments, + overloadResolutionResult, + ref useSiteInfo, + OverloadResolution.Options.DisallowExpandedForm); + + typeArguments.Free(); + diagnostics.Add(node, useSiteInfo); + + BoundCompoundAssignmentOperator? inPlaceResult; + + if (overloadResolutionResult.Succeeded) + { + var method = overloadResolutionResult.ValidResult.Member; + + BoundExpression rightConverted = CreateConversion(right, overloadResolutionResult.ValidResult.Result.ConversionForArg(0), method.Parameters[0].Type, diagnostics); + + ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false); + ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false); + + inPlaceResult = new BoundCompoundAssignmentOperator( + node, + new BinaryOperatorSignature( + kind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime), + leftType: leftType, + rightType: method.Parameters[0].Type, + returnType: leftType, + method: method, + constrainedToTypeOpt: null), + left: left, + right: rightConverted, + leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, + resultKind: LookupResultKind.Viable, + originalUserDefinedOperatorsOpt: ImmutableArray.Empty, + leftType); + + methods.Free(); + } + else if (overloadResolutionResult.HasAnyApplicableMember) + { + ImmutableArray methodsArray = methods.ToImmutableAndFree(); + + overloadResolutionResult.ReportDiagnostics( + binder: this, location: operatorToken.GetLocation(), nodeOpt: node, diagnostics: diagnostics, name: operatorToken.ValueText, + receiver: left, invokedExpression: node, arguments: analyzedArguments, memberGroup: methodsArray, + typeContainingConstructor: null, delegateTypeBeingInvoked: null); + + inPlaceResult = new BoundCompoundAssignmentOperator( + node, + BinaryOperatorSignature.Error, + left, + right, + leftPlaceholder: null, leftConversion: null, finalPlaceholder: null, finalConversion: null, + resultKind: LookupResultKind.OverloadResolutionFailure, + originalUserDefinedOperatorsOpt: methodsArray, + leftType); + } + else + { + inPlaceResult = null; + methods.Free(); + } + + analyzedArguments.Free(); + overloadResolutionResult.Free(); + + return inPlaceResult; + } } +#nullable disable + /// /// For "receiver.event += expr", produce "receiver.add_event(expr)". /// For "receiver.event -= expr", produce "receiver.remove_event(expr)". @@ -2411,7 +2550,7 @@ private BoundExpression BindIncrementOperator(ExpressionSyntax node, ExpressionS bool checkOverflowAtRuntime = CheckOverflowAtRuntime; CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); - ArrayBuilder? methods = lookupUserDefinedInstanceOperators( + ArrayBuilder? methods = LookupUserDefinedInstanceOperators( operandType, checkedName: checkOverflowAtRuntime ? (kind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PostfixIncrement ? @@ -2421,6 +2560,7 @@ private BoundExpression BindIncrementOperator(ExpressionSyntax node, ExpressionS ordinaryName: kind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PostfixIncrement ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName, + parameterCount: 0, ref useSiteInfo); if (methods?.IsEmpty != false) @@ -2443,7 +2583,7 @@ private BoundExpression BindIncrementOperator(ExpressionSyntax node, ExpressionS analyzedArguments, overloadResolutionResult, ref useSiteInfo, - OverloadResolution.Options.None); + OverloadResolution.Options.DisallowExpandedForm); typeArguments.Free(); diagnostics.Add(node, useSiteInfo); @@ -2507,71 +2647,74 @@ private BoundExpression BindIncrementOperator(ExpressionSyntax node, ExpressionS return inPlaceResult; } + } - ArrayBuilder? lookupUserDefinedInstanceOperators(TypeSymbol lookupInType, string? checkedName, string ordinaryName, ref CompoundUseSiteInfo useSiteInfo) - { - var lookupResult = LookupResult.GetInstance(); - ArrayBuilder? methods = null; - if (checkedName is not null) - { - this.LookupMembersWithFallback(lookupResult, lookupInType, name: checkedName, arity: 0, ref useSiteInfo, basesBeingResolved: null, options: LookupOptions.MustBeInstance | LookupOptions.MustBeOperator); + ArrayBuilder? LookupUserDefinedInstanceOperators(TypeSymbol lookupInType, string? checkedName, string ordinaryName, int parameterCount, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(parameterCount is 0 or 1); - if (lookupResult.IsMultiViable) - { - methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); - appendViableMethods(lookupResult, methods); - } + var lookupResult = LookupResult.GetInstance(); + ArrayBuilder? methods = null; + if (checkedName is not null) + { + Debug.Assert(SyntaxFacts.IsCheckedOperator(checkedName)); + this.LookupMembersWithFallback(lookupResult, lookupInType, name: checkedName, arity: 0, ref useSiteInfo, basesBeingResolved: null, options: LookupOptions.MustBeInstance | LookupOptions.MustBeOperator); - lookupResult.Clear(); + if (lookupResult.IsMultiViable) + { + methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); + appendViableMethods(lookupResult, parameterCount, methods); } - this.LookupMembersWithFallback(lookupResult, lookupInType, name: ordinaryName, arity: 0, ref useSiteInfo, basesBeingResolved: null, options: LookupOptions.MustBeInstance | LookupOptions.MustBeOperator); + lookupResult.Clear(); + } - if (lookupResult.IsMultiViable) + this.LookupMembersWithFallback(lookupResult, lookupInType, name: ordinaryName, arity: 0, ref useSiteInfo, basesBeingResolved: null, options: LookupOptions.MustBeInstance | LookupOptions.MustBeOperator); + + if (lookupResult.IsMultiViable) + { + if (methods is null) { - if (methods is null) + methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); + appendViableMethods(lookupResult, parameterCount, methods); + } + else + { + var existing = new HashSet(PairedOperatorComparer.Instance); + + foreach (var method in methods) { - methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); - appendViableMethods(lookupResult, methods); + existing.Add(method.GetLeastOverriddenMethod(ContainingType)); } - else - { - var existing = new HashSet(PairedOperatorComparer.Instance); - - foreach (var method in methods) - { - existing.Add(method.GetLeastOverriddenMethod(ContainingType)); - } - foreach (MethodSymbol method in lookupResult.Symbols) + foreach (MethodSymbol method in lookupResult.Symbols) + { + if (isViable(method, parameterCount) && !existing.Contains(method.GetLeastOverriddenMethod(ContainingType))) { - if (isViable(method) && !existing.Contains(method.GetLeastOverriddenMethod(ContainingType))) - { - methods.Add(method); - } + methods.Add(method); } } } + } - lookupResult.Free(); + lookupResult.Free(); - return methods; + return methods; - static void appendViableMethods(LookupResult lookupResult, ArrayBuilder methods) + static void appendViableMethods(LookupResult lookupResult, int parameterCount, ArrayBuilder methods) + { + foreach (MethodSymbol method in lookupResult.Symbols) { - foreach (MethodSymbol method in lookupResult.Symbols) + if (isViable(method, parameterCount)) { - if (isViable(method)) - { - methods.Add(method); - } + methods.Add(method); } } + } - static bool isViable(MethodSymbol method) - { - return method.ParameterCount == 0; - } + static bool isViable(MethodSymbol method, int parameterCount) + { + return method.ParameterCount == parameterCount; } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs index ed0cc94d016ad..19e732246b906 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs @@ -93,7 +93,7 @@ public RefKind LeftRefKind { get { - if ((object)Method != null) + if ((object)Method != null && Method.IsStatic) { Debug.Assert(Method.ParameterCount == 2); @@ -115,13 +115,15 @@ public RefKind RightRefKind { if ((object)Method != null) { - Debug.Assert(Method.ParameterCount == 2); + int rightIndex = Method.IsStatic ? 1 : 0; + + Debug.Assert(Method.ParameterCount == rightIndex + 1); if (!Method.ParameterRefKinds.IsDefaultOrEmpty) { - Debug.Assert(Method.ParameterRefKinds.Length == 2); + Debug.Assert(Method.ParameterRefKinds.Length == rightIndex + 1); - return Method.ParameterRefKinds[1]; + return Method.ParameterRefKinds[rightIndex]; } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index bfb102af0fbe7..7227d01a16adb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -129,6 +129,7 @@ public enum Options : ushort DynamicConvertsToAnything = 1 << 7, DisallowExpandedNonArrayParams = 1 << 8, InferringUniqueMethodGroupSignature = 1 << 9, + DisallowExpandedForm = 1 << 10, } // Perform overload resolution on the given method group, with the given arguments and @@ -1192,7 +1193,7 @@ private void AddMemberToCandidateSet( // tricks you can pull to make overriding methods [indexers] inconsistent with overridden // methods [indexers] (or implementing methods [indexers] inconsistent with interfaces). - if ((options & Options.IsMethodGroupConversion) == 0 && IsValidParams(_binder, leastOverriddenMember, disallowExpandedNonArrayParams, out TypeWithAnnotations definitionElementType)) + if ((options & (Options.IsMethodGroupConversion | Options.DisallowExpandedForm)) == 0 && IsValidParams(_binder, leastOverriddenMember, disallowExpandedNonArrayParams, out TypeWithAnnotations definitionElementType)) { var expandedResult = IsMemberApplicableInExpandedForm( member, diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 1be6cbe67c6bb..5a2d0e945b8ab 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -5222,43 +5222,8 @@ private TypeWithState ReinferAndVisitBinaryOperator( method = reinferredMethod; var parameters = method.Parameters; - visitOperandConversionAndPostConditions(left, leftOperand, leftConversion, parameters[0], leftUnderlyingType); - visitOperandConversionAndPostConditions(right, rightOperand, rightConversion, parameters[1], rightUnderlyingType); - - void visitOperandConversionAndPostConditions( - BoundExpression expr, - BoundExpression operand, - Conversion conversion, - ParameterSymbol parameter, - TypeWithState operandType) - { - var parameterAnnotations = GetParameterAnnotations(parameter); - var targetTypeWithNullability = ApplyLValueAnnotations(parameter.TypeWithAnnotations, parameterAnnotations); - - if (isLifted && targetTypeWithNullability.Type.IsNonNullableValueType()) - { - targetTypeWithNullability = TypeWithAnnotations.Create(MakeNullableOf(targetTypeWithNullability)); - } - - var resultType = VisitConversion( - expr as BoundConversion, - operand, - conversion, - targetTypeWithNullability, - operandType, - checkConversion: true, - fromExplicitCast: false, - useLegacyWarnings: false, - AssignmentKind.Argument, - parameter); - - if (CheckDisallowedNullAssignment(resultType, parameterAnnotations, expr.Syntax, operand)) - { - LearnFromNonNullTest(operand, ref State); - } - - LearnFromPostConditions(operand, parameterAnnotations); - } + VisitBinaryOperatorOperandConversionAndPostConditions(left, leftOperand, leftConversion, parameters[0], leftUnderlyingType, isLifted); + VisitBinaryOperatorOperandConversionAndPostConditions(right, rightOperand, rightConversion, parameters[1], rightUnderlyingType, isLifted); } else { @@ -5324,6 +5289,36 @@ void visitOperandConversion( } } + private void VisitBinaryOperatorOperandConversionAndPostConditions(BoundExpression expr, BoundExpression operand, Conversion conversion, ParameterSymbol parameter, TypeWithState operandType, bool isLifted) + { + var parameterAnnotations = GetParameterAnnotations(parameter); + var targetTypeWithNullability = ApplyLValueAnnotations(parameter.TypeWithAnnotations, parameterAnnotations); + + if (isLifted && targetTypeWithNullability.Type.IsNonNullableValueType()) + { + targetTypeWithNullability = TypeWithAnnotations.Create(MakeNullableOf(targetTypeWithNullability)); + } + + var resultType = VisitConversion( + expr as BoundConversion, + operand, + conversion, + targetTypeWithNullability, + operandType, + checkConversion: true, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Argument, + parameter); + + if (CheckDisallowedNullAssignment(resultType, parameterAnnotations, expr.Syntax, operand)) + { + LearnFromNonNullTest(operand, ref State); + } + + LearnFromPostConditions(operand, parameterAnnotations); + } + private void AfterLeftChildHasBeenVisited( BoundExpression leftOperand, Conversion leftConversion, @@ -10568,22 +10563,21 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress { Debug.Assert(!IsConditionalState); - if (node.MethodOpt is { } method ? - !method.IsStatic : - (!node.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty && !node.OriginalUserDefinedOperatorsOpt[0].IsStatic)) - { - TypeWithState receiverType = VisitRvalueWithState(node.Operand); - CheckCallReceiver(node.Operand, receiverType, node.MethodOpt ?? node.OriginalUserDefinedOperatorsOpt[0]); - SetNotNullResult(node); - return null; - } - var operandType = VisitRvalueWithState(node.Operand); var operandLvalue = LvalueResultType; bool setResult = false; if (this.State.Reachable) { + if (node.MethodOpt is { } method ? + !method.IsStatic : + (!node.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty && !node.OriginalUserDefinedOperatorsOpt[0].IsStatic)) + { + CheckCallReceiver(node.Operand, operandType, node.MethodOpt ?? node.OriginalUserDefinedOperatorsOpt[0]); + SetNotNullResult(node); + return null; + } + // https://github.com/dotnet/roslyn/issues/29961 Update increment method based on operand type. MethodSymbol? incrementOperator = (node.OperatorKind.IsUserDefined() && node.MethodOpt?.ParameterCount == 1) ? node.MethodOpt : null; TypeWithAnnotations targetTypeOfOperandConversion; @@ -10676,10 +10670,6 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress public override BoundNode? VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { - // for an operator like: 'TResult operator op(TLeftParam left, TRightParam right);' - // and usage like: 'x op= y;' - // expansion is (roughly): 'x = (TLeftArg)((TLeftParam)x op (TRightParam)y);' - // visit 'x' Visit(node.Left); Unsplit(); @@ -10693,6 +10683,33 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress // visit 'y' var rightTypeWithState = VisitRvalueWithState(rightConversionOperand); + if (node.Operator.Method is { } method) + { + if (!method.IsStatic) + { + // Update method based on inferred operand type. + var reinferredMethod = (MethodSymbol)AsMemberOfType(leftTypeWithState.Type, method); + SetUpdatedSymbol(node, method, reinferredMethod); + method = reinferredMethod; + + CheckCallReceiver(node.Left, leftTypeWithState, method); + VisitBinaryOperatorOperandConversionAndPostConditions(node.Right, rightConversionOperand, rightConversion, method.Parameters[0], rightTypeWithState, isLifted: false); + + SetNotNullResult(node); + return null; + } + } + else if (!node.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty && !node.OriginalUserDefinedOperatorsOpt[0].IsStatic) + { + // This is an error scenario + SetNotNullResult(node); + return null; + } + + // for an operator like: 'TResult operator op(TLeftParam left, TRightParam right);' + // and usage like: 'x op= y;' + // expansion is (roughly): 'x = (TLeftArg)((TLeftParam)x op (TRightParam)y);' + // visit '(TLeftParam)x op (TRightParam)y' var resultTypeWithState = ReinferAndVisitBinaryOperator( node, diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index 3b295eb9b6e35..b0b67aaa12a5a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -23,6 +23,51 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO { Debug.Assert(TypeSymbol.Equals(node.Right.Type, node.Operator.RightType, TypeCompareKind.ConsiderEverything2)); + if (node.Operator.Method?.IsStatic == false) + { + return VisitInstanceCompoundAssignmentOperator(node, used); + } + else + { + return VisitBuiltInOrStaticCompoundAssignmentOperator(node, used); + } + } + + private BoundExpression VisitInstanceCompoundAssignmentOperator(BoundCompoundAssignmentOperator node, bool used) + { + Debug.Assert(node.Operator.Method is { }); + + SyntaxNode syntax = node.Syntax; + + if (!used) + { + return BoundCall.Synthesized(syntax, VisitExpression(node.Left), initialBindingReceiverIsSubjectToCloning: ThreeState.False, node.Operator.Method, VisitExpression(node.Right)); + } + + TypeSymbol? leftType = node.Left.Type; // type of the target + Debug.Assert(leftType is { }); + Debug.Assert(TypeSymbol.Equals(leftType, node.Type, TypeCompareKind.AllIgnoreOptions)); + + BoundAssignmentOperator tempAssignment; + BoundLocal targetOfCompoundOperation; + + if (leftType.IsReferenceType) + { + targetOfCompoundOperation = _factory.StoreToTemp(VisitExpression(node.Left), out tempAssignment); + return new BoundSequence( + syntax: syntax, + locals: [targetOfCompoundOperation.LocalSymbol], + sideEffects: [tempAssignment, BoundCall.Synthesized(syntax, targetOfCompoundOperation, initialBindingReceiverIsSubjectToCloning: ThreeState.False, node.Operator.Method, VisitExpression(node.Right))], + value: targetOfCompoundOperation, + type: leftType); + } + + return MakeInstanceCompoundAssignmentOperatorResult(node.Syntax, node.Left, node.Right, node.Operator.Method, node.Operator.Kind.IsChecked()); + } + + private BoundExpression VisitBuiltInOrStaticCompoundAssignmentOperator(BoundCompoundAssignmentOperator node, bool used) + { + var temps = ArrayBuilder.GetInstance(); var stores = ArrayBuilder.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 1225ad402ab87..6ca0582defaf7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -422,11 +422,11 @@ private BoundExpression VisitInstanceIncrementOperator(BoundIncrementOperator no throw ExceptionUtilities.Unreachable(); } - BoundAssignmentOperator tempAssignment; - BoundLocal boundTemp; - if (operandType.IsReferenceType) { + BoundAssignmentOperator tempAssignment; + BoundLocal boundTemp; + boundTemp = _factory.StoreToTemp(VisitExpression(node.Operand), out tempAssignment); return new BoundSequence( syntax: syntax, @@ -436,29 +436,53 @@ private BoundExpression VisitInstanceIncrementOperator(BoundIncrementOperator no type: operandType); } + return MakeInstanceCompoundAssignmentOperatorResult(node.Syntax, node.Operand, rightOpt: null, node.MethodOpt, node.OperatorKind.IsChecked()); + } + + private BoundExpression MakeInstanceCompoundAssignmentOperatorResult(SyntaxNode syntax, BoundExpression left, BoundExpression? rightOpt, MethodSymbol operatorMethod, bool isChecked) + { + TypeSymbol? operandType = left.Type; //type of the variable being incremented + Debug.Assert(operandType is { }); + ArrayBuilder tempSymbols = ArrayBuilder.GetInstance(); ArrayBuilder tempInitializers = ArrayBuilder.GetInstance(); // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. - BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamicAssignment: false); + BoundExpression transformedLHS = TransformCompoundAssignmentLHS(left, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamicAssignment: false); Debug.Assert(TypeSymbol.Equals(operandType, transformedLHS.Type, TypeCompareKind.AllIgnoreOptions)); + BoundAssignmentOperator tempAssignment; + BoundLocal boundTemp; + boundTemp = _factory.StoreToTemp(transformedLHS, out tempAssignment); tempSymbols.Add(boundTemp.LocalSymbol); tempInitializers.Add(tempAssignment); - var increment = BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.False, node.MethodOpt); - var assignBack = MakeAssignmentOperator(syntax, transformedLHS, boundTemp, used: false, isChecked: node.OperatorKind.IsChecked(), isCompoundAssignment: false); + rightOpt = VisitExpression(rightOpt); if (operandType.IsValueType) { + BoundCall increment = makeIncrementCall(syntax, boundTemp, rightOpt, operatorMethod); + BoundExpression assignBack = makeAssignmentBack(syntax, transformedLHS, boundTemp, isChecked); + tempInitializers.Add(increment); tempInitializers.Add(assignBack); } else { + if (rightOpt is not null) + { + BoundLocal capturedRight = _factory.StoreToTemp(rightOpt, out tempAssignment); + tempSymbols.Add(capturedRight.LocalSymbol); + tempInitializers.Add(tempAssignment); + rightOpt = capturedRight; + } + + BoundCall increment = makeIncrementCall(syntax, boundTemp, rightOpt, operatorMethod); + BoundExpression assignBack = makeAssignmentBack(syntax, transformedLHS, boundTemp, isChecked); + // (object)default(T) != null var isNotClass = _factory.IsNotNullReference(_factory.Default(operandType)); tempInitializers.Add( @@ -481,6 +505,16 @@ private BoundExpression VisitInstanceIncrementOperator(BoundIncrementOperator no sideEffects: tempInitializers.ToImmutableAndFree(), value: boundTemp, type: operandType); + + static BoundCall makeIncrementCall(SyntaxNode syntax, BoundLocal boundTemp, BoundExpression? rightOpt, MethodSymbol operatorMethod) + { + return BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.False, operatorMethod, rightOpt is null ? [] : [rightOpt]); + } + + BoundExpression makeAssignmentBack(SyntaxNode syntax, BoundExpression transformedLHS, BoundLocal boundTemp, bool isChecked) + { + return MakeAssignmentOperator(syntax, transformedLHS, boundTemp, used: false, isChecked: isChecked, isCompoundAssignment: false); + } } /// diff --git a/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs b/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs index 521a87e71c6ba..544a3e3b5b6ca 100644 --- a/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs @@ -5168,6 +5168,39 @@ static void Main() // ++x; Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15) ); + + var source2 = @" +public class C1 +{ + public void operator ++() {} +} + +#nullable enable + +public class Program +{ + static void Main() + { + C1? x = null; + + if (false) + { + ++x; + System.Console.Write(""unreachable""); + x.ToString(); + } + + System.Console.Write(""Done""); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: "Done").VerifyDiagnostics( + // (17,13): warning CS0162: Unreachable code detected + // ++x; + Diagnostic(ErrorCode.WRN_UnreachableCode, "++").WithLocation(17, 13) + ); } [Fact] @@ -9726,7 +9759,4425 @@ interface C3 ); } - // PROTOTYPE: Test checked/unchecked at call site - // Disable ORPA during overload resolution? + [Theory] + [CombinatorialData] + public void CompoundAssignment_00690_Consumption_OnNonVariable([CombinatorialValues("+=", "-=", "*=", "/=")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator" + op + @"(int x) => throw null; + public void operator checked" + op + @"(int x) => throw null; + + public static C1 operator" + op[..^1] + @"(C1 x, int y) + { + System.Console.Write(""[operator]""); + return new C1() { _F = x._F + y }; + } + public static C1 operator checked" + op[..^1] + @"(C1 x, int y) + { + System.Console.Write(""[operator checked]""); + checked + { + return new C1() { _F = x._F + y }; + } + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1 P {get; set;} = new C1(); + + static void Main() + { + C1 x; + + P" + op + @" 1; + System.Console.WriteLine(P._F); + x = P" + op + @" 1; + System.Console.WriteLine(P._F); + + checked + { + P" + op + @" 1; + System.Console.WriteLine(P._F); + x = P" + op + @" 1; + System.Console.WriteLine(P._F); + } + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[operator]1 +[operator]2 +[operator checked]3 +[operator checked]4 +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00691_Consumption_OnNonVariable([CombinatorialValues("%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator" + op + @"(int x) => throw null; + + public static C1 operator" + op[..^1] + @"(C1 x, int y) + { + System.Console.Write(""[operator]""); + return new C1() { _F = x._F + y }; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1 P {get; set;} = new C1(); + + static void Main() + { + C1 x; + + P" + op + @" 1; + System.Console.WriteLine(P._F); + x = P" + op + @" 1; + System.Console.WriteLine(P._F); + + checked + { + P" + op + @" 1; + System.Console.WriteLine(P._F); + x = P" + op + @" 1; + System.Console.WriteLine(P._F); + } + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[operator]1 +[operator]2 +[operator]3 +[operator]4 +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00700_Consumption_NotUsed_Class([CombinatorialValues("+=", "-=", "*=", "/=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); + public void operator checked" + op + @"(long x); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } + public void operator checked" + op + @"(long x) + { + System.Console.Write(""[operator checked]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test1(x); + System.Console.WriteLine(x[0]._F); + Test2(x); + System.Console.WriteLine(x[0]._F); + Test3(x); + System.Console.WriteLine(x[0]._F); + Test4(x); + System.Console.WriteLine(x[0]._F); + Test5(x); + System.Console.WriteLine(x[0]._F); + Test6(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test1(C1[] x) + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static void Test3(T[] x) where T : class, I1 + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test4(T[] x) where T : class, I1 + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int Get1() + { + System.Console.Write(""[Get1]""); + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][Get1][operator]1 +[GetA][Get0][Get1][operator checked]2 +[GetA][Get0][Get1][operator]3 +[GetA][Get0][Get1][operator checked]4 +[GetA][Get0][Get1][operator]5 +[GetA][Get0][Get1][operator checked]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: call ""int Program.Get1()"" + IL_0011: conv.i8 + IL_0012: callvirt ""void C1." + methodName + @"(long)"" + IL_0017: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 33 (0x21) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: box ""T"" + IL_0015: call ""int Program.Get1()"" + IL_001a: conv.i8 + IL_001b: callvirt ""void I1." + methodName + @"(long)"" + IL_0020: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 60 (0x3c) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: ldloca.s V_0 + IL_0014: initobj ""T"" + IL_001a: ldloc.0 + IL_001b: box ""T"" + IL_0020: brtrue.s IL_002a + IL_0022: ldobj ""T"" + IL_0027: stloc.0 + IL_0028: ldloca.s V_0 + IL_002a: call ""int Program.Get1()"" + IL_002f: conv.i8 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var typeInfo = model.GetTypeInfo(opNode.Left); + Assert.Equal("C1", typeInfo.Type.ToTestDisplayString()); + Assert.Equal("C1", typeInfo.ConvertedType.ToTestDisplayString()); + Assert.True(model.GetConversion(opNode.Left).IsIdentity); + + typeInfo = model.GetTypeInfo(opNode.Right); + Assert.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal("System.Int64", typeInfo.ConvertedType.ToTestDisplayString()); + Assert.True(model.GetConversion(opNode.Right).IsNumeric); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" Get1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'Get1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.Get1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get1()') + Instance Receiver: + null + Arguments(0) +"); + + methodName = CompoundAssignmentOperatorName(op, isChecked: true); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: call ""int Program.Get1()"" + IL_0011: conv.i8 + IL_0012: callvirt ""void C1." + methodName + @"(long)"" + IL_0017: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 33 (0x21) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: box ""T"" + IL_0015: call ""int Program.Get1()"" + IL_001a: conv.i8 + IL_001b: callvirt ""void I1." + methodName + @"(long)"" + IL_0020: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 60 (0x3c) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: ldloca.s V_0 + IL_0014: initobj ""T"" + IL_001a: ldloc.0 + IL_001b: box ""T"" + IL_0020: brtrue.s IL_002a + IL_0022: ldobj ""T"" + IL_0027: stloc.0 + IL_0028: ldloca.s V_0 + IL_002a: call ""int Program.Get1()"" + IL_002f: conv.i8 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @", Checked) (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" Get1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'Get1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.Get1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "C1", "int").WithLocation(23, 9), + // (30,13): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "C1", "int").WithLocation(30, 13), + // (36,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(36, 9), + // (43,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(43, 13), + // (49,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(49, 9), + // (56,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(56, 13) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + private static Operations.BinaryOperatorKind CompoundAssignmentOperatorToBinaryOperatorKind(string op) + { + switch (op) + { + case "*=": return Operations.BinaryOperatorKind.Multiply; + case "/=": return Operations.BinaryOperatorKind.Divide; + case "%=": return Operations.BinaryOperatorKind.Remainder; + case "+=": return Operations.BinaryOperatorKind.Add; + case "-=": return Operations.BinaryOperatorKind.Subtract; + case ">>=": return Operations.BinaryOperatorKind.RightShift; + case ">>>=": return Operations.BinaryOperatorKind.UnsignedRightShift; + case "<<=": return Operations.BinaryOperatorKind.LeftShift; + case "&=": return Operations.BinaryOperatorKind.And; + case "|=": return Operations.BinaryOperatorKind.Or; + case "^=": return Operations.BinaryOperatorKind.ExclusiveOr; + default: throw ExceptionUtilities.UnexpectedValue(op); + } + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00701_Consumption_NotUsed_Class([CombinatorialValues("+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test1(x); + System.Console.WriteLine(x[0]._F); + Test2(x); + System.Console.WriteLine(x[0]._F); + Test3(x); + System.Console.WriteLine(x[0]._F); + Test4(x); + System.Console.WriteLine(x[0]._F); + Test5(x); + System.Console.WriteLine(x[0]._F); + Test6(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test1(C1[] x) + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static void Test3(T[] x) where T : class, I1 + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test4(T[] x) where T : class, I1 + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[Get1]""); + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][Get1][operator]1 +[GetA][Get0][Get1][operator]2 +[GetA][Get0][Get1][operator]3 +[GetA][Get0][Get1][operator]4 +[GetA][Get0][Get1][operator]5 +[GetA][Get0][Get1][operator]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: call ""int Program.G1()"" + IL_0011: conv.i8 + IL_0012: callvirt ""void C1." + methodName + @"(long)"" + IL_0017: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 33 (0x21) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: box ""T"" + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: callvirt ""void I1." + methodName + @"(long)"" + IL_0020: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 60 (0x3c) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: ldloca.s V_0 + IL_0014: initobj ""T"" + IL_001a: ldloc.0 + IL_001b: box ""T"" + IL_0020: brtrue.s IL_002a + IL_0022: ldobj ""T"" + IL_0027: stloc.0 + IL_0028: ldloca.s V_0 + IL_002a: call ""int Program.G1()"" + IL_002f: conv.i8 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: call ""int Program.G1()"" + IL_0011: conv.i8 + IL_0012: callvirt ""void C1." + methodName + @"(long)"" + IL_0017: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 33 (0x21) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: box ""T"" + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: callvirt ""void I1." + methodName + @"(long)"" + IL_0020: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 60 (0x3c) + .maxstack 2 + .locals init (T V_0) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: ldloca.s V_0 + IL_0014: initobj ""T"" + IL_001a: ldloc.0 + IL_001b: box ""T"" + IL_0020: brtrue.s IL_002a + IL_0022: ldobj ""T"" + IL_0027: stloc.0 + IL_0028: ldloca.s V_0 + IL_002a: call ""int Program.G1()"" + IL_002f: conv.i8 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 9), + // (30,13): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 13), + // (36,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 9), + // (43,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 13), + // (49,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 9), + // (56,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 13) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00710_Consumption_NotUsed_Struct([CombinatorialValues("+=", "-=", "*=", "/=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); + public void operator checked" + op + @"(long x); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } + public void operator checked" + op + @"(long x) + { + System.Console.Write(""[operator checked]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test1(x); + System.Console.WriteLine(x[0]._F); + Test2(x); + System.Console.WriteLine(x[0]._F); + Test3(x); + System.Console.WriteLine(x[0]._F); + Test4(x); + System.Console.WriteLine(x[0]._F); + Test5(x); + System.Console.WriteLine(x[0]._F); + Test6(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test1(C1[] x) + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static void Test3(T[] x) where T : struct, I1 + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test4(T[] x) where T : struct, I1 + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @" Get1(); + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @" Get1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int Get1() + { + System.Console.Write(""[Get1]""); + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][Get1][operator]1 +[GetA][Get0][Get1][operator checked]2 +[GetA][Get0][Get1][operator]3 +[GetA][Get0][Get1][operator checked]4 +[GetA][Get0][Get1][operator]5 +[GetA][Get0][Get1][operator checked]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 28 (0x1c) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: call ""int Program.Get1()"" + IL_0015: conv.i8 + IL_0016: call ""void C1." + methodName + @"(long)"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 36 (0x24) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: call ""int Program.Get1()"" + IL_0017: conv.i8 + IL_0018: constrained. ""T"" + IL_001e: callvirt ""void I1." + methodName + @"(long)"" + IL_0023: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" Get1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'Get1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.Get1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get1()') + Instance Receiver: + null + Arguments(0) +"); + + methodName = CompoundAssignmentOperatorName(op, isChecked: true); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 28 (0x1c) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: call ""int Program.Get1()"" + IL_0015: conv.i8 + IL_0016: call ""void C1." + methodName + @"(long)"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 36 (0x24) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: call ""int Program.Get1()"" + IL_0017: conv.i8 + IL_0018: constrained. ""T"" + IL_001e: callvirt ""void I1." + methodName + @"(long)"" + IL_0023: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @", Checked) (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" Get1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'Get1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.Get1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "C1", "int").WithLocation(23, 9), + // (30,13): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "C1", "int").WithLocation(30, 13), + // (36,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(36, 9), + // (43,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(43, 13), + // (49,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(49, 9), + // (56,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= Get1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" Get1()").WithArguments(op, "T", "int").WithLocation(56, 13) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00711_Consumption_NotUsed_Struct([CombinatorialValues("+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test1(x); + System.Console.WriteLine(x[0]._F); + Test2(x); + System.Console.WriteLine(x[0]._F); + Test3(x); + System.Console.WriteLine(x[0]._F); + Test4(x); + System.Console.WriteLine(x[0]._F); + Test5(x); + System.Console.WriteLine(x[0]._F); + Test6(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test1(C1[] x) + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static void Test3(T[] x) where T : struct, I1 + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test4(T[] x) where T : struct, I1 + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @" G1(); + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[Get1]""); + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][Get1][operator]1 +[GetA][Get0][Get1][operator]2 +[GetA][Get0][Get1][operator]3 +[GetA][Get0][Get1][operator]4 +[GetA][Get0][Get1][operator]5 +[GetA][Get0][Get1][operator]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 28 (0x1c) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: call ""int Program.G1()"" + IL_0015: conv.i8 + IL_0016: call ""void C1." + methodName + @"(long)"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 36 (0x24) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: call ""int Program.G1()"" + IL_0017: conv.i8 + IL_0018: constrained. ""T"" + IL_001e: callvirt ""void I1." + methodName + @"(long)"" + IL_0023: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 28 (0x1c) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: call ""int Program.G1()"" + IL_0015: conv.i8 + IL_0016: call ""void C1." + methodName + @"(long)"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 36 (0x24) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: readonly. + IL_000d: ldelema ""T"" + IL_0012: call ""int Program.G1()"" + IL_0017: conv.i8 + IL_0018: constrained. ""T"" + IL_001e: callvirt ""void I1." + methodName + @"(long)"" + IL_0023: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 9), + // (30,13): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 13), + // (36,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 9), + // (43,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 13), + // (49,9): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 9), + // (56,13): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 13) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00720_Consumption_Used_Class([CombinatorialValues("+=", "-=", "*=", "/=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); + public void operator checked" + op + @"(long x); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } + public void operator checked" + op + @"(long x) + { + System.Console.Write(""[operator checked]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1[] x = [null]; + + static void Main() + { + var val = new C1(); + x[0] = val; + C1 y = Test1(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test2(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test3(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test4(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test5(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test6(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + } + + static C1 Test1(C1[] x) + { +#line 23 + return GetA(x)[Get0()]" + op + @" G1(); + } + + static C1 Test2(C1[] x) + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test3(T[] x) where T : class, I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test4(T[] x) where T : class, I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test5(T[] x) where T : I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[G1]""); + x[0] = null; + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][G1][operator]1True +[GetA][Get0][G1][operator checked]2True +[GetA][Get0][G1][operator]3True +[GetA][Get0][G1][operator checked]4True +[GetA][Get0][G1][operator]5True +[GetA][Get0][G1][operator checked]6True +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: dup + IL_000d: call ""int Program.G1()"" + IL_0012: conv.i8 + IL_0013: callvirt ""void C1." + methodName + @"(long)"" + IL_0018: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: dup + IL_0011: box ""T"" + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: callvirt ""void I1." + methodName + @"(long)"" + IL_0021: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 85 (0x55) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + long V_3, + T V_4) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: stloc.0 + IL_0007: call ""int Program.Get0()"" + IL_000c: stloc.1 + IL_000d: ldloc.0 + IL_000e: ldloc.1 + IL_000f: ldelem ""T"" + IL_0014: stloc.2 + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: stloc.3 + IL_001c: ldloca.s V_4 + IL_001e: initobj ""T"" + IL_0024: ldloc.s V_4 + IL_0026: box ""T"" + IL_002b: brtrue.s IL_003d + IL_002d: ldloca.s V_2 + IL_002f: ldloc.3 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: br.s IL_0053 + IL_003d: ldloca.s V_2 + IL_003f: ldloc.3 + IL_0040: constrained. ""T"" + IL_0046: callvirt ""void I1." + methodName + @"(long)"" + IL_004b: ldloc.0 + IL_004c: ldloc.1 + IL_004d: ldloc.2 + IL_004e: stelem ""T"" + IL_0053: ldloc.2 + IL_0054: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + methodName = CompoundAssignmentOperatorName(op, isChecked: true); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: dup + IL_000d: call ""int Program.G1()"" + IL_0012: conv.i8 + IL_0013: callvirt ""void C1." + methodName + @"(long)"" + IL_0018: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: dup + IL_0011: box ""T"" + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: callvirt ""void I1." + methodName + @"(long)"" + IL_0021: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 85 (0x55) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + long V_3, + T V_4) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: stloc.0 + IL_0007: call ""int Program.Get0()"" + IL_000c: stloc.1 + IL_000d: ldloc.0 + IL_000e: ldloc.1 + IL_000f: ldelem ""T"" + IL_0014: stloc.2 + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: stloc.3 + IL_001c: ldloca.s V_4 + IL_001e: initobj ""T"" + IL_0024: ldloc.s V_4 + IL_0026: box ""T"" + IL_002b: brtrue.s IL_003d + IL_002d: ldloca.s V_2 + IL_002f: ldloc.3 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: br.s IL_0053 + IL_003d: ldloca.s V_2 + IL_003f: ldloc.3 + IL_0040: constrained. ""T"" + IL_0046: callvirt ""void I1." + methodName + @"(long)"" + IL_004b: ldloc.0 + IL_004c: ldloc.1 + IL_004d: ldloc.2 + IL_004e: stelem ""T"" + IL_0053: ldloc.2 + IL_0054: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @", Checked) (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,16): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 16), + // (30,20): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 20), + // (36,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 16), + // (43,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 20), + // (49,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 16), + // (56,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 20) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00721_Consumption_Used_Class([CombinatorialValues("+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1[] x = [null]; + + static void Main() + { + var val = new C1(); + x[0] = val; + C1 y = Test1(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test2(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test3(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test4(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test5(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + x[0] = val; + y = Test6(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)val == y && x[0] is null); + } + + static C1 Test1(C1[] x) + { +#line 23 + return GetA(x)[Get0()]" + op + @" G1(); + } + + static C1 Test2(C1[] x) + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test3(T[] x) where T : class, I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test4(T[] x) where T : class, I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test5(T[] x) where T : I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[G1]""); + x[0] = null; + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][G1][operator]1True +[GetA][Get0][G1][operator]2True +[GetA][Get0][G1][operator]3True +[GetA][Get0][G1][operator]4True +[GetA][Get0][G1][operator]5True +[GetA][Get0][G1][operator]6True +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: dup + IL_000d: call ""int Program.G1()"" + IL_0012: conv.i8 + IL_0013: callvirt ""void C1." + methodName + @"(long)"" + IL_0018: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: dup + IL_0011: box ""T"" + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: callvirt ""void I1." + methodName + @"(long)"" + IL_0021: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 85 (0x55) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + long V_3, + T V_4) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: stloc.0 + IL_0007: call ""int Program.Get0()"" + IL_000c: stloc.1 + IL_000d: ldloc.0 + IL_000e: ldloc.1 + IL_000f: ldelem ""T"" + IL_0014: stloc.2 + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: stloc.3 + IL_001c: ldloca.s V_4 + IL_001e: initobj ""T"" + IL_0024: ldloc.s V_4 + IL_0026: box ""T"" + IL_002b: brtrue.s IL_003d + IL_002d: ldloca.s V_2 + IL_002f: ldloc.3 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: br.s IL_0053 + IL_003d: ldloca.s V_2 + IL_003f: ldloc.3 + IL_0040: constrained. ""T"" + IL_0046: callvirt ""void I1." + methodName + @"(long)"" + IL_004b: ldloc.0 + IL_004c: ldloc.1 + IL_004d: ldloc.2 + IL_004e: stelem ""T"" + IL_0053: ldloc.2 + IL_0054: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem.ref + IL_000c: dup + IL_000d: call ""int Program.G1()"" + IL_0012: conv.i8 + IL_0013: callvirt ""void C1." + methodName + @"(long)"" + IL_0018: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelem ""T"" + IL_0010: dup + IL_0011: box ""T"" + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: callvirt ""void I1." + methodName + @"(long)"" + IL_0021: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 85 (0x55) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + long V_3, + T V_4) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: stloc.0 + IL_0007: call ""int Program.Get0()"" + IL_000c: stloc.1 + IL_000d: ldloc.0 + IL_000e: ldloc.1 + IL_000f: ldelem ""T"" + IL_0014: stloc.2 + IL_0015: call ""int Program.G1()"" + IL_001a: conv.i8 + IL_001b: stloc.3 + IL_001c: ldloca.s V_4 + IL_001e: initobj ""T"" + IL_0024: ldloc.s V_4 + IL_0026: box ""T"" + IL_002b: brtrue.s IL_003d + IL_002d: ldloca.s V_2 + IL_002f: ldloc.3 + IL_0030: constrained. ""T"" + IL_0036: callvirt ""void I1." + methodName + @"(long)"" + IL_003b: br.s IL_0053 + IL_003d: ldloca.s V_2 + IL_003f: ldloc.3 + IL_0040: constrained. ""T"" + IL_0046: callvirt ""void I1." + methodName + @"(long)"" + IL_004b: ldloc.0 + IL_004c: ldloc.1 + IL_004d: ldloc.2 + IL_004e: stelem ""T"" + IL_0053: ldloc.2 + IL_0054: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,16): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 16), + // (30,20): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 20), + // (36,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 16), + // (43,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 20), + // (49,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 16), + // (56,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 20) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00730_Consumption_Used_Struct([CombinatorialValues("+=", "-=", "*=", "/=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); + public void operator checked" + op + @"(long x); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } + public void operator checked" + op + @"(long x) + { + System.Console.Write(""[operator checked]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1[] x = [new C1()]; + + static void Main() + { + C1 y = Test1(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test2(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test3(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test4(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test5(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test6(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + } + + static C1 Test1(C1[] x) + { +#line 23 + return GetA(x)[Get0()]" + op + @" G1(); + } + + static C1 Test2(C1[] x) + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test3(T[] x) where T : struct, I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test4(T[] x) where T : struct, I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test5(T[] x) where T : I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[G1]""); + x[0] = new C1() { _F = -1 }; + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][G1][operator]11 +[GetA][Get0][G1][operator checked]22 +[GetA][Get0][G1][operator]33 +[GetA][Get0][G1][operator checked]44 +[GetA][Get0][G1][operator]55 +[GetA][Get0][G1][operator checked]66 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 44 (0x2c) + .maxstack 3 + .locals init (C1 V_0) + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: dup + IL_0011: ldobj ""C1"" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call ""int Program.G1()"" + IL_001e: conv.i8 + IL_001f: call ""void C1." + methodName + @"(long)"" + IL_0024: ldloc.0 + IL_0025: stobj ""C1"" + IL_002a: ldloc.0 + IL_002b: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 48 (0x30) + .maxstack 3 + .locals init (int V_0, + T V_1) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: stloc.0 + IL_000c: dup + IL_000d: ldloc.0 + IL_000e: ldelem ""T"" + IL_0013: stloc.1 + IL_0014: ldloca.s V_1 + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: constrained. ""T"" + IL_0022: callvirt ""void I1." + methodName + @"(long)"" + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: stelem ""T"" + IL_002e: ldloc.1 + IL_002f: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + methodName = CompoundAssignmentOperatorName(op, isChecked: true); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 44 (0x2c) + .maxstack 3 + .locals init (C1 V_0) + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: dup + IL_0011: ldobj ""C1"" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call ""int Program.G1()"" + IL_001e: conv.i8 + IL_001f: call ""void C1." + methodName + @"(long)"" + IL_0024: ldloc.0 + IL_0025: stobj ""C1"" + IL_002a: ldloc.0 + IL_002b: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 48 (0x30) + .maxstack 3 + .locals init (int V_0, + T V_1) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: stloc.0 + IL_000c: dup + IL_000d: ldloc.0 + IL_000e: ldelem ""T"" + IL_0013: stloc.1 + IL_0014: ldloca.s V_1 + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: constrained. ""T"" + IL_0022: callvirt ""void I1." + methodName + @"(long)"" + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: stelem ""T"" + IL_002e: ldloc.1 + IL_002f: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @", Checked) (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,16): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 16), + // (30,20): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 20), + // (36,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 16), + // (43,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 20), + // (49,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 16), + // (56,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 20) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00731_Consumption_Used_Struct([CombinatorialValues("+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(long x); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"(long x) + { + System.Console.Write(""[operator]""); + _F = _F + (int)x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1[] x = [new C1()]; + + static void Main() + { + C1 y = Test1(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test2(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test3(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test4(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test5(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + y = Test6(x); + System.Console.Write(y._F); + System.Console.WriteLine(x[0]._F); + } + + static C1 Test1(C1[] x) + { +#line 23 + return GetA(x)[Get0()]" + op + @" G1(); + } + + static C1 Test2(C1[] x) + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test3(T[] x) where T : struct, I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test4(T[] x) where T : struct, I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T Test5(T[] x) where T : I1 + { + return GetA(x)[Get0()]" + op + @" G1(); + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return GetA(x)[Get0()]" + op + @" G1(); + } + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } + + static int G1() + { + System.Console.Write(""[G1]""); + x[0] = new C1() { _F = -1 }; + return 1; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][G1][operator]11 +[GetA][Get0][G1][operator]22 +[GetA][Get0][G1][operator]33 +[GetA][Get0][G1][operator]44 +[GetA][Get0][G1][operator]55 +[GetA][Get0][G1][operator]66 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = CompoundAssignmentOperatorName(op, isChecked: false); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 44 (0x2c) + .maxstack 3 + .locals init (C1 V_0) + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: dup + IL_0011: ldobj ""C1"" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call ""int Program.G1()"" + IL_001e: conv.i8 + IL_001f: call ""void C1." + methodName + @"(long)"" + IL_0024: ldloc.0 + IL_0025: stobj ""C1"" + IL_002a: ldloc.0 + IL_002b: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 48 (0x30) + .maxstack 3 + .locals init (int V_0, + T V_1) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: stloc.0 + IL_000c: dup + IL_000d: ldloc.0 + IL_000e: ldelem ""T"" + IL_0013: stloc.1 + IL_0014: ldloca.s V_1 + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: constrained. ""T"" + IL_0022: callvirt ""void I1." + methodName + @"(long)"" + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: stelem ""T"" + IL_002e: ldloc.1 + IL_002f: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("C1", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void C1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: C1) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: +IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: C1) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (C1[] Program.GetA(C1[] x)) (OperationKind.Invocation, Type: C1[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: C1[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: +IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 44 (0x2c) + .maxstack 3 + .locals init (C1 V_0) + IL_0000: ldarg.0 + IL_0001: call ""C1[] Program.GetA(C1[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: ldelema ""C1"" + IL_0010: dup + IL_0011: ldobj ""C1"" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call ""int Program.G1()"" + IL_001e: conv.i8 + IL_001f: call ""void C1." + methodName + @"(long)"" + IL_0024: ldloc.0 + IL_0025: stobj ""C1"" + IL_002a: ldloc.0 + IL_002b: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 48 (0x30) + .maxstack 3 + .locals init (int V_0, + T V_1) + IL_0000: ldarg.0 + IL_0001: call ""T[] Program.GetA(T[])"" + IL_0006: call ""int Program.Get0()"" + IL_000b: stloc.0 + IL_000c: dup + IL_000d: ldloc.0 + IL_000e: ldelem ""T"" + IL_0013: stloc.1 + IL_0014: ldloca.s V_1 + IL_0016: call ""int Program.G1()"" + IL_001b: conv.i8 + IL_001c: constrained. ""T"" + IL_0022: callvirt ""void I1." + methodName + @"(long)"" + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: stelem ""T"" + IL_002e: ldloc.1 + IL_002f: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Where(n => n.OperatorToken.Text == op).Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "(System.Int64 x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("T", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +ICompoundAssignmentOperation (BinaryOperatorKind." + CompoundAssignmentOperatorToBinaryOperatorKind(op) + @") (OperatorMethod: void I1." + methodName + @"(System.Int64 x)) (OperationKind.CompoundAssignment, Type: T) (Syntax: 'GetA(x)[Get0()]" + op + @" G1()') + InConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: T) (Syntax: 'GetA(x)[Get0()]') + Array reference: + IInvocationOperation (T[] Program.GetA(T[] x)) (OperationKind.Invocation, Type: T[]) (Syntax: 'GetA(x)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Indices(1): + IInvocationOperation (System.Int32 Program.Get0()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Get0()') + Instance Receiver: + null + Arguments(0) + Right: + IConversionOperation (TryCast: False, Checked) (OperationKind.Conversion, Type: System.Int64, IsImplicit) (Syntax: 'G1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IInvocationOperation (System.Int32 Program.G1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'G1()') + Instance Receiver: + null + Arguments(0) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + var expectedErrors = new[] { + // (23,16): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(23, 16), + // (30,20): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "C1", "int").WithLocation(30, 20), + // (36,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(36, 16), + // (43,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(43, 20), + // (49,16): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(49, 16), + // (56,20): error CS0019: Operator '+=' cannot be applied to operands of type 'T' and 'int' + // return GetA(x)[Get0()]+= G1(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()]" + op + @" G1()").WithArguments(op, "T", "int").WithLocation(56, 20) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00800_Consumption_CheckedVersionInRegularContext([CombinatorialValues("+=", "-=", "*=", "/=")] string op) + { + var source1 = @" +public class C1 +{ + public int _F; + public void operator checked " + op + @"(int x) + { + System.Console.Write(""[operator]""); + _F+=x; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test1(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test1(C1[] x) + { + GetA(x)[Get0()] " + op + @" 1; + } + + static T[] GetA(T[] x) + { + System.Console.Write(""[GetA]""); + return x; + } + + static int Get0() + { + System.Console.Write(""[Get0]""); + return 0; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [comp1.ToMetadataReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (13,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // GetA(x)[Get0()] += 1; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "GetA(x)[Get0()] " + op + @" 1").WithArguments(op, "C1", "int").WithLocation(13, 9) + ); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00810_Consumption_Shadowing([CombinatorialValues("+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=")] string op) + { + string checkedForm = null; + + if (CompoundAssignmentOperatorHasCheckedForm(op)) + { + checkedForm = @" + public void operator checked" + op + @"(int x) => throw null; +"; + } + + var source1 = @" +public class C1 +{ + public void operator" + op + @"(int x) => throw null; +" + checkedForm + @" +} + +public class C2 : C1 +{ + public new void operator" + op + @"(int x) + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C2(); + x " + op + @" 1; + checked + { + x " + op + @" 1; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][operator]").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_082_Consumption_Shadowing([CombinatorialValues("+=", "-=", "*=", "/=")] string op) + { + var source1_1 = @" +public class C1 +{ + public void operator" + op + @"(int x) => throw null; + public void operator checked" + op + @"(int x) => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"(int x) => throw null; +} +"; + + var comp1_1 = CreateCompilation(source1_1, assemblyName: "C"); + + var source2 = @" +public class Test +{ + public static void Main() + { + var x = new C2(); + x " + op + @" 1; + checked + { + x " + op + @" 1; + } + } +} +"; + + var comp2 = CreateCompilation(source2, references: [comp1_1.ToMetadataReference()]); + comp2.VerifyDiagnostics(); + + var source1_2 = @" +public class C1 +{ + public void operator" + op + @"(int x) + { + System.Console.Write(""[operator]""); + } + + public void operator checked" + op + @"(int x) => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"(int x) + { + System.Console.Write(""[checked operator]""); + } + + public new void operator " + op + @"(int x) => throw null; +} +"; + + var source3 = @" +public class Program +{ + static void Main() + { + Test.Main(); + } +} +"; + var comp1_2 = CreateCompilation(source1_2, assemblyName: "C"); + + var comp3 = CreateCompilation(source3, references: [comp1_2.EmitToImageReference(), comp2.EmitToImageReference()], options: TestOptions.DebugExe); + CompileAndVerify(comp3, expectedOutput: "[operator][checked operator]").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00830_Consumption_Shadowing([CombinatorialValues("+=", "-=", "*=", "/=")] string op) + { + var source1 = @" +public abstract class C1 +{ + public abstract void operator" + op + @"(int x); + public void operator checked" + op + @"(int x) => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"(int x) + { + System.Console.Write(""[checked operator]""); + } + + public override void operator " + op + @"(int x) + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C2(); + x " + op + @" 1; + checked + { + x " + op + @" 1; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][checked operator]").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00840_Consumption_Overriding([CombinatorialValues("+=", "-=", "*=", "/=")] string op) + { + var source1 = @" +public abstract class C1 +{ + public abstract void operator" + op + @"(int x); + public abstract void operator checked" + op + @"(int x); +} + +public abstract class C2 : C1 +{ + public override void operator checked " + op + @"(int x) + { + System.Console.Write(""[checked operator]""); + } +} + +public class C3 : C2 +{ + public override void operator " + op + @"(int x) + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C3(); + x " + op + @" 1; + checked + { + x " + op + @" 1; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][checked operator]").VerifyDiagnostics(); + } + + [Fact] + public void CompoundAssignment_00850_Consumption_Ambiguity() + { + var source1 = @" +public interface I1 +{ + public void operator +=(int x); +} + +public interface I2 where T : I2 +{ + public void operator +=(int x); + public abstract static T operator +(T x, int y); + public abstract static T operator -(T x, int y); +} + +public class Program +{ + static void Test5(T x) where T : I1, I2 + { + x += 1; + x -= 1; + } +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.Net90); + comp1.VerifyDiagnostics( + // (18,11): error CS0121: The call is ambiguous between the following methods or properties: 'I1.operator +=(int)' and 'I2.operator +=(int)' + // x += 1; + Diagnostic(ErrorCode.ERR_AmbigCall, "+=").WithArguments("I1.operator +=(int)", "I2.operator +=(int)").WithLocation(18, 11) + ); + + var tree = comp1.SyntaxTrees.Single(); + var model = comp1.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Null(symbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, symbolInfo.CandidateReason); + Assert.Equal(2, symbolInfo.CandidateSymbols.Length); + Assert.Equal("void I1.op_AdditionAssignment(System.Int32 x)", symbolInfo.CandidateSymbols[0].ToTestDisplayString()); + Assert.Equal("void I2.op_AdditionAssignment(System.Int32 x)", symbolInfo.CandidateSymbols[1].ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + opNode = tree.GetRoot().DescendantNodes().OfType().Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("T I2.op_Subtraction(T x, System.Int32 y)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + } + + [Fact] + public void CompoundAssignment_00860_Consumption_UseStaticOperatorsInOldVersion() + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator +=(int x) + { + System.Console.Write(""[instance operator]""); + _F+=x; + } + + public void operator checked +=(int x) + { + System.Console.Write(""[instance operator checked]""); + checked + { + _F+=x; + } + } + + public static C1 operator +(C1 x, int y) + { + System.Console.Write(""[static operator]""); + return new C1() { _F = x._F + y }; + } + public static C1 operator checked +(C1 x, int y) + { + System.Console.Write(""[static operator checked]""); + checked + { + return new C1() { _F = x._F + y }; + } + } +} +"; + var comp1Ref = CreateCompilation(source1).EmitToImageReference(); + + var source2 = @" +public class Program +{ + static C1 P = new C1(); + + static void Main() + { + C1 x; + + P += 1; + System.Console.WriteLine(P._F); + x = P += 1; + System.Console.WriteLine(P._F); + + checked + { + P += 1; + System.Console.WriteLine(P._F); + x = P += 1; + System.Console.WriteLine(P._F); + } + } +} +"; + + var comp2 = CreateCompilation(source2, references: [comp1Ref], options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: @" +[instance operator]1 +[instance operator]2 +[instance operator checked]3 +[instance operator checked]4 +").VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [comp1Ref], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp2, expectedOutput: @" +[static operator]1 +[static operator]2 +[static operator checked]3 +[static operator checked]4 +").VerifyDiagnostics(); + } + + [Fact] + public void CompoundAssignment_00870_Consumption_Obsolete() + { + var source1 = @" +public class C1 +{ + [System.Obsolete(""Test"")] + public void operator +=(int x) {} +} + +public class Program +{ + static void Main() + { + var x = new C1(); + x += 1; + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1).VerifyDiagnostics( + // (13,9): warning CS0618: 'C1.operator +=(int)' is obsolete: 'Test' + // x += 1; + Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "x += 1").WithArguments("C1.operator +=(int)", "Test").WithLocation(13, 9) + ); + } + + [Fact] + public void CompoundAssignment_00880_Consumption_UnmanagedCallersOnly() + { + var source1 = @" +public class C1 +{ + [System.Runtime.InteropServices.UnmanagedCallersOnly] + public void operator +=(int x) {} +} + +public class Program +{ + static void Main() + { + var x = new C1(); + x += 1; + } +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.Net90, options: TestOptions.DebugExe); + comp1.VerifyDiagnostics( + // (4,6): error CS8896: 'UnmanagedCallersOnly' can only be applied to ordinary static non-abstract, non-virtual methods or static local functions. + // [System.Runtime.InteropServices.UnmanagedCallersOnly] + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyRequiresStatic, "System.Runtime.InteropServices.UnmanagedCallersOnly").WithLocation(4, 6), + // (13,9): error CS8901: 'C1.operator +=(int)' is attributed with 'UnmanagedCallersOnly' and cannot be called directly. Obtain a function pointer to this method. + // x += 1; + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "x += 1").WithArguments("C1.operator +=(int)").WithLocation(13, 9) + ); + } + + [Fact] + public void CompoundAssignment_00890_Consumption_NullableAnalysis() + { + var source1 = @" +public class C1 +{ + public void operator +=(int x) {} +} + +#nullable enable + +public class Program +{ + static void Main() + { + C1? x = null; + + try + { + x += 1; + System.Console.Write(""unreachable""); + x.ToString(); + } + catch (System.NullReferenceException) + { + System.Console.Write(""in catch""); + } + + C1? y = new C1(); + y += 1; + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "in catch").VerifyDiagnostics( + // (17,13): warning CS8602: Dereference of a possibly null reference. + // x += 1; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 13) + ); + + var source2 = @" +public class C1 +{ + public void operator +=(int x) {} +} + +#nullable enable + +public class Program +{ + static void Main() + { + C1? x = null; + + if (false) + { + x += 1; + System.Console.Write(""unreachable""); + x.ToString(); + } + + System.Console.Write(""Done""); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + CompileAndVerify(comp2, expectedOutput: "Done").VerifyDiagnostics( + // (17,13): warning CS0162: Unreachable code detected + // x += 1; + Diagnostic(ErrorCode.WRN_UnreachableCode, "x").WithLocation(17, 13) + ); + } + + [Fact] + public void CompoundAssignment_00891_Consumption_NullableAnalysis() + { + var source1 = @" +public class C1 +{ + public void operator +=(T x) {} +} + +#nullable enable + +public class Program +{ + static C1 GetC1(T x) => new C1(); + + static void Main() + { + string? x = null; + var c1 = GetC1(new object()); + + c1 += x; + x.ToString(); + + var c2 = GetC1((object?)null); + c2 += null; + c2 += (string?)null; + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + comp1.VerifyDiagnostics( + // (18,15): warning CS8604: Possible null reference argument for parameter 'x' in 'void C1.operator +=(object x)'. + // c1 += x; + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("x", "void C1.operator +=(object x)").WithLocation(18, 15) + ); + } + + [Fact] + public void CompoundAssignment_00900_Consumption_BadOperator() + { + var source1 = @" +public class C1 +{ + public void operator +=(int a, int x = 0) {} +} +"; + var source2 = @" +public class Program +{ + static void Main() + { + var x = new C1(); + x += 1; + } +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyDiagnostics( + // (4,26): error CS1020: Overloadable binary operator expected + // public void operator +=(int a, int x = 0) {} + Diagnostic(ErrorCode.ERR_OvlBinaryOperatorExpected, "+=").WithLocation(4, 26), + // (4,40): warning CS1066: The default value specified for parameter 'x' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments + // public void operator +=(int a, int x = 0) {} + Diagnostic(ErrorCode.WRN_DefaultValueForUnconsumedLocation, "x").WithArguments("x").WithLocation(4, 40) + ); + + var comp2 = CreateCompilation(source2, references: [comp1.ToMetadataReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (7,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // x += 1; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x += 1").WithArguments("+=", "C1", "int").WithLocation(7, 9) + ); + } + + [Fact] + public void CompoundAssignment_00910_Consumption_BadOperator() + { + var source1 = @" +public class C1 +{ + public void operator +=(params int[] x) {} +} +"; + var source2 = @" +public class Program +{ + static void Main() + { + var x = new C1(); + x += 1; + } +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyDiagnostics( + // (4,29): error CS1670: params is not valid in this context + // public void operator +=(params int[] x) {} + Diagnostic(ErrorCode.ERR_IllegalParams, "params").WithLocation(4, 29) + ); + + var comp2 = CreateCompilation(source2, references: [comp1.ToMetadataReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (7,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // x += 1; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x += 1").WithArguments("+=", "C1", "int").WithLocation(7, 9) + ); + } + + [Fact] + public void CompoundAssignment_00911_Consumption_BadOperator() + { + var source1 = @" +public class C1 +{ + [System.Runtime.CompilerServices.SpecialName] + public void " + WellKnownMemberNames.AdditionAssignmentOperatorName + @"(params int[] x) {} + + [System.Runtime.CompilerServices.SpecialName] + public void " + WellKnownMemberNames.AdditionAssignmentOperatorName + @"(string x) {} + + [System.Runtime.CompilerServices.SpecialName] + public void " + WellKnownMemberNames.AdditionAssignmentOperatorName + @"(string[] x) {} +} +"; + var source2 = @" +public class Program +{ + static void Main() + { + var x = new C1(); + x += 1; + x += [2]; + x += ""3""; + x += [""4""]; + } +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyEmitDiagnostics(); + + var comp2 = CreateCompilation(source2, references: [comp1.EmitToImageReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (7,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // x += 1; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x += 1").WithArguments("+=", "C1", "int").WithLocation(7, 9), + // (8,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'collection expressions' + // x += [2]; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x += [2]").WithArguments("+=", "C1", "collection expressions").WithLocation(8, 9) + ); + } + + [Fact] + public void CompoundAssignment_00920_ConflictWithRegular() + { + var source1 = @" +public class C1 +{ + public void operator +=(int x) {} + +#line 1000 + public void op_AdditionAssignment(int x) {} +} + +public class C2 +{ + public void op_AdditionAssignment(int x) {} + +#line 2000 + public void operator +=(int x) {} +} + +public class C3 +{ + public void operator +=(int x) {} + + public void op_AdditionAssignment(long x) {} +} + +public class C4 +{ + public void op_AdditionAssignment(int x) {} + + public void operator +=(long x) {} +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyDiagnostics( + // (1000,17): error CS0111: Type 'C1' already defines a member called 'op_AdditionAssignment' with the same parameter types + // public void op_AdditionAssignment(int x) {} + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "op_AdditionAssignment").WithArguments("op_AdditionAssignment", "C1").WithLocation(1000, 17), + // (2000,26): error CS0111: Type 'C2' already defines a member called 'op_AdditionAssignment' with the same parameter types + // public void operator +=(int x) {} + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "+=").WithArguments("op_AdditionAssignment", "C2").WithLocation(2000, 26) + ); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00930_Consumption_RegularVsOperator(bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public void op_AdditionAssignment(int x) {} +} + +public class C2 +{ + public void operator+=(int x) {} +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1 x = new C1(); + x += 1; + C2 y = new C2(); + y.op_AdditionAssignment(1); + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (7,9): error CS0019: Operator '+=' cannot be applied to operands of type 'C1' and 'int' + // x += 1; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x += 1").WithArguments("+=", "C1", "int").WithLocation(7, 9), + // (9,11): error CS0571: 'C2.operator +=(int)': cannot explicitly call operator or accessor + // y.op_AdditionAssignment(1); + Diagnostic(ErrorCode.ERR_CantCallSpecialMethod, "op_AdditionAssignment").WithArguments("C2.operator +=(int)").WithLocation(9, 11) + ); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00940_Override_RegularVsOperatorMismatch(bool fromMetadata) + { + var source1 = @" +abstract public class C1 +{ + public abstract void op_AdditionAssignment(int x); +} + +abstract public class C3 +{ + public abstract void operator +=(int x); +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class C2 : C1 +{ + public override void operator +=(int x) {} +} + +public class C4 : C3 +{ + public override void op_AdditionAssignment(int x) {} +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()]); + + comp2.VerifyDiagnostics( + // (4,35): error CS9505: 'C2.operator +=(int)': cannot override inherited member 'C1.op_AdditionAssignment(int)' because one of them is not an operator. + // public override void operator +=(int x) {} + Diagnostic(ErrorCode.ERR_OperatorMismatchOnOverride, "+=").WithArguments("C2.operator +=(int)", "C1.op_AdditionAssignment(int)").WithLocation(4, 35), + // (9,26): error CS9505: 'C4.op_AdditionAssignment(int)': cannot override inherited member 'C3.operator +=(int)' because one of them is not an operator. + // public override void op_AdditionAssignment(int x) {} + Diagnostic(ErrorCode.ERR_OperatorMismatchOnOverride, "op_AdditionAssignment").WithArguments("C4.op_AdditionAssignment(int)", "C3.operator +=(int)").WithLocation(9, 26) + ); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00950_Implement_RegularVsOperatorMismatch(bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + void op_AdditionAssignment(int x); +} + +public interface I2 +{ + void operator +=(int x); +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class C1 : I1 +{ + public void operator +=(int x) {} +} + +public class C2 : I2 +{ + public void op_AdditionAssignment(int x) {} +} + +public class C3 : I1 +{ + void I1.operator +=(int x) {} +} + +public class C4 : I2 +{ + void I2.op_AdditionAssignment(int x) {} +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()]); + comp2.VerifyDiagnostics( + // (2,19): error CS9504: 'C1' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C1.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C1 : I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C1", "I1.op_AdditionAssignment(int)", "C1.operator +=(int)").WithLocation(2, 19), + // (7,19): error CS9504: 'C2' does not implement interface member 'I2.operator +=(int)'. 'C2.op_AdditionAssignment(int)' cannot implement 'I2.operator +=(int)' because one of them is not an operator. + // public class C2 : I2 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I2").WithArguments("C2", "I2.operator +=(int)", "C2.op_AdditionAssignment(int)").WithLocation(7, 19), + // (12,19): error CS0535: 'C3' does not implement interface member 'I1.op_AdditionAssignment(int)' + // public class C3 : I1 + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "I1").WithArguments("C3", "I1.op_AdditionAssignment(int)").WithLocation(12, 19), + // (14,22): error CS0539: 'C3.operator +=(int)' in explicit interface declaration is not found among members of the interface that can be implemented + // void I1.operator +=(int x) {} + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "+=").WithArguments("C3.operator +=(int)").WithLocation(14, 22), + // (17,19): error CS0535: 'C4' does not implement interface member 'I2.operator +=(int)' + // public class C4 : I2 + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "I2").WithArguments("C4", "I2.operator +=(int)").WithLocation(17, 19), + // (19,13): error CS0539: 'C4.op_AdditionAssignment(int)' in explicit interface declaration is not found among members of the interface that can be implemented + // void I2.op_AdditionAssignment(int x) {} + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "op_AdditionAssignment").WithArguments("C4.op_AdditionAssignment(int)").WithLocation(19, 13) + ); + } + + [Theory] + [CombinatorialData] + public void CompoundAssignment_00960_Implement_RegularVsOperatorMismatch(bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + void op_AdditionAssignment(int x); +} + +public interface I2 +{ + void operator +=(int x); +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class C11 +{ + public void operator +=(int x) {} +} + +public class C12 : C11, I1 +{ +} + +public class C21 +{ + public void op_AdditionAssignment(int x) {} +} + +public class C22 : C21, I2 +{ +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()]); + comp2.VerifyDiagnostics( + // (7,25): error CS9504: 'C12' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C11.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C12 : C11, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C12", "I1.op_AdditionAssignment(int)", "C11.operator +=(int)").WithLocation(7, 25), + // (16,25): error CS9504: 'C22' does not implement interface member 'I2.operator +=(int)'. 'C21.op_AdditionAssignment(int)' cannot implement 'I2.operator +=(int)' because one of them is not an operator. + // public class C22 : C21, I2 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I2").WithArguments("C22", "I2.operator +=(int)", "C21.op_AdditionAssignment(int)").WithLocation(16, 25) + ); + } + + [Fact] + public void CompoundAssignment_00970_Implement_RegularVsOperatorMismatch() + { + var source = @" +public interface I1 +{ + public void operator +=(int x) + { + System.Console.Write(""[I1.operator]""); + } +} + +public class C1 : I1 +{ + public virtual void op_AdditionAssignment(int x) + { + System.Console.Write(""[C1.op_AdditionAssignment]""); + } +} + +public class C2 +{ + public virtual void op_AdditionAssignment(int x) + { + System.Console.Write(""[C2.op_AdditionAssignment]""); + } +} + +public class C3 : C2, I1 +{ +} + +public class Program +{ + static void Main() + { + I1 x = new C1(); + x += 1; + x = new C3(); + x += 1; + } +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (10,19): error CS9504: 'C1' does not implement interface member 'I1.operator +=(int)'. 'C1.op_AdditionAssignment(int)' cannot implement 'I1.operator +=(int)' because one of them is not an operator. + // public class C1 : I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C1", "I1.operator +=(int)", "C1.op_AdditionAssignment(int)").WithLocation(10, 19), + // (26,23): error CS9504: 'C3' does not implement interface member 'I1.operator +=(int)'. 'C2.op_AdditionAssignment(int)' cannot implement 'I1.operator +=(int)' because one of them is not an operator. + // public class C3 : C2, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C3", "I1.operator +=(int)", "C2.op_AdditionAssignment(int)").WithLocation(26, 23) + ); + } + + [Fact] + public void CompoundAssignment_00980_Implement_RegularVsOperatorMismatch() + { + var source = @" +public interface I1 +{ + public void op_AdditionAssignment(int x) + { + System.Console.Write(""[I1.operator]""); + } +} + +public class C1 : I1 +{ + public virtual void operator +=(int x) + { + System.Console.Write(""[C1.operator]""); + } +} + +public class C2 +{ + public virtual void operator +=(int x) + { + System.Console.Write(""[C2.operator]""); + } +} + +public class C3 : C2, I1 +{ +} + +public class Program +{ + static void Main() + { + I1 x = new C1(); + x.op_AdditionAssignment(1); + x = new C3(); + x.op_AdditionAssignment(1); + } +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (10,19): error CS9504: 'C1' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C1.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C1 : I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C1", "I1.op_AdditionAssignment(int)", "C1.operator +=(int)").WithLocation(10, 19), + // (26,23): error CS9504: 'C3' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C2.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C3 : C2, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C3", "I1.op_AdditionAssignment(int)", "C2.operator +=(int)").WithLocation(26, 23) + ); + } + + [Fact] + public void CompoundAssignment_00990_Implement_RegularVsOperatorMismatch() + { + var source = @" +public interface I1 +{ + public void operator +=(int x); +} + +public class C2 : I1 +{ + void I1.operator +=(int x) + { + System.Console.Write(""[C2.operator]""); + } +} + +public class C3 : C2, I1 +{ + public virtual void op_AdditionAssignment(int x) + { + System.Console.Write(""[C3.op_Increment]""); + } +} + +public class Program +{ + static void Main() + { + I1 x = new C3(); + x += 1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (15,23): error CS9504: 'C3' does not implement interface member 'I1.operator +=(int)'. 'C3.op_AdditionAssignment(int)' cannot implement 'I1.operator +=(int)' because one of them is not an operator. + // public class C3 : C2, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C3", "I1.operator +=(int)", "C3.op_AdditionAssignment(int)").WithLocation(15, 23) + ); + } + + [Fact] + public void CompoundAssignment_01000_Implement_RegularVsOperatorMismatch() + { + var source = @" +public interface I1 +{ + public void op_AdditionAssignment(int x); +} + +public class C2 : I1 +{ + void I1.op_AdditionAssignment(int x) + { + System.Console.Write(""[C2.op_Increment]""); + } +} + +public class C3 : C2, I1 +{ + public virtual void operator +=(int x) + { + System.Console.Write(""[C3.operator]""); + } +} + +public class Program +{ + static void Main() + { + I1 x = new C3(); + x.op_AdditionAssignment(1); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (15,23): error CS9504: 'C3' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C3.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C3 : C2, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C3", "I1.op_AdditionAssignment(int)", "C3.operator +=(int)").WithLocation(15, 23) + ); + } + + [Fact] + public void CompoundAssignment_01010_Implement_RegularVsOperatorMismatch() + { + /* + public interface I1 + { + public void operator+=(int x); + } + + public class C1 : I1 + { + public virtual void op_AdditionAssignment(int x) + { + System.Console.Write(1); + } + } + */ + var ilSource = @" +.class interface public auto ansi abstract beforefieldinit I1 +{ + .method public hidebysig newslot abstract virtual specialname + instance void op_AdditionAssignment (int32 x) cil managed + { + } +} + +.class public auto ansi beforefieldinit C1 + extends [mscorlib]System.Object + implements I1 +{ + .method public hidebysig newslot virtual + instance void op_AdditionAssignment (int32 x) cil managed + { + .maxstack 8 + + IL_0000: ldc.i4.1 + IL_0001: call void [mscorlib]System.Console::Write(int32) + IL_0006: ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } +} +"; + + var source1 = +@" +public class C2 : C1, I1 +{ +} +"; + var compilation1 = CreateCompilationWithIL(source1, ilSource); + + compilation1.VerifyDiagnostics( + // (2,23): error CS9504: 'C2' does not implement interface member 'I1.operator +=(int)'. 'C1.op_AdditionAssignment(int)' cannot implement 'I1.operator +=(int)' because one of them is not an operator. + // public class C2 : C1, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C2", "I1.operator +=(int)", "C1.op_AdditionAssignment(int)").WithLocation(2, 23) + ); + + var source2 = +@" +class Program +{ + static void Main() + { + var c1 = new C1(); + c1.op_AdditionAssignment(1); + I1 x = c1; + x += 1; + } +} +"; + var compilation2 = CreateCompilationWithIL(source2, ilSource, options: TestOptions.DebugExe); + + CompileAndVerify(compilation2, expectedOutput: "11", verify: Verification.Skipped).VerifyDiagnostics(); + + var i1M1 = compilation1.GetTypeByMetadataName("I1").GetMembers().Single(); + var c1 = compilation1.GetTypeByMetadataName("C1"); + + AssertEx.Equal("C1.op_AdditionAssignment(int)", c1.FindImplementationForInterfaceMember(i1M1).ToDisplayString()); + } + + [Fact] + public void CompoundAssignment_01020_Implement_RegularVsOperatorMismatch() + { + /* + public interface I1 + { + public void op_AdditionAssignment(int x); + } + + public class C1 : I1 + { + public virtual void operator+=(int x) + { + System.Console.Write(1); + } + } + */ + var ilSource = @" +.class interface public auto ansi abstract beforefieldinit I1 +{ + .method public hidebysig newslot abstract virtual + instance void op_AdditionAssignment (int32 x) cil managed + { + } +} + +.class public auto ansi beforefieldinit C1 + extends [mscorlib]System.Object + implements I1 +{ + .method public hidebysig newslot virtual specialname + instance void op_AdditionAssignment (int32 x) cil managed + { + .maxstack 8 + + IL_0000: ldc.i4.1 + IL_0001: call void [mscorlib]System.Console::Write(int32) + IL_0006: ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } +} +"; + + var source1 = +@" +public class C2 : C1, I1 +{ +} +"; + var compilation1 = CreateCompilationWithIL(source1, ilSource); + + compilation1.VerifyDiagnostics( + // (2,23): error CS9504: 'C2' does not implement interface member 'I1.op_AdditionAssignment(int)'. 'C1.operator +=(int)' cannot implement 'I1.op_AdditionAssignment(int)' because one of them is not an operator. + // public class C2 : C1, I1 + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberOperatorMismatch, "I1").WithArguments("C2", "I1.op_AdditionAssignment(int)", "C1.operator +=(int)").WithLocation(2, 23) + ); + + var source2 = +@" +class Program +{ + static void Main() + { + var c1 = new C1(); + c1 += 1; + I1 x = c1; + x.op_AdditionAssignment(1); + } +} +"; + var compilation2 = CreateCompilationWithIL(source2, ilSource, options: TestOptions.DebugExe); + + CompileAndVerify(compilation2, expectedOutput: "11", verify: Verification.Skipped).VerifyDiagnostics(); + + var i1M1 = compilation1.GetTypeByMetadataName("I1").GetMembers().Single(); + var c1 = compilation1.GetTypeByMetadataName("C1"); + + AssertEx.Equal("C1.operator +=(int)", c1.FindImplementationForInterfaceMember(i1M1).ToDisplayString()); + } + + [Fact] + public void CompoundAssignment_01030_Consumption_Implementation() + { + var source = @" +public interface I1 +{ + public void operator +=(int x); +} + +public class C1 : I1 +{ + public void operator +=(int x) + { + System.Console.Write(""[C1.operator]""); + } +} + +public class C2 : I1 +{ + void I1.operator +=(int x) + { + System.Console.Write(""[C2.operator]""); + } +} + +public class Program +{ + static void Main() + { + I1 x = new C1(); + x += 1; + x = new C2(); + x += 1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "[C1.operator][C2.operator]").VerifyDiagnostics(); + } + + [Fact] + public void CompoundAssignment_01040_Consumption_Overriding() + { + var source = @" +public abstract class C1 +{ + public abstract void operator +=(int x); +} + +public class C2 : C1 +{ + public override void operator +=(int x) + { + System.Console.Write(""[C2.operator]""); + } +} + +public class Program +{ + static void Main() + { + C1 x = new C2(); + x += 1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "[C2.operator]").VerifyDiagnostics(); + } + + [Fact] + public void CompoundAssignment_01050_Shadow_RegularVsOperatorMismatch() + { + var source = @" +public class C1 +{ + public void operator +=(int x){} +} + +public class C2 : C1 +{ + public void op_AdditionAssignment(int x){} +} +"; + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void CompoundAssignment_01060_Shadow_RegularVsOperatorMismatch() + { + var source = @" +public class C2 +{ + public void op_AdditionAssignment(int x){} +} + +public class C1 : C2 +{ + public void operator +=(int x){} +} +"; + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + // PROTOTYPE: Disable ORPA during overload resolution? } }