diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index f4f3d1b54026a..1da8829ba03f5 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -11011,38 +11011,7 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess // For improved diagnostics we detect the cases where the value will be used and produce a // more specific (though not technically correct) diagnostic here: // "Error CS0023: Operator '?' cannot be applied to operand of type 'T'" - bool resultIsUsed = true; - CSharpSyntaxNode parent = node.Parent; - - if (parent != null) - { - switch (parent.Kind()) - { - case SyntaxKind.ExpressionStatement: - resultIsUsed = ((ExpressionStatementSyntax)parent).Expression != node; - break; - - case SyntaxKind.SimpleLambdaExpression: - resultIsUsed = (((SimpleLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); - break; - - case SyntaxKind.ParenthesizedLambdaExpression: - resultIsUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); - break; - - case SyntaxKind.ArrowExpressionClause: - resultIsUsed = (((ArrowExpressionClauseSyntax)parent).Expression != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); - break; - - case SyntaxKind.ForStatement: - // Incrementors and Initializers doesn't have to produce a value - var loop = (ForStatementSyntax)parent; - resultIsUsed = !loop.Incrementors.Contains(node) && !loop.Initializers.Contains(node); - break; - } - } - - if (resultIsUsed) + if (ResultIsUsed(node)) { return GenerateBadConditionalAccessNodeError(node, receiver, access, diagnostics); } @@ -11061,6 +11030,42 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess return new BoundConditionalAccess(node, receiver, access, accessType); } + private bool ResultIsUsed(ExpressionSyntax node) + { + bool resultIsUsed = true; + CSharpSyntaxNode parent = node.Parent; + + if (parent != null) + { + switch (parent.Kind()) + { + case SyntaxKind.ExpressionStatement: + resultIsUsed = ((ExpressionStatementSyntax)parent).Expression != node; + break; + + case SyntaxKind.SimpleLambdaExpression: + resultIsUsed = (((SimpleLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); + break; + + case SyntaxKind.ParenthesizedLambdaExpression: + resultIsUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); + break; + + case SyntaxKind.ArrowExpressionClause: + resultIsUsed = (((ArrowExpressionClauseSyntax)parent).Expression != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation); + break; + + case SyntaxKind.ForStatement: + // Incrementors and Initializers doesn't have to produce a value + var loop = (ForStatementSyntax)parent; + resultIsUsed = !loop.Incrementors.Contains(node) && !loop.Initializers.Contains(node); + break; + } + } + + return resultIsUsed; + } + internal static bool MethodOrLambdaRequiresValue(Symbol symbol, CSharpCompilation compilation) { return symbol is MethodSymbol method && diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index 764423d3d7b8a..77f9eddd3749a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -1399,6 +1399,10 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio { return LookupResult.Empty(); } + else if ((options & LookupOptions.MustBeOperator) != 0 && unwrappedSymbol is not MethodSymbol { MethodKind: MethodKind.UserDefinedOperator }) + { + return LookupResult.Empty(); + } else if (!IsInScopeOfAssociatedSyntaxTree(unwrappedSymbol)) { return LookupResult.Empty(); @@ -1417,7 +1421,7 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio { return LookupResult.WrongArity(symbol, diagInfo); } - else if (!InCref && !unwrappedSymbol.CanBeReferencedByNameIgnoringIllegalCharacters) + else if (!InCref && !unwrappedSymbol.CanBeReferencedByNameIgnoringIllegalCharacters && (options & LookupOptions.MustBeOperator) == 0) { // Strictly speaking, this test should actually check CanBeReferencedByName. // However, we don't want to pay that cost in cases where the lookup is based diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 0babb502aa4a3..4487f8d46fc1d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -2262,7 +2263,9 @@ public static BinaryOperatorKind SyntaxKindToBinaryOperatorKind(SyntaxKind kind) } } - private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionSyntax operandSyntax, SyntaxToken operatorToken, BindingDiagnosticBag diagnostics) +#nullable enable + + private BoundExpression BindIncrementOperator(ExpressionSyntax node, ExpressionSyntax operandSyntax, SyntaxToken operatorToken, BindingDiagnosticBag diagnostics) { operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics); @@ -2290,7 +2293,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS // The operand has to be a variable, property or indexer, so it must have a type. var operandType = operand.Type; - Debug.Assert((object)operandType != null); + Debug.Assert(operandType is not null); if (operandType.IsDynamic()) { @@ -2310,6 +2313,13 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS hasErrors: false); } + // Try an in-place user-defined operator + BoundIncrementOperator? inPlaceResult = tryApplyUserDefinedInstanceOperator(node, operatorToken, kind, operand, diagnostics); + if (inPlaceResult is not null) + { + return inPlaceResult; + } + LookupResultKind resultKind; ImmutableArray originalUserDefinedOperators; var best = this.UnaryOperatorOverloadResolution(kind, operand, node, diagnostics, out resultKind, out originalUserDefinedOperators); @@ -2339,7 +2349,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS var resultPlaceholder = new BoundValuePlaceholder(node, signature.ReturnType).MakeCompilerGenerated(); - BoundExpression resultConversion = GenerateConversionForAssignment(operandType, resultPlaceholder, diagnostics, ConversionForAssignmentFlags.IncrementAssignment); + BoundExpression? resultConversion = GenerateConversionForAssignment(operandType, resultPlaceholder, diagnostics, ConversionForAssignmentFlags.IncrementAssignment); bool hasErrors = resultConversion.HasErrors; @@ -2376,6 +2386,231 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS originalUserDefinedOperators, operandType, hasErrors); + + BoundIncrementOperator? tryApplyUserDefinedInstanceOperator(ExpressionSyntax node, SyntaxToken operatorToken, UnaryOperatorKind kind, BoundExpression operand, BindingDiagnosticBag diagnostics) + { + var operandType = operand.Type; + Debug.Assert(operandType is not null); + Debug.Assert(!operandType.IsDynamic()); + + if (kind is not (UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PrefixDecrement or UnaryOperatorKind.PostfixIncrement or UnaryOperatorKind.PostfixDecrement) || + operandType.SpecialType.IsNumericType() || + !node.IsFeatureEnabled(MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators)) + { + return null; + } + + bool resultIsUsed = ResultIsUsed(node); + + if ((kind is (UnaryOperatorKind.PostfixIncrement or UnaryOperatorKind.PostfixDecrement) && resultIsUsed) || + !CheckValueKind(node, operand, BindValueKind.RefersToLocation | BindValueKind.Assignable, checkingReceiver: false, BindingDiagnosticBag.Discarded)) + { + return null; + } + + bool checkOverflowAtRuntime = CheckOverflowAtRuntime; + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + ArrayBuilder? methods = lookupUserDefinedInstanceOperators( + operandType, + checkedName: checkOverflowAtRuntime ? + (kind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PostfixIncrement ? + WellKnownMemberNames.CheckedIncrementOperatorName : + WellKnownMemberNames.CheckedDecrementOperatorName) : + null, + ordinaryName: kind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PostfixIncrement ? + WellKnownMemberNames.IncrementOperatorName : + WellKnownMemberNames.DecrementOperatorName, + 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(); + + OverloadResolution.MethodInvocationOverloadResolution( + methods, + typeArguments, + operand, + analyzedArguments, + overloadResolutionResult, + ref useSiteInfo, + OverloadResolution.Options.None); + + typeArguments.Free(); + diagnostics.Add(node, useSiteInfo); + + BoundIncrementOperator? inPlaceResult; + + if (overloadResolutionResult.Succeeded) + { + var method = overloadResolutionResult.ValidResult.Member; + + ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false); + ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false); + + inPlaceResult = new BoundIncrementOperator( + node, + (kind | UnaryOperatorKind.UserDefined).WithOverflowChecksIfApplicable(checkOverflowAtRuntime), + operand, + methodOpt: method, + constrainedToTypeOpt: null, + operandPlaceholder: null, + operandConversion: null, + resultPlaceholder: null, + resultConversion: null, + LookupResultKind.Viable, + ImmutableArray.Empty, + resultIsUsed ? operandType : GetSpecialType(SpecialType.System_Void, diagnostics, node)); + + 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: operand, invokedExpression: node, arguments: analyzedArguments, memberGroup: methodsArray, + typeContainingConstructor: null, delegateTypeBeingInvoked: null); + + inPlaceResult = new BoundIncrementOperator( + node, + (kind | UnaryOperatorKind.UserDefined).WithOverflowChecksIfApplicable(checkOverflowAtRuntime), + operand, + methodOpt: null, + constrainedToTypeOpt: null, + operandPlaceholder: null, + operandConversion: null, + resultPlaceholder: null, + resultConversion: null, + LookupResultKind.OverloadResolutionFailure, + methodsArray, + resultIsUsed ? operandType : GetSpecialType(SpecialType.System_Void, diagnostics, node)); + } + else + { + inPlaceResult = null; + methods.Free(); + } + + analyzedArguments.Free(); + overloadResolutionResult.Free(); + + 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); + + if (lookupResult.IsMultiViable) + { + methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); + appendViableMethods(lookupResult, methods); + } + + lookupResult.Clear(); + } + + this.LookupMembersWithFallback(lookupResult, lookupInType, name: ordinaryName, arity: 0, ref useSiteInfo, basesBeingResolved: null, options: LookupOptions.MustBeInstance | LookupOptions.MustBeOperator); + + if (lookupResult.IsMultiViable) + { + if (methods is null) + { + methods = ArrayBuilder.GetInstance(lookupResult.Symbols.Count); + appendViableMethods(lookupResult, methods); + } + else + { + var existing = new HashSet(PairedOperatorComparer.Instance); + + foreach (var method in methods) + { + existing.Add(method.GetLeastOverriddenMethod(ContainingType)); + } + + foreach (MethodSymbol method in lookupResult.Symbols) + { + if (isViable(method) && !existing.Contains(method.GetLeastOverriddenMethod(ContainingType))) + { + methods.Add(method); + } + } + } + } + + lookupResult.Free(); + + return methods; + + static void appendViableMethods(LookupResult lookupResult, ArrayBuilder methods) + { + foreach (MethodSymbol method in lookupResult.Symbols) + { + if (isViable(method)) + { + methods.Add(method); + } + } + } + + static bool isViable(MethodSymbol method) + { + return method.ParameterCount == 0; + } + } + } + +#nullable disable + + private class PairedOperatorComparer : IEqualityComparer + { + public static readonly PairedOperatorComparer Instance = new PairedOperatorComparer(); + + private PairedOperatorComparer() { } + + public bool Equals(MethodSymbol x, MethodSymbol y) + { + Debug.Assert(!x.IsOverride); + Debug.Assert(!x.IsStatic); + + Debug.Assert(!y.IsOverride); + Debug.Assert(!y.IsStatic); + + var typeComparer = Symbols.SymbolEqualityComparer.AllIgnoreOptions; + return typeComparer.Equals(x.ContainingType, y.ContainingType) && + SourceMemberContainerTypeSymbol.DoOperatorsPair(x, y); + } + + public int GetHashCode([DisallowNull] MethodSymbol method) + { + Debug.Assert(!method.IsOverride); + Debug.Assert(!method.IsStatic); + + var typeComparer = Symbols.SymbolEqualityComparer.AllIgnoreOptions; + int result = typeComparer.GetHashCode(method.ContainingType); + + if (method.ParameterTypesWithAnnotations is [var typeWithAnnotations, ..]) + { + result = Hash.Combine(result, typeComparer.GetHashCode(typeWithAnnotations.Type)); + } + + return result; + } } #nullable enable diff --git a/src/Compilers/CSharp/Portable/Binder/LookupOptions.cs b/src/Compilers/CSharp/Portable/Binder/LookupOptions.cs index e85082a4068e2..8fd72ce78cba6 100644 --- a/src/Compilers/CSharp/Portable/Binder/LookupOptions.cs +++ b/src/Compilers/CSharp/Portable/Binder/LookupOptions.cs @@ -110,6 +110,11 @@ internal enum LookupOptions /// Do not consider symbols that are parameters. /// MustNotBeParameter = 1 << 16, + + /// + /// Consider only symbols that are user-defined operators. + /// + MustBeOperator = 1 << 17, } internal static class LookupOptionExtensions diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 080cf2cab0b78..f1cca38ea7cb3 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -10474,6 +10474,16 @@ 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; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ExpressionStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ExpressionStatement.cs index 65fe248a66cde..f6172e528e36a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ExpressionStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ExpressionStatement.cs @@ -74,6 +74,9 @@ public override BoundNode VisitExpressionStatement(BoundExpressionStatement node case BoundKind.ConditionalAccess: return RewriteConditionalAccess((BoundConditionalAccess)expression, used: false); + + case BoundKind.IncrementOperator: + return VisitIncrementOperator((BoundIncrementOperator)expression, used: false); } return VisitExpression(expression); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 5ebaf25a2cc57..1225ad402ab87 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -384,6 +384,105 @@ private static bool IsPrefix(BoundIncrementOperator node) return op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement; } + public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) + { + return VisitIncrementOperator(node, used: true); + } + + private BoundExpression VisitIncrementOperator(BoundIncrementOperator node, bool used) + { + if (node.MethodOpt?.IsStatic == false) + { + return VisitInstanceIncrementOperator(node, used); + } + else + { + return VisitBuiltInOrStaticIncrementOperator(node); + } + } + + private BoundExpression VisitInstanceIncrementOperator(BoundIncrementOperator node, bool used) + { + Debug.Assert(node.MethodOpt is { }); + + SyntaxNode syntax = node.Syntax; + + if (!used) + { + Debug.Assert(node.Type.IsVoidType()); + return BoundCall.Synthesized(syntax, VisitExpression(node.Operand), initialBindingReceiverIsSubjectToCloning: ThreeState.False, node.MethodOpt); + } + + TypeSymbol? operandType = node.Operand.Type; //type of the variable being incremented + Debug.Assert(operandType is { }); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.AllIgnoreOptions)); + + if (!IsPrefix(node)) + { + throw ExceptionUtilities.Unreachable(); + } + + BoundAssignmentOperator tempAssignment; + BoundLocal boundTemp; + + if (operandType.IsReferenceType) + { + boundTemp = _factory.StoreToTemp(VisitExpression(node.Operand), out tempAssignment); + return new BoundSequence( + syntax: syntax, + locals: [boundTemp.LocalSymbol], + sideEffects: [tempAssignment, BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.False, node.MethodOpt)], + value: boundTemp, + type: operandType); + } + + 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); + Debug.Assert(TypeSymbol.Equals(operandType, transformedLHS.Type, TypeCompareKind.AllIgnoreOptions)); + + 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); + + if (operandType.IsValueType) + { + tempInitializers.Add(increment); + tempInitializers.Add(assignBack); + } + else + { + // (object)default(T) != null + var isNotClass = _factory.IsNotNullReference(_factory.Default(operandType)); + tempInitializers.Add( + _factory.Conditional( + isNotClass, + new BoundSequence( + syntax: syntax, + locals: [], + sideEffects: [increment, assignBack], + value: boundTemp, + type: operandType), + increment, + operandType, + isRef: false)); + } + + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree(), + value: boundTemp, + type: operandType); + } + /// /// The rewrites are as follows: suppose the operand x is a variable of type X. The /// chosen increment/decrement operator is modelled as a static method on a type T, @@ -422,7 +521,7 @@ private static bool IsPrefix(BoundIncrementOperator node) /// /// The unary operator expression representing the increment/decrement. /// A bound sequence that uses a temp to achieve the correct side effects and return value. - public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) + public BoundExpression VisitBuiltInOrStaticIncrementOperator(BoundIncrementOperator node) { bool isPrefix = IsPrefix(node); bool isDynamic = node.OperatorKind.IsDynamic(); @@ -452,7 +551,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) - var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); + var newValue = makeBuiltInOrStaticIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating @@ -566,7 +665,7 @@ BoundExpression rewriteWithRefOperand( type: boundTemp.Type); } - BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) + BoundExpression makeBuiltInOrStaticIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { if (node.OperatorKind.IsDynamic()) { @@ -576,7 +675,7 @@ BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpressi BoundExpression result; if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) { - result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + result = MakeUserDefinedStaticIncrementOperator(node, rewrittenValueToIncrement); } else { @@ -627,7 +726,7 @@ private BoundExpression ApplyConversion(BoundExpression conversion, BoundValuePl return replacement; } - private BoundExpression MakeUserDefinedIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) + private BoundExpression MakeUserDefinedStaticIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { Debug.Assert(node.MethodOpt is { }); Debug.Assert(node.MethodOpt.ParameterCount == 1); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index abb58e2d51de5..4c7d1716afb69 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2550,6 +2550,11 @@ private static void CheckForUnmatchedOperator( { foreach (var op1 in ops1) { + if (op1.IsOverride) + { + continue; + } + bool foundMatch = false; foreach (var op2 in ops2) { diff --git a/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs b/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs index 44ab1f88cf440..b7dbf8f14b46c 100644 --- a/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Symbols/UserDefinedCompoundAssignmentOperatorsTests.cs @@ -1265,7 +1265,7 @@ class C3 : C2 } "; var comp = CreateCompilation(source); - CompileAndVerify(comp, symbolValidator: validate, sourceSymbolValidator: validate).VerifyDiagnostics(); + CompileAndVerify(comp, symbolValidator: validate1, sourceSymbolValidator: validate1).VerifyDiagnostics(); var source2 = @" abstract class C1 @@ -1281,14 +1281,9 @@ class C2 : C1 "; var comp2 = CreateCompilation(source2); - // PROTOTYPE: The error below is not expected. One should be able to override just one version of the operator. - comp2.VerifyDiagnostics( - // (10,42): error CS9025: The operator 'C2.operator checked ++()' requires a matching non-checked version of the operator to also be defined - // public override void operator checked++() {} - Diagnostic(ErrorCode.ERR_CheckedOperatorNeedsMatch, op).WithArguments("C2.operator checked " + op + @"()").WithLocation(10, 42) - ); + CompileAndVerify(comp2, symbolValidator: validate2, sourceSymbolValidator: validate2).VerifyDiagnostics(); - void validate(ModuleSymbol m) + void validate1(ModuleSymbol m) { validateOp( m.GlobalNamespace.GetMember("C2." + (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName)), @@ -1305,6 +1300,13 @@ void validate(ModuleSymbol m) m.GlobalNamespace.GetMember("C2." + (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName))); } + void validate2(ModuleSymbol m) + { + validateOp( + m.GlobalNamespace.GetMember("C2." + (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName)), + m.GlobalNamespace.GetMember("C1." + (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName))); + } + static void validateOp(MethodSymbol m, MethodSymbol overridden) { Assert.Equal(MethodKind.UserDefinedOperator, m.MethodKind); @@ -2578,13 +2580,7 @@ class C4 : C2 Diagnostic(ErrorCode.ERR_OverrideNotNew, op).WithArguments("C3.operator " + op + @"()").WithLocation(15, 38), // (20,47): error CS0113: A member 'C4.operator checked ++()' marked as override cannot be marked as new or virtual // public new override void operator checked ++() {} - Diagnostic(ErrorCode.ERR_OverrideNotNew, op).WithArguments("C4.operator checked " + op + @"()").WithLocation(20, 47), - - // PROTOTYPE: The error below is not expected. One should be able to override just one version of the operator. - - // (20,47): error CS9025: The operator 'C4.operator checked ++()' requires a matching non-checked version of the operator to also be defined - // public new override void operator checked ++() {} - Diagnostic(ErrorCode.ERR_CheckedOperatorNeedsMatch, op).WithArguments("C4.operator checked " + op + @"()").WithLocation(20, 47) + Diagnostic(ErrorCode.ERR_OverrideNotNew, op).WithArguments("C4.operator checked " + op + @"()").WithLocation(20, 47) ); } @@ -2798,5 +2794,2432 @@ interface I4 : I2 Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, op).WithArguments("I4.operator checked " + op + @"()").WithLocation(20, 37) ); } + + [Theory] + [CombinatorialData] + public void Increment_069_Consumption_OnNonVariable([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator" + op + @"() => throw null; + public void operator checked" + op + @"() => throw null; + + public static C1 operator" + op + @"(C1 x) + { + System.Console.Write(""[operator]""); + return new C1() { _F = x._F + 1 }; + } + public static C1 operator checked" + op + @"(C1 x) + { + System.Console.Write(""[operator checked]""); + checked + { + return new C1() { _F = x._F + 1 }; + } + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static C1 P {get; set;} = new C1(); + + static void Main() + { + C1 x; + + " + op + @"P; + System.Console.WriteLine(P._F); + P" + op + @"; + System.Console.WriteLine(P._F); + x = " + op + @"P; + System.Console.WriteLine(P._F); + x = P" + op + @"; + System.Console.WriteLine(P._F); + + checked + { + " + op + @"P; + System.Console.WriteLine(P._F); + P" + op + @"; + System.Console.WriteLine(P._F); + x = " + op + @"P; + System.Console.WriteLine(P._F); + x = P" + op + @"; + 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 +[operator checked]5 +[operator checked]6 +[operator checked]7 +[operator checked]8 +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_070_Consumption_Prefix_NotUsed_Class([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + 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) + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test2(C1[] x) + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + static void Test3(T[] x) where T : class, I1 + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test4(T[] x) where T : class, I1 + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + static void Test5(T[] x) where T : I1 + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test6(T[] x) where T : I1 + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + const string expectedOutput = @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 20 (0x14) + .maxstack 2 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call ""C1[] Program.GetA(C1[])"" + IL_0007: call ""int Program.Get0()"" + IL_000c: ldelem.ref + IL_000d: callvirt ""void C1." + methodName + @"()"" + IL_0012: nop + IL_0013: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 29 (0x1d) + .maxstack 2 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call ""T[] Program.GetA(T[])"" + IL_0007: call ""int Program.Get0()"" + IL_000c: ldelem ""T"" + IL_0011: box ""T"" + IL_0016: callvirt ""void I1." + methodName + @"()"" + IL_001b: nop + IL_001c: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 32 (0x20) + .maxstack 2 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call ""T[] Program.GetA(T[])"" + IL_0007: call ""int Program.Get0()"" + IL_000c: readonly. + IL_000e: ldelema ""T"" + IL_0013: constrained. ""T"" + IL_0019: callvirt ""void I1." + methodName + @"()"" + IL_001e: nop + IL_001f: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "()", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("System.Void", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +IIncrementOrDecrementOperation (Prefix) (OperatorMethod: void C1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: System.Void) (Syntax: '" + op + @"GetA(x)[Get0()]') + Target: + 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) +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 22 (0x16) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""C1[] Program.GetA(C1[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: ldelem.ref + IL_000e: callvirt ""void C1." + methodName + @"()"" + IL_0013: nop + IL_0014: nop + IL_0015: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 31 (0x1f) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""T[] Program.GetA(T[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: ldelem ""T"" + IL_0012: box ""T"" + IL_0017: callvirt ""void I1." + methodName + @"()"" + IL_001c: nop + IL_001d: nop + IL_001e: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""T[] Program.GetA(T[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: readonly. + IL_000f: ldelema ""T"" + IL_0014: constrained. ""T"" + IL_001a: callvirt ""void I1." + methodName + @"()"" + IL_001f: nop + IL_0020: nop + IL_0021: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "()", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("System.Void", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +IIncrementOrDecrementOperation (Prefix, Checked) (OperatorMethod: void I1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: System.Void) (Syntax: '" + op + @"GetA(x)[Get0()]') + Target: + 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) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + 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.RegularNext); + verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + + var expectedErrors = new[] { + // (23,9): error CS0023: Operator '++' cannot be applied to operand of type 'C1' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "C1").WithLocation(23, 9), + // (30,13): error CS0023: Operator '++' cannot be applied to operand of type 'C1' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "C1").WithLocation(30, 13), + // (36,9): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "T").WithLocation(36, 9), + // (43,13): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "T").WithLocation(43, 13), + // (49,9): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "T").WithLocation(49, 9), + // (56,13): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op +"GetA(x)[Get0()]").WithArguments(op, "T").WithLocation(56, 13) + }; + comp2.VerifyDiagnostics(expectedErrors); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular13); + comp2.VerifyDiagnostics(expectedErrors); + } + + [Theory] + [CombinatorialData] + public void Increment_071_Consumption_Prefix_NotUsed_Struct([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + 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) + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test2(C1[] x) + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + static void Test3(T[] x) where T : struct, I1 + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test4(T[] x) where T : struct, I1 + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + static void Test5(T[] x) where T : I1 + { + " + op + @"GetA(x)[Get0()]; + } + + static void Test6(T[] x) where T : I1 + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +").VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call ""C1[] Program.GetA(C1[])"" + IL_0007: call ""int Program.Get0()"" + IL_000c: ldelema ""C1"" + IL_0011: call ""void C1." + methodName + @"()"" + IL_0016: nop + IL_0017: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 32 (0x20) + .maxstack 2 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call ""T[] Program.GetA(T[])"" + IL_0007: call ""int Program.Get0()"" + IL_000c: readonly. + IL_000e: ldelema ""T"" + IL_0013: constrained. ""T"" + IL_0019: callvirt ""void I1." + methodName + @"()"" + IL_001e: nop + IL_001f: ret +} +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 26 (0x1a) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""C1[] Program.GetA(C1[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: ldelema ""C1"" + IL_0012: call ""void C1." + methodName + @"()"" + IL_0017: nop + IL_0018: nop + IL_0019: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 34 (0x22) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""T[] Program.GetA(T[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: readonly. + IL_000f: ldelema ""T"" + IL_0014: constrained. ""T"" + IL_001a: callvirt ""void I1." + methodName + @"()"" + IL_001f: nop + IL_0020: nop + IL_0021: ret +} +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_072_Consumption_Prefix_Used_Class([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + C1 y = Test1(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + y = Test2(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + y = Test3(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + y = Test4(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + y = Test5(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + y = Test6(x); + System.Console.Write(y._F); + System.Console.WriteLine((object)x[0] == y); + } + + static C1 Test1(C1[] x) + { + return " + op + @"GetA(x)[Get0()]; + } + + static C1 Test2(C1[] x) + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + static T Test3(T[] x) where T : class, I1 + { + return " + op + @"GetA(x)[Get0()]; + } + + static T Test4(T[] x) where T : class, I1 + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + static T Test5(T[] x) where T : I1 + { + return " + op + @"GetA(x)[Get0()]; + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1True +[GetA][Get0][operator checked]2True +[GetA][Get0][operator]3True +[GetA][Get0][operator checked]4True +[GetA][Get0][operator]5True +[GetA][Get0][operator checked]6True +").VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 19 (0x13) + .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: dup + IL_000d: callvirt ""void C1." + methodName + @"()"" + IL_0012: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 28 (0x1c) + .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: dup + IL_0011: box ""T"" + IL_0016: callvirt ""void I1." + methodName + @"()"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 75 (0x4b) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + T V_3) + 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: ldloca.s V_3 + IL_0017: initobj ""T"" + IL_001d: ldloc.3 + IL_001e: box ""T"" + IL_0023: brtrue.s IL_0034 + IL_0025: ldloca.s V_2 + IL_0027: constrained. ""T"" + IL_002d: callvirt ""void I1." + methodName + @"()"" + IL_0032: br.s IL_0049 + IL_0034: ldloca.s V_2 + IL_0036: constrained. ""T"" + IL_003c: callvirt ""void I1." + methodName + @"()"" + IL_0041: ldloc.0 + IL_0042: ldloc.1 + IL_0043: ldloc.2 + IL_0044: stelem ""T"" + IL_0049: ldloc.2 + IL_004a: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "()", 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, @" +IIncrementOrDecrementOperation (Prefix) (OperatorMethod: void C1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: C1) (Syntax: '" + op + @"GetA(x)[Get0()]') + Target: + 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) +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 19 (0x13) + .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: dup + IL_000d: callvirt ""void C1." + methodName + @"()"" + IL_0012: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 28 (0x1c) + .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: dup + IL_0011: box ""T"" + IL_0016: callvirt ""void I1." + methodName + @"()"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 75 (0x4b) + .maxstack 3 + .locals init (T[] V_0, + int V_1, + T V_2, + T V_3) + 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: ldloca.s V_3 + IL_0017: initobj ""T"" + IL_001d: ldloc.3 + IL_001e: box ""T"" + IL_0023: brtrue.s IL_0034 + IL_0025: ldloca.s V_2 + IL_0027: constrained. ""T"" + IL_002d: callvirt ""void I1." + methodName + @"()"" + IL_0032: br.s IL_0049 + IL_0034: ldloca.s V_2 + IL_0036: constrained. ""T"" + IL_003c: callvirt ""void I1." + methodName + @"()"" + IL_0041: ldloc.0 + IL_0042: ldloc.1 + IL_0043: ldloc.2 + IL_0044: stelem ""T"" + IL_0049: ldloc.2 + IL_004a: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "()", 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, @" +IIncrementOrDecrementOperation (Prefix, Checked) (OperatorMethod: void I1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: T) (Syntax: '" + op + @"GetA(x)[Get0()]') + Target: + 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) +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1True +[GetA][Get0][operator checked]2True +[GetA][Get0][operator]3True +[GetA][Get0][operator checked]4True +[GetA][Get0][operator]5True +[GetA][Get0][operator checked]6True +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_073_Consumption_Prefix_Used_Struct([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + 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) + { + return " + op + @"GetA(x)[Get0()]; + } + + static C1 Test2(C1[] x) + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + static T Test3(T[] x) where T : struct, I1 + { + return " + op + @"GetA(x)[Get0()]; + } + + static T Test4(T[] x) where T : struct, I1 + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + static T Test5(T[] x) where T : I1 + { + return " + op + @"GetA(x)[Get0()]; + } + + static T Test6(T[] x) where T : I1 + { + checked + { + return " + op + @"GetA(x)[Get0()]; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]11 +[GetA][Get0][operator checked]22 +[GetA][Get0][operator]33 +[GetA][Get0][operator checked]44 +[GetA][Get0][operator]55 +[GetA][Get0][operator checked]66 +").VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 38 (0x26) + .maxstack 2 + .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 ""void C1." + methodName + @"()"" + IL_001e: ldloc.0 + IL_001f: stobj ""C1"" + IL_0024: ldloc.0 + IL_0025: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 42 (0x2a) + .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: constrained. ""T"" + IL_001c: callvirt ""void I1." + methodName + @"()"" + IL_0021: ldloc.0 + IL_0022: ldloc.1 + IL_0023: stelem ""T"" + IL_0028: ldloc.1 + IL_0029: ret +} +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 38 (0x26) + .maxstack 2 + .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 ""void C1." + methodName + @"()"" + IL_001e: ldloc.0 + IL_001f: stobj ""C1"" + IL_0024: ldloc.0 + IL_0025: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 42 (0x2a) + .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: constrained. ""T"" + IL_001c: callvirt ""void I1." + methodName + @"()"" + IL_0021: ldloc.0 + IL_0022: ldloc.1 + IL_0023: stelem ""T"" + IL_0028: ldloc.1 + IL_0029: ret +} +"); + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]11 +[GetA][Get0][operator checked]22 +[GetA][Get0][operator]33 +[GetA][Get0][operator checked]44 +[GetA][Get0][operator]55 +[GetA][Get0][operator checked]66 +").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_074_Consumption_Postfix_NotUsed_Class([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public class C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + 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 + @"; + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + static void Test3(T[] x) where T : class, I1 + { + GetA(x)[Get0()]" + op + @"; + } + + static void Test4(T[] x) where T : class, I1 + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @"; + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + const string expectedOutput = @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +"; + var verifier = CompileAndVerify(comp2, expectedOutput: expectedOutput).VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 18 (0x12) + .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: callvirt ""void C1." + methodName + @"()"" + IL_0011: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 27 (0x1b) + .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: callvirt ""void I1." + methodName + @"()"" + IL_001a: ret +} +"); + + verifier.VerifyIL("Program.Test5(T[])", +@" +{ + // Code size 30 (0x1e) + .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: constrained. ""T"" + IL_0018: callvirt ""void I1." + methodName + @"()"" + IL_001d: ret +} +"); + + var tree = comp2.SyntaxTrees.Single(); + var model = comp2.GetSemanticModel(tree); + var opNode = tree.GetRoot().DescendantNodes().OfType().First(); + var symbolInfo = model.GetSymbolInfo(opNode); + + Assert.Equal("void C1." + methodName + "()", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("System.Void", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + var group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + var iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +IIncrementOrDecrementOperation (Postfix) (OperatorMethod: void C1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: System.Void) (Syntax: 'GetA(x)[Get0()]" + op + @"') + Target: + 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) +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 18 (0x12) + .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: callvirt ""void C1." + methodName + @"()"" + IL_0011: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 27 (0x1b) + .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: callvirt ""void I1." + methodName + @"()"" + IL_001a: ret +} +"); + + verifier.VerifyIL("Program.Test6(T[])", +@" +{ + // Code size 30 (0x1e) + .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: constrained. ""T"" + IL_0018: callvirt ""void I1." + methodName + @"()"" + IL_001d: ret +} +"); + + opNode = tree.GetRoot().DescendantNodes().OfType().Last(); + symbolInfo = model.GetSymbolInfo(opNode); + Assert.Equal("void I1." + methodName + "()", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + Assert.Equal("System.Void", model.GetTypeInfo(opNode).Type.ToTestDisplayString()); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + + iOp = model.GetOperation(opNode); + VerifyOperationTree(comp2, iOp, @" +IIncrementOrDecrementOperation (Postfix, Checked) (OperatorMethod: void I1." + methodName + @"()) (OperationKind." + (op == "++" ? "Increment" : "Decrement") + @", Type: System.Void) (Syntax: 'GetA(x)[Get0()]" + op + @"') + Target: + 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) +"); + + 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 CS0023: Operator '++' cannot be applied to operand of type 'C1' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "C1").WithLocation(23, 9), + // (30,13): error CS0023: Operator '++' cannot be applied to operand of type 'C1' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "C1").WithLocation(30, 13), + // (36,9): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "T").WithLocation(36, 9), + // (43,13): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "T").WithLocation(43, 13), + // (49,9): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "T").WithLocation(49, 9), + // (56,13): error CS0023: Operator '++' cannot be applied to operand of type 'T' + // GetA(x)[Get0()]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "GetA(x)[Get0()]" + op).WithArguments(op, "T").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 Increment_075_Consumption_Postfix_NotUsed_Struct([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public interface I1 +{ + public void operator" + op + @"(); + public void operator checked" + op + @"(); +} + +public struct C1 : I1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } + public void operator checked" + op + @"() + { + System.Console.Write(""[operator checked]""); + _F++; + } +} +"; + 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 + @"; + } + + static void Test2(C1[] x) + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + static void Test3(T[] x) where T : struct, I1 + { + GetA(x)[Get0()]" + op + @"; + } + + static void Test4(T[] x) where T : struct, I1 + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + static void Test5(T[] x) where T : I1 + { + GetA(x)[Get0()]" + op + @"; + } + + static void Test6(T[] x) where T : I1 + { + checked + { + GetA(x)[Get0()]" + op + @"; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +").VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test1", +@" +{ + // Code size 22 (0x16) + .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 ""void C1." + methodName + @"()"" + IL_0015: ret +} +"); + + verifier.VerifyIL("Program.Test3(T[])", +@" +{ + // Code size 30 (0x1e) + .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: constrained. ""T"" + IL_0018: callvirt ""void I1." + methodName + @"()"" + IL_001d: ret +} +"); + + methodName = (op == "++" ? WellKnownMemberNames.CheckedIncrementOperatorName : WellKnownMemberNames.CheckedDecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 22 (0x16) + .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 ""void C1." + methodName + @"()"" + IL_0015: ret +} +"); + + verifier.VerifyIL("Program.Test4(T[])", +@" +{ + // Code size 30 (0x1e) + .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: constrained. ""T"" + IL_0018: callvirt ""void I1." + methodName + @"()"" + IL_001d: ret +} +"); + + comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp2, expectedOutput: @" +[GetA][Get0][operator]1 +[GetA][Get0][operator checked]2 +[GetA][Get0][operator]3 +[GetA][Get0][operator checked]4 +[GetA][Get0][operator]5 +[GetA][Get0][operator checked]6 +").VerifyDiagnostics(); + } + + [Fact] + public void Increment_076_Consumption_Postfix_For() + { + var source = @" +public class C1 +{ + public int _F; + public void operator ++() + { + _F++; + } +} + +public class Program +{ + static void Main() + { + C1 x = new C1(); + for (x++; x._F < 4; x++) + { + System.Console.Write(x._F); + } + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: @"123").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_077_Consumption_Postfix_Used([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public void operator" + op + @"() {} + public void operator checked" + op + @"() {} +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1 x = new C1(); + C1 y = x" + op + @"; + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + comp2.VerifyDiagnostics( + // (7,16): error CS0023: Operator '++' cannot be applied to operand of type 'C1' + // C1 y = x++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "x" + op).WithArguments(op, "C1").WithLocation(7, 16) + ); + } + + [Theory] + [CombinatorialData] + public void Increment_078_Consumption_Postfix_Used([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator" + op + @"() => throw null; + public void operator checked" + op + @"() => throw null; + + public static C1 operator" + op + @"(C1 x) + { + System.Console.Write(""[operator]""); + return new C1() { _F = x._F + 1 }; + } + + public static C1 operator checked" + op + @"(C1 x) + { + System.Console.Write(""[operator checked]""); + return new C1() { _F = x._F + 1 }; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1 x = new C1(); + C1 y = x" + op + @"; + System.Console.Write(y._F); + System.Console.Write(x._F); + + checked + { + y = x" + op + @"; + } + + System.Console.Write(y._F); + System.Console.Write(x._F); + } +} +"; + + var comp2 = CreateCompilation(source2, references: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.ReleaseExe); + CompileAndVerify(comp2, expectedOutput: "[operator]01[operator checked]12").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_079_Consumption_RegularVersionInCheckedContext([CombinatorialValues("++", "--")] string op, bool fromMetadata) + { + var source1 = @" +public class C1 +{ + public int _F; + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } +} +"; + var comp1 = CreateCompilation(source1); + + var source2 = @" +public class Program +{ + static void Main() + { + C1[] x = [new C1()]; + Test2(x); + System.Console.WriteLine(x[0]._F); + } + + static void Test2(C1[] x) + { + checked + { + " + op + @"GetA(x)[Get0()]; + } + } + + 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: [fromMetadata ? comp1.EmitToImageReference() : comp1.ToMetadataReference()], options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp2, expectedOutput: @"[GetA][Get0][operator]1").VerifyDiagnostics(); + + var methodName = (op == "++" ? WellKnownMemberNames.IncrementOperatorName : WellKnownMemberNames.DecrementOperatorName); + + verifier.VerifyIL("Program.Test2", +@" +{ + // Code size 22 (0x16) + .maxstack 2 + IL_0000: nop + IL_0001: nop + IL_0002: ldarg.0 + IL_0003: call ""C1[] Program.GetA(C1[])"" + IL_0008: call ""int Program.Get0()"" + IL_000d: ldelem.ref + IL_000e: callvirt ""void C1." + methodName + @"()"" + IL_0013: nop + IL_0014: nop + IL_0015: ret +} +"); + } + + [Theory] + [CombinatorialData] + public void Increment_080_Consumption_CheckedVersionInRegularContext([CombinatorialValues("++", "--")] string op) + { + var source1 = @" +public class C1 +{ + public int _F; + public void operator checked " + op + @"() + { + System.Console.Write(""[operator]""); + _F++; + } +} +"; + 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) + { + " + op + @"GetA(x)[Get0()]; + } + + 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 CS0023: Operator '++' cannot be applied to operand of type 'C1' + // ++GetA(x)[Get0()]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, op + "GetA(x)[Get0()]").WithArguments(op, "C1").WithLocation(13, 9) + ); + } + + [Theory] + [CombinatorialData] + public void Increment_081_Consumption_Shadowing([CombinatorialValues("++", "--")] string op) + { + var source1 = @" +public class C1 +{ + public void operator" + op + @"() => throw null; + public void operator checked" + op + @"() => throw null; +} + +public class C2 : C1 +{ + public new void operator" + op + @"() + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C2(); + " + op + @"x; + checked + { + " + op + @"x; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][operator]").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_082_Consumption_Shadowing([CombinatorialValues("++", "--")] string op) + { + var source1_1 = @" +public class C1 +{ + public void operator" + op + @"() => throw null; + public void operator checked" + op + @"() => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"() => throw null; +} +"; + + var comp1_1 = CreateCompilation(source1_1, assemblyName: "C"); + + var source2 = @" +public class Test +{ + public static void Main() + { + var x = new C2(); + " + op + @"x; + checked + { + " + op + @"x; + } + } +} +"; + + var comp2 = CreateCompilation(source2, references: [comp1_1.ToMetadataReference()]); + comp2.VerifyDiagnostics(); + + var source1_2 = @" +public class C1 +{ + public void operator" + op + @"() + { + System.Console.Write(""[operator]""); + } + + public void operator checked" + op + @"() => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"() + { + System.Console.Write(""[checked operator]""); + } + + public new void operator " + op + @"() => 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 Increment_083_Consumption_Shadowing([CombinatorialValues("++", "--")] string op) + { + var source1 = @" +public abstract class C1 +{ + public abstract void operator" + op + @"(); + public void operator checked" + op + @"() => throw null; +} + +public class C2 : C1 +{ + public new void operator checked " + op + @"() + { + System.Console.Write(""[checked operator]""); + } + + public override void operator " + op + @"() + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C2(); + " + op + @"x; + checked + { + " + op + @"x; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][checked operator]").VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void Increment_084_Consumption_Overriding([CombinatorialValues("++", "--")] string op) + { + var source1 = @" +public abstract class C1 +{ + public abstract void operator" + op + @"(); + public abstract void operator checked" + op + @"(); +} + +public abstract class C2 : C1 +{ + public override void operator checked " + op + @"() + { + System.Console.Write(""[checked operator]""); + } +} + +public class C3 : C2 +{ + public override void operator " + op + @"() + { + System.Console.Write(""[operator]""); + } +} + +public class Program +{ + static void Main() + { + var x = new C3(); + " + op + @"x; + checked + { + " + op + @"x; + } + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "[operator][checked operator]").VerifyDiagnostics(); + } + + [Fact] + public void Increment_085_Consumption_Ambiguity() + { + var source1 = @" +public interface I1 +{ + public void operator ++(); +} + +public interface I2 where T : I2 +{ + public void operator ++(); + public abstract static T operator ++(T x); + public abstract static T operator --(T x); +} + +public class Program +{ + static void Test5(T x) where T : I1, I2 + { + ++x; + --x; + } +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.Net90); + comp1.VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'I1.operator ++()' and 'I2.operator ++()' + // ++x; + Diagnostic(ErrorCode.ERR_AmbigCall, "++").WithArguments("I1.operator ++()", "I2.operator ++()").WithLocation(18, 9) + ); + + 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_Increment()", symbolInfo.CandidateSymbols[0].ToTestDisplayString()); + Assert.Equal("void I2.op_Increment()", 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_Decrement(T x)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal(CandidateReason.None, symbolInfo.CandidateReason); + Assert.Empty(symbolInfo.CandidateSymbols); + + group = model.GetMemberGroup(opNode); + Assert.Empty(group); + } + + [Fact] + public void Increment_086_Consumption_UseStaticOperatorsInOldVersion() + { + var source1 = @" +public class C1 +{ + public int _F; + + public void operator ++() + { + System.Console.Write(""[instance operator]""); + _F++; + } + + public void operator checked ++() + { + System.Console.Write(""[instance operator checked]""); + checked + { + _F++; + } + } + + public static C1 operator ++(C1 x) + { + System.Console.Write(""[static operator]""); + return new C1() { _F = x._F + 1 }; + } + public static C1 operator checked ++(C1 x) + { + System.Console.Write(""[static operator checked]""); + checked + { + return new C1() { _F = x._F + 1 }; + } + } +} +"; + var comp1Ref = CreateCompilation(source1).EmitToImageReference(); + + var source2 = @" +public class Program +{ + static C1 P = new C1(); + + static void Main() + { + C1 x; + + ++P; + System.Console.WriteLine(P._F); + P++; + System.Console.WriteLine(P._F); + x = ++P; + System.Console.WriteLine(P._F); + x = P++; + System.Console.WriteLine(P._F); + + checked + { + ++P; + System.Console.WriteLine(P._F); + P++; + System.Console.WriteLine(P._F); + x = ++P; + System.Console.WriteLine(P._F); + x = P++; + 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]3 +[static operator]4 +[instance operator checked]5 +[instance operator checked]6 +[instance operator checked]7 +[static operator checked]8 +").VerifyDiagnostics(); + + comp2 = CreateCompilation(source2, references: [comp1Ref], options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp2, expectedOutput: @" +[static operator]1 +[static operator]2 +[static operator]3 +[static operator]4 +[static operator checked]5 +[static operator checked]6 +[static operator checked]7 +[static operator checked]8 +").VerifyDiagnostics(); + } + + [Fact] + public void Increment_087_Consumption_Obsolete() + { + var source1 = @" +public class C1 +{ + [System.Obsolete(""Test"")] + public void operator ++() {} +} + +public class Program +{ + static void Main() + { + var x = new C1(); + ++x; + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1).VerifyDiagnostics( + // (13,9): warning CS0618: 'C1.operator ++()' is obsolete: 'Test' + // ++x; + Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "++x").WithArguments("C1.operator ++()", "Test").WithLocation(13, 9) + ); + } + + [Fact] + public void Increment_088_Consumption_UnmanagedCallersOnly() + { + var source1 = @" +public class C1 +{ + [System.Runtime.InteropServices.UnmanagedCallersOnly] + public void operator ++() {} +} + +public class Program +{ + static void Main() + { + var x = new C1(); + ++x; + } +} +"; + + 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 ++()' is attributed with 'UnmanagedCallersOnly' and cannot be called directly. Obtain a function pointer to this method. + // ++x; + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "++x").WithArguments("C1.operator ++()").WithLocation(13, 9) + ); + } + + [Fact] + public void Increment_089_Consumption_NullableAnalysis() + { + var source1 = @" +public class C1 +{ + public void operator ++() {} +} + +#nullable enable + +public class Program +{ + static void Main() + { + C1? x = null; + + try + { + ++x; + System.Console.Write(""unreachable""); + x.ToString(); + } + catch (System.NullReferenceException) + { + System.Console.Write(""in catch""); + } + + C1? y = new C1(); + ++y; + } +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe); + CompileAndVerify(comp1, expectedOutput: "in catch").VerifyDiagnostics( + // (17,15): warning CS8602: Dereference of a possibly null reference. + // ++x; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15) + ); + } + + [Fact] + public void Increment_090_Consumption_BadOperator() + { + var source1 = @" +public class C1 +{ + public void operator ++(int x = 0) {} +} +"; + var source2 = @" +public class Program +{ + static void Main() + { + var x = new C1(); + ++x; + } +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyDiagnostics( + // (4,26): error CS9502: Overloaded instance increment operator '++' must take no parameters + // public void operator ++(int x = 0) {} + Diagnostic(ErrorCode.ERR_BadIncrementOpArgs, "++").WithArguments("++").WithLocation(4, 26), + // (4,33): 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 x = 0) {} + Diagnostic(ErrorCode.WRN_DefaultValueForUnconsumedLocation, "x").WithArguments("x").WithLocation(4, 33) + ); + + var comp2 = CreateCompilation(source2, references: [comp1.ToMetadataReference()], options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (7,9): error CS0023: Operator '++' cannot be applied to operand of type 'C1' + // ++x; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "++x").WithArguments("++", "C1").WithLocation(7, 9) + ); + } + + [Fact] + public void Increment_091_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; + } +} +"; + + CSharpCompilation comp1 = CreateCompilation(source1); + comp1.VerifyDiagnostics( + // (4,26): error CS9502: Overloaded instance increment operator '++' must take no parameters + // public void operator ++(params int[] x) {} + Diagnostic(ErrorCode.ERR_BadIncrementOpArgs, "++").WithArguments("++").WithLocation(4, 26), + // (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 CS0023: Operator '++' cannot be applied to operand of type 'C1' + // ++x; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "++x").WithArguments("++", "C1").WithLocation(7, 9) + ); + } } }