88using System . Collections . Generic ;
99using System . Collections . Immutable ;
1010using System . Diagnostics ;
11+ using System . Diagnostics . CodeAnalysis ;
1112using System . Linq ;
1213using Microsoft . CodeAnalysis . CSharp . Symbols ;
1314using Microsoft . CodeAnalysis . CSharp . Syntax ;
@@ -2262,7 +2263,9 @@ public static BinaryOperatorKind SyntaxKindToBinaryOperatorKind(SyntaxKind kind)
22622263 }
22632264 }
22642265
2265- private BoundExpression BindIncrementOperator ( CSharpSyntaxNode node , ExpressionSyntax operandSyntax , SyntaxToken operatorToken , BindingDiagnosticBag diagnostics )
2266+ #nullable enable
2267+
2268+ private BoundExpression BindIncrementOperator ( ExpressionSyntax node , ExpressionSyntax operandSyntax , SyntaxToken operatorToken , BindingDiagnosticBag diagnostics )
22662269 {
22672270 operandSyntax . CheckDeconstructionCompatibleArgument ( diagnostics ) ;
22682271
@@ -2290,7 +2293,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS
22902293
22912294 // The operand has to be a variable, property or indexer, so it must have a type.
22922295 var operandType = operand . Type ;
2293- Debug . Assert ( ( object ) operandType != null ) ;
2296+ Debug . Assert ( operandType is not null ) ;
22942297
22952298 if ( operandType . IsDynamic ( ) )
22962299 {
@@ -2310,6 +2313,13 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS
23102313 hasErrors : false ) ;
23112314 }
23122315
2316+ // Try an in-place user-defined operator
2317+ BoundIncrementOperator ? inPlaceResult = tryApplyUserDefinedInstanceOperator ( node , operatorToken , kind , operand , diagnostics ) ;
2318+ if ( inPlaceResult is not null )
2319+ {
2320+ return inPlaceResult ;
2321+ }
2322+
23132323 LookupResultKind resultKind ;
23142324 ImmutableArray < MethodSymbol > originalUserDefinedOperators ;
23152325 var best = this . UnaryOperatorOverloadResolution ( kind , operand , node , diagnostics , out resultKind , out originalUserDefinedOperators ) ;
@@ -2339,7 +2349,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS
23392349
23402350 var resultPlaceholder = new BoundValuePlaceholder ( node , signature . ReturnType ) . MakeCompilerGenerated ( ) ;
23412351
2342- BoundExpression resultConversion = GenerateConversionForAssignment ( operandType , resultPlaceholder , diagnostics , ConversionForAssignmentFlags . IncrementAssignment ) ;
2352+ BoundExpression ? resultConversion = GenerateConversionForAssignment ( operandType , resultPlaceholder , diagnostics , ConversionForAssignmentFlags . IncrementAssignment ) ;
23432353
23442354 bool hasErrors = resultConversion . HasErrors ;
23452355
@@ -2376,6 +2386,231 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS
23762386 originalUserDefinedOperators ,
23772387 operandType ,
23782388 hasErrors ) ;
2389+
2390+ BoundIncrementOperator ? tryApplyUserDefinedInstanceOperator ( ExpressionSyntax node , SyntaxToken operatorToken , UnaryOperatorKind kind , BoundExpression operand , BindingDiagnosticBag diagnostics )
2391+ {
2392+ var operandType = operand . Type ;
2393+ Debug . Assert ( operandType is not null ) ;
2394+ Debug . Assert ( ! operandType . IsDynamic ( ) ) ;
2395+
2396+ if ( kind is not ( UnaryOperatorKind . PrefixIncrement or UnaryOperatorKind . PrefixDecrement or UnaryOperatorKind . PostfixIncrement or UnaryOperatorKind . PostfixDecrement ) ||
2397+ operandType . SpecialType . IsNumericType ( ) ||
2398+ ! node . IsFeatureEnabled ( MessageID . IDS_FeatureUserDefinedCompoundAssignmentOperators ) )
2399+ {
2400+ return null ;
2401+ }
2402+
2403+ bool resultIsUsed = ResultIsUsed ( node ) ;
2404+
2405+ if ( ( kind is ( UnaryOperatorKind . PostfixIncrement or UnaryOperatorKind . PostfixDecrement ) && resultIsUsed ) ||
2406+ ! CheckValueKind ( node , operand , BindValueKind . RefersToLocation | BindValueKind . Assignable , checkingReceiver : false , BindingDiagnosticBag . Discarded ) )
2407+ {
2408+ return null ;
2409+ }
2410+
2411+ bool checkOverflowAtRuntime = CheckOverflowAtRuntime ;
2412+ CompoundUseSiteInfo < AssemblySymbol > useSiteInfo = GetNewCompoundUseSiteInfo ( diagnostics ) ;
2413+
2414+ ArrayBuilder < MethodSymbol > ? methods = lookupUserDefinedInstanceOperators (
2415+ operandType ,
2416+ checkedName : checkOverflowAtRuntime ?
2417+ ( kind is UnaryOperatorKind . PrefixIncrement or UnaryOperatorKind . PostfixIncrement ?
2418+ WellKnownMemberNames . CheckedIncrementOperatorName :
2419+ WellKnownMemberNames . CheckedDecrementOperatorName ) :
2420+ null ,
2421+ ordinaryName : kind is UnaryOperatorKind . PrefixIncrement or UnaryOperatorKind . PostfixIncrement ?
2422+ WellKnownMemberNames . IncrementOperatorName :
2423+ WellKnownMemberNames . DecrementOperatorName ,
2424+ ref useSiteInfo ) ;
2425+
2426+ if ( methods ? . IsEmpty != false )
2427+ {
2428+ diagnostics . Add ( node , useSiteInfo ) ;
2429+ methods ? . Free ( ) ;
2430+ return null ;
2431+ }
2432+
2433+ Debug . Assert ( ! methods . IsEmpty ) ;
2434+
2435+ var overloadResolutionResult = OverloadResolutionResult < MethodSymbol > . GetInstance ( ) ;
2436+ var typeArguments = ArrayBuilder < TypeWithAnnotations > . GetInstance ( ) ;
2437+ var analyzedArguments = AnalyzedArguments . GetInstance ( ) ;
2438+
2439+ OverloadResolution . MethodInvocationOverloadResolution (
2440+ methods ,
2441+ typeArguments ,
2442+ operand ,
2443+ analyzedArguments ,
2444+ overloadResolutionResult ,
2445+ ref useSiteInfo ,
2446+ OverloadResolution . Options . None ) ;
2447+
2448+ typeArguments . Free ( ) ;
2449+ diagnostics . Add ( node , useSiteInfo ) ;
2450+
2451+ BoundIncrementOperator ? inPlaceResult ;
2452+
2453+ if ( overloadResolutionResult . Succeeded )
2454+ {
2455+ var method = overloadResolutionResult . ValidResult . Member ;
2456+
2457+ ReportDiagnosticsIfObsolete ( diagnostics , method , node , hasBaseReceiver : false ) ;
2458+ ReportDiagnosticsIfUnmanagedCallersOnly ( diagnostics , method , node , isDelegateConversion : false ) ;
2459+
2460+ inPlaceResult = new BoundIncrementOperator (
2461+ node ,
2462+ ( kind | UnaryOperatorKind . UserDefined ) . WithOverflowChecksIfApplicable ( checkOverflowAtRuntime ) ,
2463+ operand ,
2464+ methodOpt : method ,
2465+ constrainedToTypeOpt : null ,
2466+ operandPlaceholder : null ,
2467+ operandConversion : null ,
2468+ resultPlaceholder : null ,
2469+ resultConversion : null ,
2470+ LookupResultKind . Viable ,
2471+ ImmutableArray < MethodSymbol > . Empty ,
2472+ resultIsUsed ? operandType : GetSpecialType ( SpecialType . System_Void , diagnostics , node ) ) ;
2473+
2474+ methods . Free ( ) ;
2475+ }
2476+ else if ( overloadResolutionResult . HasAnyApplicableMember )
2477+ {
2478+ ImmutableArray < MethodSymbol > methodsArray = methods . ToImmutableAndFree ( ) ;
2479+
2480+ overloadResolutionResult . ReportDiagnostics (
2481+ binder : this , location : operatorToken . GetLocation ( ) , nodeOpt : node , diagnostics : diagnostics , name : operatorToken . ValueText ,
2482+ receiver : operand , invokedExpression : node , arguments : analyzedArguments , memberGroup : methodsArray ,
2483+ typeContainingConstructor : null , delegateTypeBeingInvoked : null ) ;
2484+
2485+ inPlaceResult = new BoundIncrementOperator (
2486+ node ,
2487+ ( kind | UnaryOperatorKind . UserDefined ) . WithOverflowChecksIfApplicable ( checkOverflowAtRuntime ) ,
2488+ operand ,
2489+ methodOpt : null ,
2490+ constrainedToTypeOpt : null ,
2491+ operandPlaceholder : null ,
2492+ operandConversion : null ,
2493+ resultPlaceholder : null ,
2494+ resultConversion : null ,
2495+ LookupResultKind . OverloadResolutionFailure ,
2496+ methodsArray ,
2497+ resultIsUsed ? operandType : GetSpecialType ( SpecialType . System_Void , diagnostics , node ) ) ;
2498+ }
2499+ else
2500+ {
2501+ inPlaceResult = null ;
2502+ methods . Free ( ) ;
2503+ }
2504+
2505+ analyzedArguments . Free ( ) ;
2506+ overloadResolutionResult . Free ( ) ;
2507+
2508+ return inPlaceResult ;
2509+ }
2510+
2511+ ArrayBuilder < MethodSymbol > ? lookupUserDefinedInstanceOperators ( TypeSymbol lookupInType , string ? checkedName , string ordinaryName , ref CompoundUseSiteInfo < AssemblySymbol > useSiteInfo )
2512+ {
2513+ var lookupResult = LookupResult . GetInstance ( ) ;
2514+ ArrayBuilder < MethodSymbol > ? methods = null ;
2515+ if ( checkedName is not null )
2516+ {
2517+ this . LookupMembersWithFallback ( lookupResult , lookupInType , name : checkedName , arity : 0 , ref useSiteInfo , basesBeingResolved : null , options : LookupOptions . MustBeInstance | LookupOptions . MustBeOperator ) ;
2518+
2519+ if ( lookupResult . IsMultiViable )
2520+ {
2521+ methods = ArrayBuilder < MethodSymbol > . GetInstance ( lookupResult . Symbols . Count ) ;
2522+ appendViableMethods ( lookupResult , methods ) ;
2523+ }
2524+
2525+ lookupResult . Clear ( ) ;
2526+ }
2527+
2528+ this . LookupMembersWithFallback ( lookupResult , lookupInType , name : ordinaryName , arity : 0 , ref useSiteInfo , basesBeingResolved : null , options : LookupOptions . MustBeInstance | LookupOptions . MustBeOperator ) ;
2529+
2530+ if ( lookupResult . IsMultiViable )
2531+ {
2532+ if ( methods is null )
2533+ {
2534+ methods = ArrayBuilder < MethodSymbol > . GetInstance ( lookupResult . Symbols . Count ) ;
2535+ appendViableMethods ( lookupResult , methods ) ;
2536+ }
2537+ else
2538+ {
2539+ var existing = new HashSet < MethodSymbol > ( PairedOperatorComparer . Instance ) ;
2540+
2541+ foreach ( var method in methods )
2542+ {
2543+ existing . Add ( method . GetLeastOverriddenMethod ( ContainingType ) ) ;
2544+ }
2545+
2546+ foreach ( MethodSymbol method in lookupResult . Symbols )
2547+ {
2548+ if ( isViable ( method ) && ! existing . Contains ( method . GetLeastOverriddenMethod ( ContainingType ) ) )
2549+ {
2550+ methods . Add ( method ) ;
2551+ }
2552+ }
2553+ }
2554+ }
2555+
2556+ lookupResult . Free ( ) ;
2557+
2558+ return methods ;
2559+
2560+ static void appendViableMethods ( LookupResult lookupResult , ArrayBuilder < MethodSymbol > methods )
2561+ {
2562+ foreach ( MethodSymbol method in lookupResult . Symbols )
2563+ {
2564+ if ( isViable ( method ) )
2565+ {
2566+ methods . Add ( method ) ;
2567+ }
2568+ }
2569+ }
2570+
2571+ static bool isViable ( MethodSymbol method )
2572+ {
2573+ return method . ParameterCount == 0 ;
2574+ }
2575+ }
2576+ }
2577+
2578+ #nullable disable
2579+
2580+ private class PairedOperatorComparer : IEqualityComparer < MethodSymbol >
2581+ {
2582+ public static readonly PairedOperatorComparer Instance = new PairedOperatorComparer ( ) ;
2583+
2584+ private PairedOperatorComparer ( ) { }
2585+
2586+ public bool Equals ( MethodSymbol x , MethodSymbol y )
2587+ {
2588+ Debug . Assert ( ! x . IsOverride ) ;
2589+ Debug . Assert ( ! x . IsStatic ) ;
2590+
2591+ Debug . Assert ( ! y . IsOverride ) ;
2592+ Debug . Assert ( ! y . IsStatic ) ;
2593+
2594+ var typeComparer = Symbols . SymbolEqualityComparer . AllIgnoreOptions ;
2595+ return typeComparer . Equals ( x . ContainingType , y . ContainingType ) &&
2596+ SourceMemberContainerTypeSymbol . DoOperatorsPair ( x , y ) ;
2597+ }
2598+
2599+ public int GetHashCode ( [ DisallowNull ] MethodSymbol method )
2600+ {
2601+ Debug . Assert ( ! method . IsOverride ) ;
2602+ Debug . Assert ( ! method . IsStatic ) ;
2603+
2604+ var typeComparer = Symbols . SymbolEqualityComparer . AllIgnoreOptions ;
2605+ int result = typeComparer . GetHashCode ( method . ContainingType ) ;
2606+
2607+ if ( method . ParameterTypesWithAnnotations is [ var typeWithAnnotations , ..] )
2608+ {
2609+ result = Hash . Combine ( result , typeComparer . GetHashCode ( typeWithAnnotations . Type ) ) ;
2610+ }
2611+
2612+ return result ;
2613+ }
23792614 }
23802615
23812616#nullable enable
0 commit comments