1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using Microsoft.CSharp.RuntimeBinder.Errors;
9 using Microsoft.CSharp.RuntimeBinder.Syntax;
10 
11 namespace Microsoft.CSharp.RuntimeBinder.Semantics
12 {
13     internal sealed partial class ExpressionBinder
14     {
15         /*
16             These are the predefined binary operator signatures
17 
18                 (object,    object)     :                   == !=
19                 (string,    string)     :                   == !=
20                 (string,    string)     :       +
21                 (string,    object)     :       +
22                 (object,    string)     :       +
23 
24                 (int,       int)        :  / % + - << >>   == != < > <= >=&| ^
25                 (uint,      uint)       :  / % + -         == != < > <= >=&| ^
26                 (long,      long)       :  / % + -         == != < > <= >=&| ^
27                 (ulong,     ulong)      :  / % + -         == != < > <= >=&| ^
28                 (uint,      int)        :           << >>
29                 (long,      int)        :           << >>
30                 (ulong,     int)        :           << >>
31 
32                 (float,     float)      :  / % + -         == != < > <= >=
33                 (double,    double)     :  / % + -         == != < > <= >=
34                 (decimal,   decimal)    :  / % + -         == != < > <= >=
35 
36                 (bool,      bool)       :                   == !=          &| ^ && ||
37 
38                 (Sys.Del,   Sys.Del)    :                   == !=
39 
40                 // Below here the types cannot be represented entirely by a PREDEFTYPE.
41                 (delegate,  delegate)   :       + -         == !=
42 
43                 (enum,      enum)       :         -         == != < > <= >=&| ^
44                 (enum,      under)      :       + -
45                 (under,     enum)       :       +
46 
47                 (ptr,       ptr)        :         -     Not callable through dynamic
48                 (ptr,       int)        :       + -     Not callable through dynamic
49                 (ptr,       uint)       :       + -     Not callable through dynamic
50                 (ptr,       long)       :       + -     Not callable through dynamic
51                 (ptr,       ulong)      :       + -     Not callable through dynamic
52                 (int,       ptr)        :       +       Not callable through dynamic
53                 (uint,      ptr)        :       +       Not callable through dynamic
54                 (long,      ptr)        :       +       Not callable through dynamic
55                 (ulong,     ptr)        :       +       Not callable through dynamic
56 
57                 (void,     void)      :                   == != < > <= >=
58 
59             There are the predefined unary operator signatures:
60 
61                 int     : + -   ~
62                 uint    : +     ~
63                 long    : + -   ~
64                 ulong   : +     ~
65 
66                 float   : + -
67                 double  : + -
68                 decimal : + -
69 
70                 bool    :     !
71 
72                 // Below here the types cannot be represented entirely by a PREDEFTYPE.
73                 enum    :       ~
74                 ptr     :
75 
76             Note that pointer operators cannot be lifted over nullable and are not callable through dynamic
77         */
78 
79         // BinOpBindMethod and UnaOpBindMethod are method pointer arrays to dispatch the appropriate operator binder.
80         // Method pointers must be in the order of the corresponding enums. We check this when the full signature is set.
81         // When the binding method is looked up in these arrays we ASSERT
82         // if the array is out of bounds of the corresponding array.
83 
84         private readonly BinOpSig[] g_binopSignatures;
85 
86         // We want unary minus to bind to "operator -(ulong)" and then we
87         // produce an error (since there is no pfn). We can't let - bind to a floating point type,
88         // since they lose precision. See the language spec.
89 
90         // Increment and decrement operators are special.
91 
92         private readonly UnaOpSig[] g_rguos;
93 
BindUserDefinedBinOp(ExpressionKind ek, BinOpArgInfo info)94         private ExprBinOp BindUserDefinedBinOp(ExpressionKind ek, BinOpArgInfo info)
95         {
96             MethPropWithInst pmpwi;
97             if (info.pt1 <= PredefinedType.PT_ULONG && info.pt2 <= PredefinedType.PT_ULONG)
98             {
99                 return null;
100             }
101 
102             Expr expr;
103             if (info.binopKind == BinOpKind.Logical)
104             {
105                 // Logical operators cannot be overloaded, but use the bitwise overloads.
106                 ExprCall call = BindUDBinop(
107                     ek - ExpressionKind.LogicalAnd + ExpressionKind.BitwiseAnd, info.arg1, info.arg2, true, out pmpwi);
108                 if (call == null)
109                 {
110                     return null;
111                 }
112 
113                 expr = BindUserBoolOp(ek, call);
114             }
115             else
116             {
117                 expr = BindUDBinop(ek, info.arg1, info.arg2, false, out pmpwi);
118             }
119 
120             if (expr == null)
121             {
122                 return null;
123             }
124 
125             return GetExprFactory().CreateUserDefinedBinop(ek, expr.Type, info.arg1, info.arg2, expr, pmpwi);
126         }
127 
128         // Adds special signatures to the candidate list.  If we find an exact match
129         // then it will be the last item on the list and we return true.
GetSpecialBinopSignatures(List<BinOpFullSig> prgbofs, BinOpArgInfo info)130         private bool GetSpecialBinopSignatures(List<BinOpFullSig> prgbofs, BinOpArgInfo info)
131         {
132             Debug.Assert(prgbofs != null);
133             if (info.pt1 <= PredefinedType.PT_ULONG && info.pt2 <= PredefinedType.PT_ULONG)
134             {
135                 return false;
136             }
137             return GetDelBinOpSigs(prgbofs, info) ||
138                    GetEnumBinOpSigs(prgbofs, info) ||
139                    GetRefEqualSigs(prgbofs, info);
140         }
141 
142         // Adds standard and lifted signatures to the candidate list.  If we find an exact match
143         // then it will be the last item on the list and we return true.
144 
GetStandardAndLiftedBinopSignatures(List<BinOpFullSig> rgbofs, BinOpArgInfo info)145         private bool GetStandardAndLiftedBinopSignatures(List<BinOpFullSig> rgbofs, BinOpArgInfo info)
146         {
147             Debug.Assert(rgbofs != null);
148 
149             int ibosMinLift = 0;
150             for (int ibos = 0; ibos < g_binopSignatures.Length; ibos++)
151             {
152                 BinOpSig bos = g_binopSignatures[ibos];
153                 if ((bos.mask & info.mask) == 0)
154                 {
155                     continue;
156                 }
157 
158                 CType typeSig1 = GetPredefindType(bos.pt1);
159                 CType typeSig2 = GetPredefindType(bos.pt2);
160                 if (typeSig1 == null || typeSig2 == null)
161                     continue;
162 
163                 ConvKind cv1 = GetConvKind(info.pt1, bos.pt1);
164                 ConvKind cv2 = GetConvKind(info.pt2, bos.pt2);
165                 LiftFlags grflt = LiftFlags.None;
166 
167                 switch (cv1)
168                 {
169                     default:
170                         Debug.Fail("Shouldn't happen!");
171                         continue;
172 
173                     case ConvKind.None:
174                         continue;
175                     case ConvKind.Explicit:
176                         if (!(info.arg1 is ExprConstant constant))
177                         {
178                             continue;
179                         }
180                         // Need to try to convert.
181 
182                         if (canConvert(constant, typeSig1))
183                         {
184                             break;
185                         }
186 
187                         if (ibos < ibosMinLift || !bos.CanLift())
188                         {
189                             continue;
190                         }
191 
192                         Debug.Assert(typeSig1.IsValType());
193 
194                         typeSig1 = GetSymbolLoader().GetTypeManager().GetNullable(typeSig1);
195                         if (!canConvert(constant, typeSig1))
196                         {
197                             continue;
198                         }
199                         switch (GetConvKind(info.ptRaw1, bos.pt1))
200                         {
201                             default:
202                                 grflt = grflt | LiftFlags.Convert1;
203                                 break;
204                             case ConvKind.Implicit:
205                             case ConvKind.Identity:
206                                 grflt = grflt | LiftFlags.Lift1;
207                                 break;
208                         }
209                         break;
210                     case ConvKind.Unknown:
211                         if (canConvert(info.arg1, typeSig1))
212                         {
213                             break;
214                         }
215                         if (ibos < ibosMinLift || !bos.CanLift())
216                         {
217                             continue;
218                         }
219                         Debug.Assert(typeSig1.IsValType());
220 
221                         typeSig1 = GetSymbolLoader().GetTypeManager().GetNullable(typeSig1);
222                         if (!canConvert(info.arg1, typeSig1))
223                         {
224                             continue;
225                         }
226                         switch (GetConvKind(info.ptRaw1, bos.pt1))
227                         {
228                             default:
229                                 grflt = grflt | LiftFlags.Convert1;
230                                 break;
231                             case ConvKind.Implicit:
232                             case ConvKind.Identity:
233                                 grflt = grflt | LiftFlags.Lift1;
234                                 break;
235                         }
236                         break;
237                     case ConvKind.Implicit:
238                         break;
239                     case ConvKind.Identity:
240                         if (cv2 == ConvKind.Identity)
241                         {
242                             BinOpFullSig newsig = new BinOpFullSig(this, bos);
243                             if (newsig.Type1() != null && newsig.Type2() != null)
244                             {
245                                 // Exact match.
246                                 rgbofs.Add(newsig);
247                                 return true;
248                             }
249                         }
250                         break;
251                 }
252 
253                 switch (cv2)
254                 {
255                     default:
256                         Debug.Fail("Shouldn't happen!");
257                         continue;
258                     case ConvKind.None:
259                         continue;
260                     case ConvKind.Explicit:
261                         if (!(info.arg2 is ExprConstant constant))
262                         {
263                             continue;
264                         }
265 
266                         // Need to try to convert.
267                         if (canConvert(constant, typeSig2))
268                         {
269                             break;
270                         }
271 
272                         if (ibos < ibosMinLift || !bos.CanLift())
273                         {
274                             continue;
275                         }
276                         Debug.Assert(typeSig2.IsValType());
277 
278                         typeSig2 = GetSymbolLoader().GetTypeManager().GetNullable(typeSig2);
279                         if (!canConvert(constant, typeSig2))
280                         {
281                             continue;
282                         }
283 
284                         switch (GetConvKind(info.ptRaw2, bos.pt2))
285                         {
286                             default:
287                                 grflt = grflt | LiftFlags.Convert2;
288                                 break;
289                             case ConvKind.Implicit:
290                             case ConvKind.Identity:
291                                 grflt = grflt | LiftFlags.Lift2;
292                                 break;
293                         }
294                         break;
295                     case ConvKind.Unknown:
296                         if (canConvert(info.arg2, typeSig2))
297                         {
298                             break;
299                         }
300                         if (ibos < ibosMinLift || !bos.CanLift())
301                         {
302                             continue;
303                         }
304                         Debug.Assert(typeSig2.IsValType());
305 
306                         typeSig2 = GetSymbolLoader().GetTypeManager().GetNullable(typeSig2);
307                         if (!canConvert(info.arg2, typeSig2))
308                         {
309                             continue;
310                         }
311                         switch (GetConvKind(info.ptRaw2, bos.pt2))
312                         {
313                             default:
314                                 grflt = grflt | LiftFlags.Convert2;
315                                 break;
316                             case ConvKind.Implicit:
317                             case ConvKind.Identity:
318                                 grflt = grflt | LiftFlags.Lift2;
319                                 break;
320                         }
321                         break;
322                     case ConvKind.Identity:
323                     case ConvKind.Implicit:
324                         break;
325                 }
326 
327                 if (grflt != LiftFlags.None)
328                 {
329                     // We have a lifted signature.
330                     rgbofs.Add(new BinOpFullSig(typeSig1, typeSig2, bos.pfn, bos.grfos, grflt, bos.fnkind));
331 
332                     // NOTE: Can't skip any if we use a lifted signature because the
333                     // type might convert to int? and to long (but not to int) in which
334                     // case we should get an ambiguity. But we can skip the lifted ones....
335                     ibosMinLift = ibos + bos.cbosSkip + 1;
336                 }
337                 else
338                 {
339                     // Record it as applicable and skip accordingly.
340                     rgbofs.Add(new BinOpFullSig(this, bos));
341                     ibos += bos.cbosSkip;
342                 }
343             }
344             return false;
345         }
346 
347         // Returns the index of the best match, or -1 if there is no best match.
FindBestSignatureInList( List<BinOpFullSig> binopSignatures, BinOpArgInfo info)348         private int FindBestSignatureInList(
349                 List<BinOpFullSig> binopSignatures,
350                 BinOpArgInfo info)
351         {
352             Debug.Assert(binopSignatures != null);
353 
354             if (binopSignatures.Count == 1)
355             {
356                 return 0;
357             }
358 
359             int bestSignature = 0;
360             int index;
361             // Try to find a candidate for the best.
362             for (index = 1; index < binopSignatures.Count; index++)
363             {
364                 if (bestSignature < 0)
365                 {
366                     bestSignature = index;
367                 }
368                 else
369                 {
370                     int nT = WhichBofsIsBetter(binopSignatures[bestSignature], binopSignatures[index], info.type1, info.type2);
371                     if (nT == 0)
372                     {
373                         bestSignature = -1;
374                     }
375                     else if (nT > 0)
376                     {
377                         bestSignature = index;
378                     }
379                 }
380             }
381 
382             if (bestSignature == -1)
383             {
384                 return -1;
385             }
386 
387             // Verify that the candidate really is not worse than all others.
388             // Do we need to loop over the whole list here, or just
389             // from 0 . bestSignature - 1?
390             for (index = 0; index < binopSignatures.Count; index++)
391             {
392                 if (index == bestSignature)
393                 {
394                     continue;
395                 }
396                 if (WhichBofsIsBetter(binopSignatures[bestSignature], binopSignatures[index], info.type1, info.type2) >= 0)
397                 {
398                     return -1;
399                 }
400             }
401             return bestSignature;
402         }
403 
bindNullEqualityComparison(ExpressionKind ek, BinOpArgInfo info)404         private ExprBinOp bindNullEqualityComparison(ExpressionKind ek, BinOpArgInfo info)
405         {
406             Expr arg1 = info.arg1;
407             Expr arg2 = info.arg2;
408             if (info.binopKind == BinOpKind.Equal)
409             {
410                 CType typeBool = GetPredefindType(PredefinedType.PT_BOOL);
411                 ExprBinOp exprRes = null;
412                 if (info.type1 is NullableType && info.type2 is NullType)
413                 {
414                     arg2 = GetExprFactory().CreateZeroInit(info.type1);
415                     exprRes = GetExprFactory().CreateBinop(ek, typeBool, arg1, arg2);
416                 }
417                 if (info.type1 is NullType && info.type2 is NullableType)
418                 {
419                     arg1 = GetExprFactory().CreateZeroInit(info.type2);
420                     exprRes = GetExprFactory().CreateBinop(ek, typeBool, arg1, arg2);
421                 }
422                 if (exprRes != null)
423                 {
424                     exprRes.IsLifted = true;
425                     return exprRes;
426                 }
427             }
428 
429             throw BadOperatorTypesError(info.arg1, info.arg2);
430         }
431 
432         /*
433             This handles binding binary operators by first checking for user defined operators, then
434             applying overload resolution to the predefined operators. It handles lifting over nullable.
435         */
BindStandardBinop(ExpressionKind ek, Expr arg1, Expr arg2)436         public Expr BindStandardBinop(ExpressionKind ek, Expr arg1, Expr arg2)
437         {
438             Debug.Assert(arg1 != null);
439             Debug.Assert(arg2 != null);
440 
441             (BinOpKind kind, EXPRFLAG flags) = GetBinopKindAndFlags(ek);
442             BinOpArgInfo info = new BinOpArgInfo(arg1, arg2)
443             {
444                 binopKind = kind
445             };
446 
447             info.mask = (BinOpMask)(1 << (int)info.binopKind);
448 
449             List<BinOpFullSig> binopSignatures = new List<BinOpFullSig>();
450 
451             // First check if this is a user defined binop. If it is, return it.
452             ExprBinOp exprUD = BindUserDefinedBinOp(ek, info);
453             if (exprUD != null)
454             {
455                 return exprUD;
456             }
457 
458             // Get the special binop signatures. If successful, the special binop signature will be
459             // the last item in the array of signatures that we give it.
460 
461             bool exactMatch = GetSpecialBinopSignatures(binopSignatures, info);
462             if (!exactMatch)
463             {
464                 // No match, try to get standard and lifted binop signatures.
465 
466                 exactMatch = GetStandardAndLiftedBinopSignatures(binopSignatures, info);
467             }
468 
469             // If we have an exact match in either the special binop signatures or the standard/lifted binop
470             // signatures, then we set our best match. Otherwise, we check if we had any signatures at all.
471             // If we didn't, then its possible where we have x == null, where x is nullable, so try to bind
472             // the null equality comparison. Otherwise, we had some ambiguity - we have a match, but its not exact.
473 
474             int bestBinopSignature;
475             if (exactMatch)
476             {
477                 Debug.Assert(binopSignatures.Count > 0);
478                 bestBinopSignature = binopSignatures.Count - 1;
479             }
480             else if (binopSignatures.Count == 0)
481             {
482                 // If we got no matches then it's possible that we're in the case
483                 // x == null, where x is nullable.
484                 return bindNullEqualityComparison(ek, info);
485             }
486             else
487             {
488                 // We had some matches, try to find the best one. FindBestSignatureInList returns < 0 if
489                 // we don't have a best one, otherwise it returns the index of the best one in our list that
490                 // we give it.
491 
492                 bestBinopSignature = FindBestSignatureInList(binopSignatures, info);
493                 if (bestBinopSignature < 0)
494                 {
495                     // Ambiguous.
496 
497                     throw AmbiguousOperatorError(ek, arg1, arg2);
498                 }
499             }
500 
501             // If we're here, we should have a binop signature that exactly matches.
502 
503             Debug.Assert(bestBinopSignature < binopSignatures.Count);
504 
505             // We've found the one to use, so lets go and bind it.
506 
507             return BindStandardBinopCore(info, binopSignatures[bestBinopSignature], ek, flags);
508         }
509 
BindStandardBinopCore(BinOpArgInfo info, BinOpFullSig bofs, ExpressionKind ek, EXPRFLAG flags)510         private Expr BindStandardBinopCore(BinOpArgInfo info, BinOpFullSig bofs, ExpressionKind ek, EXPRFLAG flags)
511         {
512             if (bofs.pfn == null)
513             {
514                 throw BadOperatorTypesError(info.arg1, info.arg2);
515             }
516 
517             if (!bofs.isLifted() || !bofs.AutoLift())
518             {
519                 Expr expr1 = info.arg1;
520                 Expr expr2 = info.arg2;
521                 if (bofs.ConvertOperandsBeforeBinding())
522                 {
523                     expr1 = mustConvert(expr1, bofs.Type1());
524                     expr2 = mustConvert(expr2, bofs.Type2());
525                 }
526                 if (bofs.fnkind == BinOpFuncKind.BoolBitwiseOp)
527                 {
528                     return BindBoolBitwiseOp(ek, flags, expr1, expr2, bofs);
529                 }
530                 return bofs.pfn(ek, flags, expr1, expr2);
531             }
532             Debug.Assert(bofs.fnkind != BinOpFuncKind.BoolBitwiseOp);
533             if (IsEnumArithmeticBinOp(ek, info))
534             {
535                 Expr expr1 = info.arg1;
536                 Expr expr2 = info.arg2;
537                 if (bofs.ConvertOperandsBeforeBinding())
538                 {
539                     expr1 = mustConvert(expr1, bofs.Type1());
540                     expr2 = mustConvert(expr2, bofs.Type2());
541                 }
542 
543                 return BindLiftedEnumArithmeticBinOp(ek, flags, expr1, expr2);
544             }
545 
546             return BindLiftedStandardBinOp(info, bofs, ek, flags);
547         }
548 
BindLiftedStandardBinOp(BinOpArgInfo info, BinOpFullSig bofs, ExpressionKind ek, EXPRFLAG flags)549         private ExprBinOp BindLiftedStandardBinOp(BinOpArgInfo info, BinOpFullSig bofs, ExpressionKind ek, EXPRFLAG flags)
550         {
551             Debug.Assert(bofs.Type1() is NullableType || bofs.Type2() is NullableType);
552 
553             Expr arg1 = info.arg1;
554             Expr arg2 = info.arg2;
555 
556             // We want to get the base types of the arguments and attempt to bind the non-lifted form of the
557             // method so that we error report (ie divide by zero etc), and then we store in the resulting
558             // binop that we have a lifted operator.
559 
560             Expr nonLiftedResult = null;
561 
562             LiftArgument(arg1, bofs.Type1(), bofs.ConvertFirst(), out Expr pArgument1, out Expr nonLiftedArg1);
563             LiftArgument(arg2, bofs.Type2(), bofs.ConvertSecond(), out Expr pArgument2, out Expr nonLiftedArg2);
564 
565             // Now call the non-lifted method to generate errors, and stash the result.
566             if (!nonLiftedArg1.isNull() && !nonLiftedArg2.isNull())
567             {
568                 // Only compute the method if theres no nulls. If there are, we'll special case it
569                 // later, since operations with a null operand are null.
570                 nonLiftedResult = bofs.pfn(ek, flags, nonLiftedArg1, nonLiftedArg2);
571             }
572 
573             // Check if we have a comparison. If so, set the result type to bool.
574             CType resultType;
575             if (info.binopKind == BinOpKind.Compare || info.binopKind == BinOpKind.Equal)
576             {
577                 resultType = GetPredefindType(PredefinedType.PT_BOOL);
578             }
579             else
580             {
581                 resultType = bofs.fnkind == BinOpFuncKind.EnumBinOp
582                     ? GetEnumBinOpType(ek, nonLiftedArg1.Type, nonLiftedArg2.Type, out var _)
583                     : pArgument1.Type;
584 
585                 if (!(resultType is NullableType))
586                 {
587                     resultType = GetSymbolLoader().GetTypeManager().GetNullable(resultType);
588                 }
589             }
590 
591             ExprBinOp exprRes = GetExprFactory().CreateBinop(ek, resultType, pArgument1, pArgument2);
592             mustCast(nonLiftedResult, resultType, 0);
593             exprRes.IsLifted = true;
594             exprRes.Flags |= flags;
595             Debug.Assert((exprRes.Flags & EXPRFLAG.EXF_LVALUE) == 0);
596 
597             return exprRes;
598         }
599 
600         /////////////////////////////////////////////////////////////////////////////////
601 
LiftArgument(Expr pArgument, CType pParameterType, bool bConvertBeforeLift, out Expr ppLiftedArgument, out Expr ppNonLiftedArgument)602         private void LiftArgument(Expr pArgument, CType pParameterType, bool bConvertBeforeLift,
603                                             out Expr ppLiftedArgument, out Expr ppNonLiftedArgument)
604         {
605             Expr pLiftedArgument = mustConvert(pArgument, pParameterType);
606             if (pLiftedArgument != pArgument)
607             {
608                 MarkAsIntermediateConversion(pLiftedArgument);
609             }
610 
611             Expr pNonLiftedArgument = pArgument;
612             if (pParameterType is NullableType paramNub)
613             {
614                 if (pNonLiftedArgument.isNull())
615                 {
616                     pNonLiftedArgument = mustCast(pNonLiftedArgument, pParameterType);
617                 }
618                 pNonLiftedArgument = mustCast(pNonLiftedArgument, paramNub.GetUnderlyingType());
619                 if (bConvertBeforeLift)
620                 {
621                     MarkAsIntermediateConversion(pNonLiftedArgument);
622                 }
623             }
624             else
625             {
626                 pNonLiftedArgument = pLiftedArgument;
627             }
628             ppLiftedArgument = pLiftedArgument;
629             ppNonLiftedArgument = pNonLiftedArgument;
630         }
631 
632         /*
633             Get the special signatures when at least one of the args is a delegate instance.
634             Returns true iff an exact signature match is found.
635         */
GetDelBinOpSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)636         private bool GetDelBinOpSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)
637         {
638             if (!info.ValidForDelegate())
639             {
640                 return false;
641             }
642             if (!info.type1.isDelegateType() && !info.type2.isDelegateType())
643             {
644                 return false;
645             }
646 
647             // No conversions needed. Determine the lifting. This is the common case.
648             if (info.type1 == info.type2)
649             {
650                 prgbofs.Add(new BinOpFullSig(info.type1, info.type2, BindDelBinOp, OpSigFlags.Reference, LiftFlags.None, BinOpFuncKind.DelBinOp));
651                 return true;
652             }
653 
654             // Now, for each delegate type, if both arguments convert to that delegate type, that is a candidate
655             // for this binary operator. It's possible that we add two candidates, in which case they will compete
656             // in overload resolution. Or we could add no candidates.
657 
658             bool t1tot2 = info.type2.isDelegateType() && canConvert(info.arg1, info.type2);
659             bool t2tot1 = info.type1.isDelegateType() && canConvert(info.arg2, info.type1);
660 
661             if (t1tot2)
662             {
663                 prgbofs.Add(new BinOpFullSig(info.type2, info.type2, BindDelBinOp, OpSigFlags.Reference, LiftFlags.None, BinOpFuncKind.DelBinOp));
664             }
665 
666             if (t2tot1)
667             {
668                 prgbofs.Add(new BinOpFullSig(info.type1, info.type1, BindDelBinOp, OpSigFlags.Reference, LiftFlags.None, BinOpFuncKind.DelBinOp));
669             }
670 
671             // Might be ambiguous so return false.
672             return false;
673         }
674 
675         /*
676             Utility method to determine whether arg1 is convertible to typeDst, either in a regular
677             scenario or lifted scenario. Sets pgrflt, ptypeSig1 and ptypeSig2 accordingly.
678         */
CanConvertArg1(BinOpArgInfo info, CType typeDst, out LiftFlags pgrflt, out CType ptypeSig1, out CType ptypeSig2)679         private bool CanConvertArg1(BinOpArgInfo info, CType typeDst, out LiftFlags pgrflt,
680                                       out CType ptypeSig1, out CType ptypeSig2)
681         {
682             ptypeSig1 = null;
683             ptypeSig2 = null;
684             Debug.Assert(!(typeDst is NullableType));
685 
686             if (canConvert(info.arg1, typeDst))
687                 pgrflt = LiftFlags.None;
688             else
689             {
690                 pgrflt = LiftFlags.None;
691                 typeDst = GetSymbolLoader().GetTypeManager().GetNullable(typeDst);
692                 if (!canConvert(info.arg1, typeDst))
693                     return false;
694                 pgrflt = LiftFlags.Convert1;
695             }
696             ptypeSig1 = typeDst;
697 
698             if (info.type2 is NullableType)
699             {
700                 pgrflt = pgrflt | LiftFlags.Lift2;
701                 ptypeSig2 = GetSymbolLoader().GetTypeManager().GetNullable(info.typeRaw2);
702             }
703             else
704                 ptypeSig2 = info.typeRaw2;
705 
706             return true;
707         }
708 
709 
710         /*
711             Same as CanConvertArg1 but with the indices interchanged!
712         */
CanConvertArg2(BinOpArgInfo info, CType typeDst, out LiftFlags pgrflt, out CType ptypeSig1, out CType ptypeSig2)713         private bool CanConvertArg2(BinOpArgInfo info, CType typeDst, out LiftFlags pgrflt,
714                                       out CType ptypeSig1, out CType ptypeSig2)
715         {
716             Debug.Assert(!(typeDst is NullableType));
717             ptypeSig1 = null;
718             ptypeSig2 = null;
719 
720             if (canConvert(info.arg2, typeDst))
721                 pgrflt = LiftFlags.None;
722             else
723             {
724                 pgrflt = LiftFlags.None;
725                 typeDst = GetSymbolLoader().GetTypeManager().GetNullable(typeDst);
726                 if (!canConvert(info.arg2, typeDst))
727                     return false;
728                 pgrflt = LiftFlags.Convert2;
729             }
730             ptypeSig2 = typeDst;
731 
732             if (info.type1 is NullableType)
733             {
734                 pgrflt = pgrflt | LiftFlags.Lift1;
735                 ptypeSig1 = GetSymbolLoader().GetTypeManager().GetNullable(info.typeRaw1);
736             }
737             else
738                 ptypeSig1 = info.typeRaw1;
739 
740             return true;
741         }
742 
743 
744         /*
745             Record the appropriate binary operator full signature from the given BinOpArgInfo. This assumes
746             that any NullableType valued args should be lifted.
747         */
RecordBinOpSigFromArgs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)748         private void RecordBinOpSigFromArgs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)
749         {
750             LiftFlags grflt = LiftFlags.None;
751             CType typeSig1;
752             CType typeSig2;
753 
754             if (info.type1 != info.typeRaw1)
755             {
756                 Debug.Assert(info.type1 is NullableType);
757                 grflt = grflt | LiftFlags.Lift1;
758                 typeSig1 = GetSymbolLoader().GetTypeManager().GetNullable(info.typeRaw1);
759             }
760             else
761                 typeSig1 = info.typeRaw1;
762 
763             if (info.type2 != info.typeRaw2)
764             {
765                 Debug.Assert(info.type2 is NullableType);
766                 grflt = grflt | LiftFlags.Lift2;
767                 typeSig2 = GetSymbolLoader().GetTypeManager().GetNullable(info.typeRaw2);
768             }
769             else
770                 typeSig2 = info.typeRaw2;
771 
772             prgbofs.Add(new BinOpFullSig(typeSig1, typeSig2, BindEnumBinOp, OpSigFlags.Value, grflt, BinOpFuncKind.EnumBinOp));
773         }
774 
775         /*
776             Get the special signatures when at least one of the args is an enum.  Return true if
777             we find an exact match.
778         */
GetEnumBinOpSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)779         private bool GetEnumBinOpSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)
780         {
781             if (!info.typeRaw1.isEnumType() && !info.typeRaw2.isEnumType())
782             {
783                 return false;
784             }
785 
786             // (enum,      enum)       :         -         == != < > <= >=&| ^
787             // (enum,      under)      :       + -
788             // (under,     enum)       :       +
789             CType typeSig1 = null;
790             CType typeSig2 = null;
791             LiftFlags grflt = LiftFlags.None;
792 
793             // Look for the no conversions cases. Still need to determine the lifting. These are the common case.
794             if (info.typeRaw1 == info.typeRaw2)
795             {
796                 if (!info.ValidForEnum())
797                 {
798                     return false;
799                 }
800                 RecordBinOpSigFromArgs(prgbofs, info);
801                 return true;
802             }
803 
804             bool isValidForEnum;
805 
806             if (info.typeRaw1.isEnumType())
807             {
808                 isValidForEnum = (info.typeRaw2 == info.typeRaw1.underlyingEnumType() && info.ValidForEnumAndUnderlyingType());
809             }
810             else
811             {
812                 isValidForEnum = (info.typeRaw1 == info.typeRaw2.underlyingEnumType() && info.ValidForUnderlyingTypeAndEnum());
813             }
814 
815             if (isValidForEnum)
816             {
817                 RecordBinOpSigFromArgs(prgbofs, info);
818                 return true;
819             }
820 
821             // Now deal with the conversion cases. Since there are no conversions from enum types to other
822             // enum types we never need to do both cases.
823 
824             if (info.typeRaw1.isEnumType())
825             {
826                 isValidForEnum = info.ValidForEnum() && CanConvertArg2(info, info.typeRaw1, out grflt, out typeSig1, out typeSig2) ||
827                     info.ValidForEnumAndUnderlyingType() && CanConvertArg2(info, info.typeRaw1.underlyingEnumType(), out grflt, out typeSig1, out typeSig2);
828             }
829             else
830             {
831                 isValidForEnum = info.ValidForEnum() && CanConvertArg1(info, info.typeRaw2, out grflt, out typeSig1, out typeSig2) ||
832                     info.ValidForEnumAndUnderlyingType() && CanConvertArg1(info, info.typeRaw2.underlyingEnumType(), out grflt, out typeSig1, out typeSig2);
833             }
834 
835             if (isValidForEnum)
836             {
837                 prgbofs.Add(new BinOpFullSig(typeSig1, typeSig2, BindEnumBinOp, OpSigFlags.Value, grflt, BinOpFuncKind.EnumBinOp));
838             }
839             return false;
840         }
841 
IsEnumArithmeticBinOp(ExpressionKind ek, BinOpArgInfo info)842         private bool IsEnumArithmeticBinOp(ExpressionKind ek, BinOpArgInfo info)
843         {
844             switch (ek)
845             {
846                 case ExpressionKind.Add:
847                     return info.typeRaw1.isEnumType() ^ info.typeRaw2.isEnumType();
848                 case ExpressionKind.Subtract:
849                     return info.typeRaw1.isEnumType() | info.typeRaw2.isEnumType();
850             }
851 
852             return false;
853         }
854 
855 
856         /*
857             See if standard reference equality applies. Make sure not to return true if another == operator
858             may be applicable and better (or ambiguous)! This also handles == on System.Delegate, since
859             it has special rules as well.
860         */
GetRefEqualSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)861         private bool GetRefEqualSigs(List<BinOpFullSig> prgbofs, BinOpArgInfo info)
862         {
863             if (info.mask != BinOpMask.Equal)
864             {
865                 return false;
866             }
867 
868             if (info.type1 != info.typeRaw1 || info.type2 != info.typeRaw2)
869             {
870                 return false;
871             }
872 
873             bool fRet = false;
874             CType type1 = info.type1;
875             CType type2 = info.type2;
876             CType typeObj = GetPredefindType(PredefinedType.PT_OBJECT);
877             CType typeCls = null;
878 
879             if (type1 is NullType && type2 is NullType)
880             {
881                 typeCls = typeObj;
882                 fRet = true;
883             }
884             else
885             {
886 
887                 // Check for: operator ==(System.Delegate, System.Delegate).
888                 CType typeDel = GetPredefindType(PredefinedType.PT_DELEGATE);
889                 if (canConvert(info.arg1, typeDel) && canConvert(info.arg2, typeDel) && !type1.isDelegateType()
890                     && !type2.isDelegateType())
891                 {
892                     prgbofs.Add(
893                         new BinOpFullSig(
894                             typeDel, typeDel, BindDelBinOp, OpSigFlags.Convert, LiftFlags.None,
895                             BinOpFuncKind.DelBinOp));
896                 }
897 
898                 // The reference type equality operators only handle reference types.
899                 Debug.Assert(type1.fundType() != FUNDTYPE.FT_VAR);
900                 if (type1.fundType() != FUNDTYPE.FT_REF)
901                 {
902                     return false;
903                 }
904 
905                 if (type2 is NullType)
906                 {
907                     fRet = true;
908 
909                     // We don't need to determine the actual best type since we're
910                     // returning true - indicating that we've found the best operator.
911                     typeCls = typeObj;
912                 }
913                 else
914                 {
915                     Debug.Assert(type2.fundType() != FUNDTYPE.FT_VAR);
916                     if (type2.fundType() != FUNDTYPE.FT_REF)
917                     {
918                         return false;
919                     }
920 
921                     if (type1 is NullType)
922                     {
923                         fRet = true;
924 
925                         // We don't need to determine the actual best type since we're
926                         // returning true - indicating that we've found the best operator.
927                         typeCls = typeObj;
928                     }
929                     else
930                     {
931                         if (!canCast(type1, type2, CONVERTTYPE.NOUDC) && !canCast(type2, type1, CONVERTTYPE.NOUDC))
932                             return false;
933 
934                         if (type1.isInterfaceType() || type1.isPredefType(PredefinedType.PT_STRING)
935                             || GetSymbolLoader().HasBaseConversion(type1, typeDel))
936                             type1 = typeObj;
937                         else if (type1 is ArrayType)
938                             type1 = GetPredefindType(PredefinedType.PT_ARRAY);
939                         else if (!type1.isClassType())
940                             return false;
941 
942                         if (type2.isInterfaceType() || type2.isPredefType(PredefinedType.PT_STRING)
943                             || GetSymbolLoader().HasBaseConversion(type2, typeDel))
944                             type2 = typeObj;
945                         else if (type2 is ArrayType)
946                             type2 = GetPredefindType(PredefinedType.PT_ARRAY);
947                         else if (!type2.isClassType())
948                             return false;
949 
950                         Debug.Assert(
951                             type1.isClassType() && !type1.isPredefType(PredefinedType.PT_STRING)
952                             && !type1.isPredefType(PredefinedType.PT_DELEGATE));
953                         Debug.Assert(
954                             type2.isClassType() && !type2.isPredefType(PredefinedType.PT_STRING)
955                             && !type2.isPredefType(PredefinedType.PT_DELEGATE));
956 
957                         if (GetSymbolLoader().HasBaseConversion(type2, type1))
958                             typeCls = type1;
959                         else if (GetSymbolLoader().HasBaseConversion(type1, type2))
960                             typeCls = type2;
961 
962                     }
963                 }
964             }
965 
966             prgbofs.Add(new BinOpFullSig(typeCls, typeCls, BindRefCmpOp, OpSigFlags.None, LiftFlags.None, BinOpFuncKind.RefCmpOp));
967             return fRet;
968         }
969 
970         /*
971             Determine which BinOpSig is better for overload resolution.
972             Better means: at least as good in all Params, and better in at least one param.
973 
974             Better w/r to a param means:
975             1) same type as argument
976             2) implicit conversion from this one's param type to the other's param type
977             Because of user defined conversion operators this relation is not transitive.
978 
979             Returns negative if ibos1 is better, positive if ibos2 is better, 0 if neither.
980         */
981 
WhichBofsIsBetter(BinOpFullSig bofs1, BinOpFullSig bofs2, CType type1, CType type2)982         private int WhichBofsIsBetter(BinOpFullSig bofs1, BinOpFullSig bofs2, CType type1, CType type2)
983         {
984             BetterType bt1;
985             BetterType bt2;
986 
987             if (bofs1.FPreDef() && bofs2.FPreDef())
988             {
989                 // Faster to compare predefs.
990                 bt1 = WhichTypeIsBetter(bofs1.pt1, bofs2.pt1, type1);
991                 bt2 = WhichTypeIsBetter(bofs1.pt2, bofs2.pt2, type2);
992             }
993             else
994             {
995                 bt1 = WhichTypeIsBetter(bofs1.Type1(), bofs2.Type1(), type1);
996                 bt2 = WhichTypeIsBetter(bofs1.Type2(), bofs2.Type2(), type2);
997             }
998 
999             int res;
1000 
1001             Debug.Assert(Enum.IsDefined(typeof(BetterType), bt1));
1002             Debug.Assert(Enum.IsDefined(typeof(BetterType), bt2));
1003             switch (bt1)
1004             {
1005                 case BetterType.Left:
1006                     res = -1;
1007                     break;
1008 
1009                 case BetterType.Right:
1010                     res = 1;
1011                     break;
1012 
1013                 default:
1014                     res = 0;
1015                     break;
1016             }
1017 
1018             switch (bt2)
1019             {
1020                 case BetterType.Left:
1021                     res--;
1022                     break;
1023 
1024                 case BetterType.Right:
1025                     res++;
1026                     break;
1027             }
1028 
1029             return res;
1030         }
1031 
1032 
1033         /////////////////////////////////////////////////////////////////////////////////
1034         // Bind a standard unary operator. Takes care of user defined operators, predefined operators
1035         // and lifting over nullable.
1036 
CalculateExprAndUnaryOpKinds(OperatorKind op, bool bChecked)1037         private static (ExpressionKind, UnaOpKind, EXPRFLAG) CalculateExprAndUnaryOpKinds(OperatorKind op, bool bChecked)
1038         {
1039             ExpressionKind ek;
1040             UnaOpKind uok;
1041             EXPRFLAG flags = 0;
1042             switch (op)
1043             {
1044                 case OperatorKind.OP_UPLUS:
1045                     uok = UnaOpKind.Plus;
1046                     ek = ExpressionKind.UnaryPlus;
1047                     break;
1048 
1049                 case OperatorKind.OP_NEG:
1050                     if (bChecked)
1051                     {
1052                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
1053                     }
1054                     uok = UnaOpKind.Minus;
1055                     ek = ExpressionKind.Negate;
1056                     break;
1057 
1058                 case OperatorKind.OP_BITNOT:
1059                     uok = UnaOpKind.Tilde;
1060                     ek = ExpressionKind.BitwiseNot;
1061                     break;
1062 
1063                 case OperatorKind.OP_LOGNOT:
1064                     uok = UnaOpKind.Bang;
1065                     ek = ExpressionKind.LogicalNot;
1066                     break;
1067 
1068                 case OperatorKind.OP_POSTINC:
1069                     flags = EXPRFLAG.EXF_ISPOSTOP;
1070                     if (bChecked)
1071                     {
1072                         flags |= EXPRFLAG.EXF_CHECKOVERFLOW;
1073                     }
1074                     uok = UnaOpKind.IncDec;
1075                     ek = ExpressionKind.Add;
1076                     break;
1077 
1078                 case OperatorKind.OP_PREINC:
1079                     if (bChecked)
1080                     {
1081                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
1082                     }
1083                     uok = UnaOpKind.IncDec;
1084                     ek = ExpressionKind.Add;
1085                     break;
1086 
1087                 case OperatorKind.OP_POSTDEC:
1088                     flags = EXPRFLAG.EXF_ISPOSTOP;
1089                     if (bChecked)
1090                     {
1091                         flags |= EXPRFLAG.EXF_CHECKOVERFLOW;
1092                     }
1093                     uok = UnaOpKind.IncDec;
1094                     ek = ExpressionKind.Subtract;
1095                     break;
1096 
1097                 case OperatorKind.OP_PREDEC:
1098                     if (bChecked)
1099                     {
1100                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
1101                     }
1102                     uok = UnaOpKind.IncDec;
1103                     ek = ExpressionKind.Subtract;
1104                     break;
1105 
1106                 default:
1107                     Debug.Fail($"Bad op: {op}");
1108                     throw Error.InternalCompilerError();
1109             }
1110 
1111             return (ek, uok, flags);
1112         }
1113 
BindStandardUnaryOperator(OperatorKind op, Expr pArgument)1114         public Expr BindStandardUnaryOperator(OperatorKind op, Expr pArgument)
1115         {
1116             Debug.Assert(pArgument != null);
1117 
1118             CType type = pArgument.Type;
1119             Debug.Assert(type != null);
1120             if (type is NullableType nub)
1121             {
1122                 CType nonNub = nub.UnderlyingType;
1123                 if (nonNub.isEnumType())
1124                 {
1125                     PredefinedType ptOp;
1126                     switch (nonNub.fundType())
1127                     {
1128                         case FUNDTYPE.FT_U4:
1129                             ptOp = PredefinedType.PT_UINT;
1130                             break;
1131 
1132                         case FUNDTYPE.FT_I8:
1133                             ptOp = PredefinedType.PT_LONG;
1134                             break;
1135 
1136                         case FUNDTYPE.FT_U8:
1137                             ptOp = PredefinedType.PT_ULONG;
1138                             break;
1139 
1140                         default:
1141                             // Promote all smaller types to int.
1142                             ptOp = PredefinedType.PT_INT;
1143                             break;
1144                     }
1145 
1146                     return mustCast(
1147                         BindStandardUnaryOperator(
1148                             op, mustCast(pArgument, GetTypes().GetNullable(GetPredefindType(ptOp)))), nub);
1149                 }
1150             }
1151 
1152             (ExpressionKind ek, UnaOpKind unaryOpKind, EXPRFLAG flags) =
1153                 CalculateExprAndUnaryOpKinds(op, Context.Checked);
1154 
1155             UnaOpMask unaryOpMask = (UnaOpMask)(1 << (int)unaryOpKind);
1156 
1157             List<UnaOpFullSig> pSignatures = new List<UnaOpFullSig>();
1158 
1159             UnaryOperatorSignatureFindResult eResultOfSignatureFind = PopulateSignatureList(pArgument, unaryOpKind, unaryOpMask, ek, flags, pSignatures, out Expr pResult);
1160 
1161             // nBestSignature is a 0-based index.
1162             int nBestSignature = pSignatures.Count - 1;
1163 
1164             if (eResultOfSignatureFind == UnaryOperatorSignatureFindResult.Return)
1165             {
1166                 Debug.Assert(pResult != null);
1167                 return pResult;
1168             }
1169             else if (eResultOfSignatureFind != UnaryOperatorSignatureFindResult.Match)
1170             {
1171                 // If we didn't find a best match while populating, try to find while doing
1172                 // applicability testing.
1173                 if (!FindApplicableSignatures(
1174                             pArgument,
1175                             unaryOpMask,
1176                             pSignatures))
1177                 {
1178                     if (pSignatures.Count == 0)
1179                     {
1180                         throw BadOperatorTypesError(pArgument, null);
1181                     }
1182 
1183                     nBestSignature = 0;
1184                     // If we couldn't find exactly one, then we need to do some betterness testing.
1185                     if (pSignatures.Count != 1)
1186                     {
1187                         // Determine which is best.
1188                         for (int iuofs = 1; iuofs < pSignatures.Count; iuofs++)
1189                         {
1190                             if (nBestSignature < 0)
1191                             {
1192                                 nBestSignature = iuofs;
1193                             }
1194                             else
1195                             {
1196                                 int nT = WhichUofsIsBetter(pSignatures[nBestSignature], pSignatures[iuofs], type);
1197                                 if (nT == 0)
1198                                 {
1199                                     nBestSignature = -1;
1200                                 }
1201                                 else if (nT > 0)
1202                                 {
1203                                     nBestSignature = iuofs;
1204                                 }
1205                             }
1206                         }
1207                         if (nBestSignature < 0)
1208                         {
1209                             // Ambiguous.
1210                             throw AmbiguousOperatorError(ek, pArgument, null);
1211                         }
1212 
1213                         // Verify that our answer works.
1214                         for (int iuofs = 0; iuofs < pSignatures.Count; iuofs++)
1215                         {
1216                             if (iuofs == nBestSignature)
1217                             {
1218                                 continue;
1219                             }
1220                             if (WhichUofsIsBetter(pSignatures[nBestSignature], pSignatures[iuofs], type) >= 0)
1221                             {
1222                                 throw AmbiguousOperatorError(ek, pArgument, null);
1223                             }
1224                         }
1225                     }
1226                 }
1227                 else
1228                 {
1229                     nBestSignature = pSignatures.Count - 1;
1230                 }
1231             }
1232 
1233             Debug.Assert(nBestSignature < pSignatures.Count);
1234 
1235             UnaOpFullSig uofs = pSignatures[nBestSignature];
1236 
1237             if (uofs.pfn == null)
1238             {
1239                 if (unaryOpKind == UnaOpKind.IncDec)
1240                 {
1241                     return BindIncOp(ek, flags, pArgument, uofs);
1242                 }
1243 
1244                 throw BadOperatorTypesError(pArgument, null);
1245             }
1246 
1247             if (uofs.isLifted())
1248             {
1249                 return BindLiftedStandardUnop(ek, flags, pArgument, uofs);
1250             }
1251 
1252             if (pArgument is ExprConstant)
1253             {
1254                 // Wrap the constant in an identity cast, to force the later casts to not be optimised out.
1255                 // The ExpressionTreeRewriter will remove this again.
1256                 pArgument = ExprFactory.CreateCast(pArgument.Type, pArgument);
1257             }
1258 
1259             // Try the conversion - if it fails, do a cast without user defined casts.
1260             Expr arg = tryConvert(pArgument, uofs.GetType());
1261             if (arg == null)
1262             {
1263                 arg = mustCast(pArgument, uofs.GetType(), CONVERTTYPE.NOUDC);
1264             }
1265             return uofs.pfn(ek, flags, arg);
1266         }
1267 
1268         /////////////////////////////////////////////////////////////////////////////////
1269 
PopulateSignatureList(Expr pArgument, UnaOpKind unaryOpKind, UnaOpMask unaryOpMask, ExpressionKind exprKind, EXPRFLAG flags, List<UnaOpFullSig> pSignatures, out Expr ppResult)1270         private UnaryOperatorSignatureFindResult PopulateSignatureList(Expr pArgument, UnaOpKind unaryOpKind, UnaOpMask unaryOpMask, ExpressionKind exprKind, EXPRFLAG flags, List<UnaOpFullSig> pSignatures, out Expr ppResult)
1271         {
1272             // We should have already checked argument != null and argument.type != null.
1273             Debug.Assert(pArgument != null);
1274             Debug.Assert(pArgument.Type != null);
1275 
1276             ppResult = null;
1277             CType pArgumentType = pArgument.Type;
1278             CType pRawType = pArgumentType.StripNubs();
1279             PredefinedType ptRaw = pRawType.isPredefined() ? pRawType.getPredefType() : PredefinedType.PT_COUNT;
1280 
1281             // Find all applicable operator signatures.
1282             // First check for special ones (enum, ptr) and check for user defined ops.
1283 
1284             if (ptRaw > PredefinedType.PT_ULONG)
1285             {
1286                 // Enum types are special in that they carry a set of "predefined" operators (~ and inc/dec).
1287                 if (pRawType.isEnumType())
1288                 {
1289                     // Nullable enums are dealt with already.
1290                     Debug.Assert(pRawType == pArgumentType);
1291                     Debug.Assert(pArgumentType is AggregateType);
1292                     if ((unaryOpMask & (UnaOpMask.Tilde | UnaOpMask.IncDec)) != 0)
1293                     {
1294                         // We have an exact match.
1295                         if (unaryOpKind == UnaOpKind.Tilde)
1296                         {
1297                             pSignatures.Add(new UnaOpFullSig(
1298                                     pArgumentType.getAggregate().GetUnderlyingType(),
1299                                     BindEnumUnaOp,
1300                                     LiftFlags.None,
1301                                     UnaOpFuncKind.EnumUnaOp));
1302                         }
1303                         else
1304                         {
1305                             // For enums, we want to add the signature as the underlying type so that we'll
1306                             // perform the conversions to and from the enum type.
1307                             pSignatures.Add(new UnaOpFullSig(
1308                                     pArgumentType.getAggregate().GetUnderlyingType(),
1309                                     null,
1310                                     LiftFlags.None,
1311                                     UnaOpFuncKind.None));
1312                         }
1313 
1314                         return UnaryOperatorSignatureFindResult.Match;
1315                     }
1316                 }
1317                 else if (unaryOpKind == UnaOpKind.IncDec)
1318                 {
1319                     Debug.Assert(!(pArgumentType is PointerType));
1320 
1321                     // Check for user defined inc/dec
1322                     ExprMultiGet exprGet = GetExprFactory().CreateMultiGet(0, pArgumentType, null);
1323 
1324                     Expr exprVal = bindUDUnop((ExpressionKind)(exprKind - ExpressionKind.Add + ExpressionKind.Inc), exprGet);
1325                     if (exprVal != null)
1326                     {
1327                         if (exprVal.Type != null && exprVal.Type != pArgumentType)
1328                         {
1329                             exprVal = mustConvert(exprVal, pArgumentType);
1330                         }
1331 
1332                         Debug.Assert(pArgument != null);
1333                         ExprMulti exprMulti = GetExprFactory().CreateMulti(EXPRFLAG.EXF_ASSGOP | flags, pArgumentType, pArgument, exprVal);
1334                         exprGet.OptionalMulti = exprMulti;
1335 
1336                         // Check whether Lvalue can be assigned.
1337                         CheckLvalue(pArgument, CheckLvalueKind.Increment);
1338                         ppResult = exprMulti;
1339                         return UnaryOperatorSignatureFindResult.Return;
1340                     }
1341                     // Try for a predefined increment operator.
1342                 }
1343                 else
1344                 {
1345                     // Check for user defined.
1346                     Expr expr = bindUDUnop(exprKind, pArgument);
1347                     if (expr != null)
1348                     {
1349                         ppResult = expr;
1350                         return UnaryOperatorSignatureFindResult.Return;
1351                     }
1352                 }
1353             }
1354 
1355             return UnaryOperatorSignatureFindResult.Continue;
1356         }
1357 
1358         /////////////////////////////////////////////////////////////////////////////////
1359 
FindApplicableSignatures( Expr pArgument, UnaOpMask unaryOpMask, List<UnaOpFullSig> pSignatures)1360         private bool FindApplicableSignatures(
1361                 Expr pArgument,
1362                 UnaOpMask unaryOpMask,
1363                 List<UnaOpFullSig> pSignatures)
1364         {
1365             // All callers should already assert this to be the case.
1366             Debug.Assert(pArgument != null);
1367             Debug.Assert(pArgument.Type != null);
1368 
1369             long iuosMinLift = 0;
1370 
1371             CType pArgumentType = pArgument.Type;
1372             CType pRawType = pArgumentType.StripNubs();
1373             PredefinedType pt = pArgumentType.isPredefined() ? pArgumentType.getPredefType() : PredefinedType.PT_COUNT;
1374             PredefinedType ptRaw = pRawType.isPredefined() ? pRawType.getPredefType() : PredefinedType.PT_COUNT;
1375 
1376             for (int index = 0; index < g_rguos.Length; index++)
1377             {
1378                 UnaOpSig uos = g_rguos[index];
1379                 if ((uos.grfuom & unaryOpMask) == 0)
1380                 {
1381                     continue;
1382                 }
1383 
1384                 ConvKind cv = GetConvKind(pt, g_rguos[index].pt);
1385                 CType typeSig = null;
1386 
1387                 switch (cv)
1388                 {
1389                     default:
1390                         Debug.Fail("Shouldn't happen!");
1391                         continue;
1392 
1393                     case ConvKind.None:
1394                         continue;
1395 
1396                     case ConvKind.Explicit:
1397                         if (!(pArgument is ExprConstant))
1398                         {
1399                             continue;
1400                         }
1401 
1402                         if (canConvert(pArgument, typeSig = GetPredefindType(uos.pt)))
1403                         {
1404                             break;
1405                         }
1406                         if (index < iuosMinLift)
1407                         {
1408                             continue;
1409                         }
1410                         typeSig = GetSymbolLoader().GetTypeManager().GetNullable(typeSig);
1411                         if (!canConvert(pArgument, typeSig))
1412                         {
1413                             continue;
1414                         }
1415                         break;
1416 
1417                     case ConvKind.Unknown:
1418                         if (canConvert(pArgument, typeSig = GetPredefindType(uos.pt)))
1419                         {
1420                             break;
1421                         }
1422                         if (index < iuosMinLift)
1423                         {
1424                             continue;
1425                         }
1426                         typeSig = GetSymbolLoader().GetTypeManager().GetNullable(typeSig);
1427                         if (!canConvert(pArgument, typeSig))
1428                         {
1429                             continue;
1430                         }
1431                         break;
1432 
1433                     case ConvKind.Implicit:
1434                         break;
1435 
1436                     case ConvKind.Identity:
1437                         {
1438                             UnaOpFullSig result = new UnaOpFullSig(this, uos);
1439                             if (result.GetType() != null)
1440                             {
1441                                 pSignatures.Add(result);
1442                                 return true;
1443                             }
1444                         }
1445                         break;
1446                 }
1447 
1448                 if (typeSig is NullableType)
1449                 {
1450                     // Need to use a lifted signature.
1451                     LiftFlags grflt = LiftFlags.None;
1452 
1453                     switch (GetConvKind(ptRaw, uos.pt))
1454                     {
1455                         default:
1456                             grflt = grflt | LiftFlags.Convert1;
1457                             break;
1458                         case ConvKind.Implicit:
1459                         case ConvKind.Identity:
1460                             grflt = grflt | LiftFlags.Lift1;
1461                             break;
1462                     }
1463 
1464                     pSignatures.Add(new UnaOpFullSig(typeSig, uos.pfn, grflt, uos.fnkind));
1465 
1466                     // NOTE: Can't skip any if we use the lifted signature because the
1467                     // type might convert to int? and to long (but not to int) in which
1468                     // case we should get an ambiguity. But we can skip the lifted ones....
1469                     iuosMinLift = index + uos.cuosSkip + 1;
1470                 }
1471                 else
1472                 {
1473                     // Record it as applicable and skip accordingly.
1474                     UnaOpFullSig newResult = new UnaOpFullSig(this, uos);
1475                     if (newResult.GetType() != null)
1476                     {
1477                         pSignatures.Add(newResult);
1478                     }
1479                     index += uos.cuosSkip;
1480                 }
1481             }
1482             return false;
1483         }
1484 
BindLiftedStandardUnop(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)1485         private ExprOperator BindLiftedStandardUnop(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)
1486         {
1487             NullableType type = uofs.GetType() as NullableType;
1488             Debug.Assert(arg?.Type != null);
1489             if (arg.Type is NullType)
1490             {
1491                 throw BadOperatorTypesError(arg, null);
1492             }
1493 
1494             LiftArgument(arg, uofs.GetType(), uofs.Convert(), out Expr pArgument, out Expr nonLiftedArg);
1495 
1496             // Now call the function with the non lifted arguments to report errors.
1497             Expr nonLiftedResult = uofs.pfn(ek, flags, nonLiftedArg);
1498             ExprUnaryOp exprRes = GetExprFactory().CreateUnaryOp(ek, type, pArgument);
1499             mustCast(nonLiftedResult, type, 0);
1500             exprRes.Flags |= flags;
1501 
1502             Debug.Assert((exprRes.Flags & EXPRFLAG.EXF_LVALUE) == 0);
1503             return exprRes;
1504         }
1505 
1506         /*
1507             Determine which UnaOpSig is better for overload resolution.
1508             Returns negative if iuos1 is better, positive if iuos2 is better, 0 if neither.
1509         */
WhichUofsIsBetter(UnaOpFullSig uofs1, UnaOpFullSig uofs2, CType typeArg)1510         private int WhichUofsIsBetter(UnaOpFullSig uofs1, UnaOpFullSig uofs2, CType typeArg)
1511         {
1512             BetterType bt;
1513 
1514             if (uofs1.FPreDef() && uofs2.FPreDef())
1515             {
1516                 // Faster to compare predefs.
1517                 bt = WhichTypeIsBetter(uofs1.pt, uofs2.pt, typeArg);
1518             }
1519             else
1520             {
1521                 bt = WhichTypeIsBetter(uofs1.GetType(), uofs2.GetType(), typeArg);
1522             }
1523 
1524             Debug.Assert(Enum.IsDefined(typeof(BetterType), bt));
1525             switch (bt)
1526             {
1527                 case BetterType.Left:
1528                     return -1;
1529                 case BetterType.Right:
1530                     return +1;
1531                 default:
1532                     return 0;
1533             }
1534         }
1535 
1536         /*
1537             Handles standard binary integer based operators.
1538         */
BindIntBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1539         private ExprOperator BindIntBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1540         {
1541             Debug.Assert(arg1.Type.isPredefined() && arg2.Type.isPredefined() && arg1.Type.getPredefType() == arg2.Type.getPredefType());
1542             return BindIntOp(ek, flags, arg1, arg2, arg1.Type.getPredefType());
1543         }
1544 
1545 
1546         /*
1547             Handles standard unary integer based operators.
1548         */
BindIntUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)1549         private ExprOperator BindIntUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)
1550         {
1551             Debug.Assert(arg.Type.isPredefined());
1552             return BindIntOp(ek, flags, arg, null, arg.Type.getPredefType());
1553         }
1554 
1555 
1556         /*
1557             Handles standard binary floating point (float, double) based operators.
1558         */
BindRealBinOp(ExpressionKind ek, EXPRFLAG _, Expr arg1, Expr arg2)1559         private ExprOperator BindRealBinOp(ExpressionKind ek, EXPRFLAG _, Expr arg1, Expr arg2)
1560         {
1561             Debug.Assert(arg1.Type.isPredefined() && arg2.Type.isPredefined() && arg1.Type.getPredefType() == arg2.Type.getPredefType());
1562             return bindFloatOp(ek, arg1, arg2);
1563         }
1564 
1565 
1566         /*
1567             Handles standard unary floating point (float, double) based operators.
1568         */
BindRealUnaOp(ExpressionKind ek, EXPRFLAG _, Expr arg)1569         private ExprOperator BindRealUnaOp(ExpressionKind ek, EXPRFLAG _, Expr arg)
1570         {
1571             Debug.Assert(arg.Type.isPredefined());
1572             return bindFloatOp(ek, arg, null);
1573         }
1574 
1575 
1576         /*
1577             Handles standard increment and decrement operators.
1578         */
BindIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)1579         private Expr BindIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)
1580         {
1581             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract);
1582 
1583             CheckLvalue(arg, CheckLvalueKind.Increment);
1584             CType typeRaw = uofs.GetType().StripNubs();
1585 
1586             FUNDTYPE ft = typeRaw.fundType();
1587             if (ft == FUNDTYPE.FT_R8 || ft == FUNDTYPE.FT_R4)
1588             {
1589                 flags &= ~EXPRFLAG.EXF_CHECKOVERFLOW;
1590             }
1591 
1592             if (uofs.isLifted())
1593             {
1594                 return BindLiftedIncOp(ek, flags, arg, uofs);
1595             }
1596             else
1597             {
1598                 return BindNonliftedIncOp(ek, flags, arg, uofs);
1599             }
1600         }
1601 
BindIncOpCore(ExpressionKind ek, EXPRFLAG flags, Expr exprVal, CType type)1602         private Expr BindIncOpCore(ExpressionKind ek, EXPRFLAG flags, Expr exprVal, CType type)
1603         {
1604             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract);
1605             ConstVal cv;
1606 
1607             if (type.isEnumType() && type.fundType() > FUNDTYPE.FT_LASTINTEGRAL)
1608             {
1609                 // This is an error case when enum derives from an illegal type. Just treat it as an int.
1610                 type = GetPredefindType(PredefinedType.PT_INT);
1611             }
1612 
1613             Debug.Assert(type.fundType() != FUNDTYPE.FT_PTR); // Can't have a pointer.
1614             switch (type.fundType())
1615             {
1616                 default:
1617                     Debug.Assert(type.isPredefType(PredefinedType.PT_DECIMAL));
1618                     PREDEFMETH predefMeth;
1619                     if (ek == ExpressionKind.Add)
1620                     {
1621                         ek = ExpressionKind.DecimalInc;
1622                         predefMeth = PREDEFMETH.PM_DECIMAL_OPINCREMENT;
1623                     }
1624                     else
1625                     {
1626                         ek = ExpressionKind.DecimalDec;
1627                         predefMeth = PREDEFMETH.PM_DECIMAL_OPDECREMENT;
1628                     }
1629 
1630                     return CreateUnaryOpForPredefMethodCall(ek, predefMeth, type, exprVal);
1631 
1632                 case FUNDTYPE.FT_I1:
1633                 case FUNDTYPE.FT_I2:
1634                 case FUNDTYPE.FT_U1:
1635                 case FUNDTYPE.FT_U2:
1636                     type = GetPredefindType(PredefinedType.PT_INT);
1637                     cv = ConstVal.Get(1);
1638                     break;
1639 
1640                 case FUNDTYPE.FT_I4:
1641                 case FUNDTYPE.FT_U4:
1642                     cv = ConstVal.Get(1);
1643                     break;
1644 
1645                 case FUNDTYPE.FT_I8:
1646                 case FUNDTYPE.FT_U8:
1647                     cv = ConstVal.Get((long)1);
1648                     break;
1649 
1650                 case FUNDTYPE.FT_R4:
1651                 case FUNDTYPE.FT_R8:
1652                     cv = ConstVal.Get(1.0);
1653                     break;
1654             }
1655 
1656             return LScalar(ek, flags, exprVal, type, cv, type);
1657         }
1658 
LScalar(ExpressionKind ek, EXPRFLAG flags, Expr exprVal, CType type, ConstVal cv, CType typeTmp)1659         private Expr LScalar(ExpressionKind ek, EXPRFLAG flags, Expr exprVal, CType type, ConstVal cv, CType typeTmp)
1660         {
1661             CType typeOne = type;
1662             if (typeOne.isEnumType())
1663             {
1664                 typeOne = typeOne.underlyingEnumType();
1665             }
1666 
1667             ExprBinOp pExprResult = GetExprFactory().CreateBinop(ek, typeTmp, exprVal, GetExprFactory().CreateConstant(typeOne, cv));
1668             pExprResult.Flags |= flags;
1669             return typeTmp != type ? mustCast(pExprResult, type, CONVERTTYPE.NOUDC) : pExprResult;
1670         }
1671 
BindNonliftedIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)1672         private ExprMulti BindNonliftedIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)
1673         {
1674             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract);
1675             Debug.Assert(!uofs.isLifted());
1676 
1677             Debug.Assert(arg != null);
1678             ExprMultiGet exprGet = GetExprFactory().CreateMultiGet(EXPRFLAG.EXF_ASSGOP, arg.Type, null);
1679             Expr exprVal = exprGet;
1680             CType type = uofs.GetType();
1681             Debug.Assert(!(type is NullableType));
1682 
1683             // These used to be converts, but we're making them casts now - this is because
1684             // we need to remove the ability to call inc(sbyte) etc for all types smaller than int.
1685             // Note however, that this will give us different error messages on compile time versus runtime
1686             // for checked increments.
1687             //
1688             // Also, we changed it so that we now generate the cast to and from enum for enum increments.
1689             exprVal = mustCast(exprVal, type);
1690             exprVal = BindIncOpCore(ek, flags, exprVal, type);
1691             Expr op = mustCast(exprVal, arg.Type, CONVERTTYPE.NOUDC);
1692 
1693             ExprMulti exprMulti = GetExprFactory().CreateMulti(EXPRFLAG.EXF_ASSGOP | flags, arg.Type, arg, op);
1694             exprGet.OptionalMulti = exprMulti;
1695             return exprMulti;
1696         }
1697 
BindLiftedIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)1698         private ExprMulti BindLiftedIncOp(ExpressionKind ek, EXPRFLAG flags, Expr arg, UnaOpFullSig uofs)
1699         {
1700             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract);
1701             Debug.Assert(uofs.isLifted());
1702 
1703             NullableType type = uofs.GetType() as NullableType;
1704             Debug.Assert(arg != null);
1705 
1706             ExprMultiGet exprGet = GetExprFactory().CreateMultiGet(EXPRFLAG.EXF_ASSGOP, arg.Type, null);
1707             Expr exprVal = exprGet;
1708             Expr nonLiftedArg = exprVal;
1709 
1710             // We want to give the lifted argument as the binop, but use the non-lifted argument as the
1711             // argument of the call.
1712             //Debug.Assert(uofs.LiftArg() || type.IsValType());
1713             nonLiftedArg = mustCast(nonLiftedArg, type.GetUnderlyingType());
1714             Expr nonLiftedResult = BindIncOpCore(ek, flags, nonLiftedArg, type.GetUnderlyingType());
1715             exprVal = mustCast(exprVal, type);
1716             ExprUnaryOp exprRes = GetExprFactory().CreateUnaryOp((ek == ExpressionKind.Add) ? ExpressionKind.Inc : ExpressionKind.Dec, arg.Type/* type */, exprVal);
1717             mustCast(mustCast(nonLiftedResult, type), arg.Type);
1718             exprRes.Flags |= flags;
1719 
1720             ExprMulti exprMulti = GetExprFactory().CreateMulti(EXPRFLAG.EXF_ASSGOP | flags, arg.Type, arg, exprRes);
1721             exprGet.OptionalMulti = exprMulti;
1722             return exprMulti;
1723         }
1724 
1725         /*
1726             Handles standard binary decimal based operators.
1727             This function is called twice by the EE for every binary operator it evaluates
1728             Here is how it works.
1729         */
BindDecBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1730         private ExprBinOp BindDecBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1731         {
1732             Debug.Assert(arg1.Type.isPredefType(PredefinedType.PT_DECIMAL) && arg2.Type.isPredefType(PredefinedType.PT_DECIMAL));
1733 
1734             CType typeDec = GetPredefindType(PredefinedType.PT_DECIMAL);
1735             Debug.Assert(typeDec != null);
1736 
1737             CType typeRet;
1738 
1739             switch (ek)
1740             {
1741                 default:
1742                     Debug.Fail($"Bad kind: {ek}");
1743                     typeRet = null;
1744                     break;
1745                 case ExpressionKind.Add:
1746                 case ExpressionKind.Subtract:
1747                 case ExpressionKind.Multiply:
1748                 case ExpressionKind.Divide:
1749                 case ExpressionKind.Modulo:
1750                     typeRet = typeDec;
1751                     break;
1752                 case ExpressionKind.LessThan:
1753                 case ExpressionKind.LessThanOrEqual:
1754                 case ExpressionKind.GreaterThan:
1755                 case ExpressionKind.GreaterThanOrEqual:
1756                 case ExpressionKind.Eq:
1757                 case ExpressionKind.NotEq:
1758                     typeRet = GetPredefindType(PredefinedType.PT_BOOL);
1759                     break;
1760             }
1761 
1762             return GetExprFactory().CreateBinop(ek, typeRet, arg1, arg2);
1763         }
1764 
1765 
1766         /*
1767             Handles standard unary decimal based operators.
1768         */
BindDecUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)1769         private ExprUnaryOp BindDecUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)
1770         {
1771             Debug.Assert(arg.Type.isPredefType(PredefinedType.PT_DECIMAL));
1772             Debug.Assert(ek == ExpressionKind.Negate || ek == ExpressionKind.UnaryPlus);
1773 
1774             CType typeDec = GetPredefindType(PredefinedType.PT_DECIMAL);
1775             Debug.Assert(typeDec != null);
1776 
1777             if (ek == ExpressionKind.Negate)
1778             {
1779                 PREDEFMETH predefMeth = PREDEFMETH.PM_DECIMAL_OPUNARYMINUS;
1780                 return CreateUnaryOpForPredefMethodCall(ExpressionKind.DecimalNegate, predefMeth, typeDec, arg);
1781             }
1782             return GetExprFactory().CreateUnaryOp(ExpressionKind.UnaryPlus, typeDec, arg);
1783         }
1784 
1785 
1786         /*
1787             Handles string concatenation.
1788         */
BindStrBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1789         private Expr BindStrBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1790         {
1791             Debug.Assert(ek == ExpressionKind.Add);
1792             Debug.Assert(arg1.Type.isPredefType(PredefinedType.PT_STRING) || arg2.Type.isPredefType(PredefinedType.PT_STRING));
1793             return bindStringConcat(arg1, arg2);
1794         }
1795 
1796 
1797         /*
1798             Bind a shift operator: <<, >>. These can have integer or long first operands,
1799             and second operand must be int.
1800         */
BindShiftOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1801         private ExprBinOp BindShiftOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1802         {
1803             Debug.Assert(ek == ExpressionKind.LeftShirt || ek == ExpressionKind.RightShift);
1804             Debug.Assert(arg1.Type.isPredefined());
1805             Debug.Assert(arg2.Type.isPredefType(PredefinedType.PT_INT));
1806 
1807             PredefinedType ptOp = arg1.Type.getPredefType();
1808             Debug.Assert(ptOp == PredefinedType.PT_INT || ptOp == PredefinedType.PT_UINT || ptOp == PredefinedType.PT_LONG || ptOp == PredefinedType.PT_ULONG);
1809 
1810             return GetExprFactory().CreateBinop(ek, arg1.Type, arg1, arg2);
1811         }
1812 
1813         /*
1814             Bind a bool binary operator: ==, !=, &&, ||, , |, ^. If both operands are constant, the
1815             result will be a constant also.
1816         */
BindBoolBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1817         private ExprBinOp BindBoolBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1818         {
1819             Debug.Assert(arg1 != null);
1820             Debug.Assert(arg2 != null);
1821             Debug.Assert(arg1.Type.isPredefType(PredefinedType.PT_BOOL) || (arg1.Type is NullableType argNubType1 && argNubType1.GetUnderlyingType().isPredefType(PredefinedType.PT_BOOL)));
1822             Debug.Assert(arg2.Type.isPredefType(PredefinedType.PT_BOOL) || (arg2.Type is NullableType argNubType2 && argNubType2.GetUnderlyingType().isPredefType(PredefinedType.PT_BOOL)));
1823 
1824             return GetExprFactory().CreateBinop(ek, GetPredefindType(PredefinedType.PT_BOOL), arg1, arg2);
1825         }
1826 
BindBoolBitwiseOp(ExpressionKind ek, EXPRFLAG flags, Expr expr1, Expr expr2, BinOpFullSig bofs)1827         private ExprOperator BindBoolBitwiseOp(ExpressionKind ek, EXPRFLAG flags, Expr expr1, Expr expr2, BinOpFullSig bofs)
1828         {
1829             Debug.Assert(ek == ExpressionKind.BitwiseAnd || ek == ExpressionKind.BitwiseOr);
1830             Debug.Assert(expr1.Type.isPredefType(PredefinedType.PT_BOOL) || expr1.Type is NullableType expNubType1 && expNubType1.GetUnderlyingType().isPredefType(PredefinedType.PT_BOOL));
1831             Debug.Assert(expr2.Type.isPredefType(PredefinedType.PT_BOOL) || expr2.Type is NullableType expNubType2 && expNubType2.GetUnderlyingType().isPredefType(PredefinedType.PT_BOOL));
1832 
1833             if (expr1.Type is NullableType || expr2.Type is NullableType)
1834             {
1835                 CType typeBool = GetPredefindType(PredefinedType.PT_BOOL);
1836                 CType typeRes = GetSymbolLoader().GetTypeManager().GetNullable(typeBool);
1837 
1838                 // Get the non-lifted result.
1839                 Expr nonLiftedArg1 = CNullable.StripNullableConstructor(expr1);
1840                 Expr nonLiftedArg2 = CNullable.StripNullableConstructor(expr2);
1841                 Expr nonLiftedResult = null;
1842 
1843                 if (!(nonLiftedArg1.Type is NullableType) && !(nonLiftedArg2.Type is NullableType))
1844                 {
1845                     nonLiftedResult = BindBoolBinOp(ek, flags, nonLiftedArg1, nonLiftedArg2);
1846                 }
1847 
1848                 // Make the binop and set that its lifted.
1849                 ExprBinOp exprRes = GetExprFactory().CreateBinop(ek, typeRes, expr1, expr2);
1850                 if (nonLiftedResult != null)
1851                 {
1852                     // Bitwise operators can have null non-lifted results if we have a nub sym somewhere.
1853                     mustCast(nonLiftedResult, typeRes, 0);
1854                 }
1855                 exprRes.IsLifted = true;
1856                 exprRes.Flags |= flags;
1857                 Debug.Assert((exprRes.Flags & EXPRFLAG.EXF_LVALUE) == 0);
1858                 return exprRes;
1859             }
1860             return BindBoolBinOp(ek, flags, expr1, expr2);
1861         }
1862 
BindLiftedBoolBitwiseOp(ExpressionKind ek, EXPRFLAG flags, Expr expr1, Expr expr2)1863         private Expr BindLiftedBoolBitwiseOp(ExpressionKind ek, EXPRFLAG flags, Expr expr1, Expr expr2)
1864         {
1865             return null;
1866         }
1867 
1868 
1869         /*
1870             Handles boolean unary operator (!).
1871         */
BindBoolUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)1872         private Expr BindBoolUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)
1873         {
1874             Debug.Assert(arg.Type.isPredefType(PredefinedType.PT_BOOL));
1875             Debug.Assert(ek == ExpressionKind.LogicalNot);
1876 
1877             // Get the result type and operand type.
1878             CType typeBool = GetPredefindType(PredefinedType.PT_BOOL);
1879 
1880             // Determine if arg has a constant value.
1881             // Strip off EXPRKIND.EK_SEQUENCE for constant checking.
1882 
1883             Expr argConst = arg.GetConst();
1884 
1885             if (argConst == null)
1886                 return GetExprFactory().CreateUnaryOp(ExpressionKind.LogicalNot, typeBool, arg);
1887 
1888             return GetExprFactory().CreateConstant(typeBool, ConstVal.Get(((ExprConstant)argConst).Val.Int32Val == 0));
1889         }
1890 
1891 
1892         /*
1893             Handles string equality.
1894         */
BindStrCmpOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1895         private ExprBinOp BindStrCmpOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1896         {
1897             Debug.Assert(ek == ExpressionKind.Eq || ek == ExpressionKind.NotEq);
1898             Debug.Assert(arg1.Type.isPredefType(PredefinedType.PT_STRING) && arg2.Type.isPredefType(PredefinedType.PT_STRING));
1899 
1900             // Get the predefined method for string comparison, and then stash it in the Expr so we can
1901             // transform it later.
1902 
1903             PREDEFMETH predefMeth = ek == ExpressionKind.Eq ? PREDEFMETH.PM_STRING_OPEQUALITY : PREDEFMETH.PM_STRING_OPINEQUALITY;
1904             ek = ek == ExpressionKind.Eq ? ExpressionKind.StringEq : ExpressionKind.StringNotEq;
1905             return CreateBinopForPredefMethodCall(ek, predefMeth, GetPredefindType(PredefinedType.PT_BOOL), arg1, arg2);
1906         }
1907 
1908 
1909         /*
1910             Handles reference equality operators. Type variables come through here.
1911         */
BindRefCmpOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1912         private ExprBinOp BindRefCmpOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1913         {
1914             Debug.Assert(ek == ExpressionKind.Eq || ek == ExpressionKind.NotEq);
1915 
1916             // Must box type variables for the verifier.
1917             arg1 = mustConvert(arg1, GetPredefindType(PredefinedType.PT_OBJECT), CONVERTTYPE.NOUDC);
1918             arg2 = mustConvert(arg2, GetPredefindType(PredefinedType.PT_OBJECT), CONVERTTYPE.NOUDC);
1919 
1920             return GetExprFactory().CreateBinop(ek, GetPredefindType(PredefinedType.PT_BOOL), arg1, arg2);
1921         }
1922 
1923 
1924         /*
1925             Handles delegate binary operators.
1926         */
BindDelBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1927         private Expr BindDelBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1928         {
1929             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract || ek == ExpressionKind.Eq || ek == ExpressionKind.NotEq);
1930             Debug.Assert(arg1.Type == arg2.Type && (arg1.Type.isDelegateType() || arg1.Type.isPredefType(PredefinedType.PT_DELEGATE)));
1931 
1932             PREDEFMETH predefMeth = (PREDEFMETH)0;
1933             CType RetType = null;
1934             switch (ek)
1935             {
1936                 case ExpressionKind.Add:
1937                     predefMeth = PREDEFMETH.PM_DELEGATE_COMBINE;
1938                     RetType = arg1.Type;
1939                     ek = ExpressionKind.DelegateAdd;
1940                     break;
1941 
1942                 case ExpressionKind.Subtract:
1943                     predefMeth = PREDEFMETH.PM_DELEGATE_REMOVE;
1944                     RetType = arg1.Type;
1945                     ek = ExpressionKind.DelegateSubtract;
1946                     break;
1947 
1948                 case ExpressionKind.Eq:
1949                     predefMeth = PREDEFMETH.PM_DELEGATE_OPEQUALITY;
1950                     RetType = GetPredefindType(PredefinedType.PT_BOOL);
1951                     ek = ExpressionKind.DelegateEq;
1952                     break;
1953 
1954                 case ExpressionKind.NotEq:
1955                     predefMeth = PREDEFMETH.PM_DELEGATE_OPINEQUALITY;
1956                     RetType = GetPredefindType(PredefinedType.PT_BOOL);
1957                     ek = ExpressionKind.DelegateNotEq;
1958                     break;
1959             }
1960             return CreateBinopForPredefMethodCall(ek, predefMeth, RetType, arg1, arg2);
1961         }
1962 
1963 
1964         /*
1965             Handles enum binary operators.
1966         */
BindEnumBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)1967         private Expr BindEnumBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
1968         {
1969             AggregateType typeDst = GetEnumBinOpType(ek, arg1.Type, arg2.Type, out AggregateType typeEnum);
1970 
1971             Debug.Assert(typeEnum != null);
1972             PredefinedType ptOp;
1973 
1974             switch (typeEnum.fundType())
1975             {
1976                 default:
1977                     // Promote all smaller types to int.
1978                     ptOp = PredefinedType.PT_INT;
1979                     break;
1980                 case FUNDTYPE.FT_U4:
1981                     ptOp = PredefinedType.PT_UINT;
1982                     break;
1983                 case FUNDTYPE.FT_I8:
1984                     ptOp = PredefinedType.PT_LONG;
1985                     break;
1986                 case FUNDTYPE.FT_U8:
1987                     ptOp = PredefinedType.PT_ULONG;
1988                     break;
1989             }
1990 
1991             CType typeOp = GetPredefindType(ptOp);
1992             arg1 = mustCast(arg1, typeOp, CONVERTTYPE.NOUDC);
1993             arg2 = mustCast(arg2, typeOp, CONVERTTYPE.NOUDC);
1994 
1995             Expr exprRes = BindIntOp(ek, flags, arg1, arg2, ptOp);
1996 
1997             if (exprRes.Type != typeDst)
1998             {
1999                 Debug.Assert(!typeDst.isPredefType(PredefinedType.PT_BOOL));
2000                 exprRes = mustCast(exprRes, typeDst, CONVERTTYPE.NOUDC);
2001             }
2002 
2003             return exprRes;
2004         }
2005 
BindLiftedEnumArithmeticBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)2006         private Expr BindLiftedEnumArithmeticBinOp(ExpressionKind ek, EXPRFLAG flags, Expr arg1, Expr arg2)
2007         {
2008             Debug.Assert(ek == ExpressionKind.Add || ek == ExpressionKind.Subtract);
2009             CType nonNullableType1 = arg1.Type is NullableType arg1NubType ? arg1NubType.UnderlyingType : arg1.Type;
2010             CType nonNullableType2 = arg2.Type is NullableType arg2NubType ? arg2NubType.UnderlyingType : arg2.Type;
2011             if (nonNullableType1 is NullType)
2012             {
2013                 nonNullableType1 = nonNullableType2.underlyingEnumType();
2014             }
2015             else if (nonNullableType2 is NullType)
2016             {
2017                 nonNullableType2 = nonNullableType1.underlyingEnumType();
2018             }
2019 
2020             AggregateType typeEnum;
2021             NullableType typeDst = GetTypes().GetNullable(GetEnumBinOpType(ek, nonNullableType1, nonNullableType2, out typeEnum));
2022 
2023             Debug.Assert(typeEnum != null);
2024             PredefinedType ptOp;
2025 
2026             switch (typeEnum.fundType())
2027             {
2028                 default:
2029                     // Promote all smaller types to int.
2030                     ptOp = PredefinedType.PT_INT;
2031                     break;
2032                 case FUNDTYPE.FT_U4:
2033                     ptOp = PredefinedType.PT_UINT;
2034                     break;
2035                 case FUNDTYPE.FT_I8:
2036                     ptOp = PredefinedType.PT_LONG;
2037                     break;
2038                 case FUNDTYPE.FT_U8:
2039                     ptOp = PredefinedType.PT_ULONG;
2040                     break;
2041             }
2042 
2043             NullableType typeOp = GetTypes().GetNullable(GetPredefindType(ptOp));
2044             arg1 = mustCast(arg1, typeOp, CONVERTTYPE.NOUDC);
2045             arg2 = mustCast(arg2, typeOp, CONVERTTYPE.NOUDC);
2046 
2047             ExprBinOp exprRes = GetExprFactory().CreateBinop(ek, typeOp, arg1, arg2);
2048             exprRes.IsLifted = true;
2049             exprRes.Flags |= flags;
2050             Debug.Assert((exprRes.Flags & EXPRFLAG.EXF_LVALUE) == 0);
2051 
2052             if (exprRes.Type != typeDst)
2053             {
2054                 return mustCast(exprRes, typeDst, CONVERTTYPE.NOUDC);
2055             }
2056 
2057             return exprRes;
2058         }
2059 
2060 
2061         /*
2062             Handles enum unary operator (~).
2063         */
BindEnumUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)2064         private Expr BindEnumUnaOp(ExpressionKind ek, EXPRFLAG flags, Expr arg)
2065         {
2066             Debug.Assert(ek == ExpressionKind.BitwiseNot);
2067             Debug.Assert((ExprCast)arg != null);
2068             Debug.Assert(((ExprCast)arg).Argument.Type.isEnumType());
2069 
2070             PredefinedType ptOp;
2071             CType typeEnum = ((ExprCast)arg).Argument.Type;
2072 
2073             switch (typeEnum.fundType())
2074             {
2075                 default:
2076                     // Promote all smaller types to int.
2077                     ptOp = PredefinedType.PT_INT;
2078                     break;
2079                 case FUNDTYPE.FT_U4:
2080                     ptOp = PredefinedType.PT_UINT;
2081                     break;
2082                 case FUNDTYPE.FT_I8:
2083                     ptOp = PredefinedType.PT_LONG;
2084                     break;
2085                 case FUNDTYPE.FT_U8:
2086                     ptOp = PredefinedType.PT_ULONG;
2087                     break;
2088             }
2089 
2090             CType typeOp = GetPredefindType(ptOp);
2091             arg = mustCast(arg, typeOp, CONVERTTYPE.NOUDC);
2092 
2093             Expr exprRes = BindIntOp(ek, flags, arg, null, ptOp);
2094             return mustCastInUncheckedContext(exprRes, typeEnum, CONVERTTYPE.NOUDC);
2095         }
2096 
2097         /*
2098             Given a binary operator EXPRKIND, get the BinOpKind and flags.
2099         */
GetBinopKindAndFlags(ExpressionKind ek)2100         private (BinOpKind, EXPRFLAG) GetBinopKindAndFlags(ExpressionKind ek)
2101         {
2102             BinOpKind pBinopKind;
2103             EXPRFLAG flags = 0;
2104             switch (ek)
2105             {
2106                 case ExpressionKind.Add:
2107                     if (Context.Checked)
2108                     {
2109                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
2110                     }
2111                     pBinopKind = BinOpKind.Add;
2112                     break;
2113                 case ExpressionKind.Subtract:
2114                     if (Context.Checked)
2115                     {
2116                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
2117                     }
2118                     pBinopKind = BinOpKind.Sub;
2119                     break;
2120                 case ExpressionKind.Divide:
2121                 case ExpressionKind.Modulo:
2122                     // EXPRKIND.EK_DIV and EXPRKIND.EK_MOD need to be treated special for hasSideEffects,
2123                     // hence the EXPRFLAG.EXF_ASSGOP. Yes, this is a hack.
2124                     flags = EXPRFLAG.EXF_ASSGOP;
2125                     if (Context.Checked)
2126                     {
2127                         flags |= EXPRFLAG.EXF_CHECKOVERFLOW;
2128                     }
2129                     pBinopKind = BinOpKind.Mul;
2130                     break;
2131                 case ExpressionKind.Multiply:
2132                     if (Context.Checked)
2133                     {
2134                         flags = EXPRFLAG.EXF_CHECKOVERFLOW;
2135                     }
2136                     pBinopKind = BinOpKind.Mul;
2137                     break;
2138                 case ExpressionKind.BitwiseAnd:
2139                 case ExpressionKind.BitwiseOr:
2140                     pBinopKind = BinOpKind.Bitwise;
2141                     break;
2142                 case ExpressionKind.BitwiseExclusiveOr:
2143                     pBinopKind = BinOpKind.BitXor;
2144                     break;
2145                 case ExpressionKind.LeftShirt:
2146                 case ExpressionKind.RightShift:
2147                     pBinopKind = BinOpKind.Shift;
2148                     break;
2149                 case ExpressionKind.LogicalOr:
2150                 case ExpressionKind.LogicalAnd:
2151                     pBinopKind = BinOpKind.Logical;
2152                     break;
2153                 case ExpressionKind.LessThan:
2154                 case ExpressionKind.LessThanOrEqual:
2155                 case ExpressionKind.GreaterThan:
2156                 case ExpressionKind.GreaterThanOrEqual:
2157                     pBinopKind = BinOpKind.Compare;
2158                     break;
2159                 case ExpressionKind.Eq:
2160                 case ExpressionKind.NotEq:
2161                     pBinopKind = BinOpKind.Equal;
2162                     break;
2163                 default:
2164                     Debug.Fail($"Bad ek: {ek}");
2165                     throw Error.InternalCompilerError();
2166             }
2167 
2168             return (pBinopKind, flags);
2169         }
2170 
2171         /*
2172             Convert an expression involving I4, U4, I8 or U8 operands. The operands are
2173             assumed to be already converted to the correct types.
2174         */
BindIntOp(ExpressionKind kind, EXPRFLAG flags, Expr op1, Expr op2, PredefinedType ptOp)2175         private ExprOperator BindIntOp(ExpressionKind kind, EXPRFLAG flags, Expr op1, Expr op2, PredefinedType ptOp)
2176         {
2177             //Debug.Assert(kind.isRelational() || kind.isArithmetic() || kind.isBitwise());
2178             Debug.Assert(ptOp == PredefinedType.PT_INT || ptOp == PredefinedType.PT_UINT || ptOp == PredefinedType.PT_LONG || ptOp == PredefinedType.PT_ULONG);
2179             CType typeOp = GetPredefindType(ptOp);
2180             Debug.Assert(typeOp != null);
2181             Debug.Assert(op1 != null && op1.Type == typeOp);
2182             Debug.Assert(op2 == null || op2.Type == typeOp);
2183             Debug.Assert((op2 == null) == (kind == ExpressionKind.Negate || kind == ExpressionKind.UnaryPlus || kind == ExpressionKind.BitwiseNot));
2184 
2185             if (kind == ExpressionKind.Negate)
2186             {
2187                 return BindIntegerNeg(flags, op1, ptOp);
2188             }
2189 
2190             CType typeDest = kind.IsRelational() ? GetPredefindType(PredefinedType.PT_BOOL) : typeOp;
2191 
2192             ExprOperator exprRes = GetExprFactory().CreateOperator(kind, typeDest, op1, op2);
2193             exprRes.Flags |= flags;
2194             Debug.Assert((exprRes.Flags & EXPRFLAG.EXF_LVALUE) == 0);
2195             return exprRes;
2196         }
2197 
BindIntegerNeg(EXPRFLAG flags, Expr op, PredefinedType ptOp)2198         private ExprOperator BindIntegerNeg(EXPRFLAG flags, Expr op, PredefinedType ptOp)
2199         {
2200             // 14.6.2 Unary minus operator
2201             // For an operation of the form -x, unary operator overload resolution (14.2.3) is applied to select
2202             // a specific operator implementation. The operand is converted to the parameter type of the selected
2203             // operator, and the type of the result is the return type of the operator. The predefined negation
2204             // operators are:
2205             //
2206             //  Integer negation:
2207             //
2208             //   int operator -(int x);
2209             //   long operator -(long x);
2210             //
2211             // The result is computed by subtracting x from zero. In a checked context, if the value of x is the
2212             // smallest int or long (-2^31 or -2^63, respectively), a System.OverflowException is thrown. In an
2213             //  unchecked context, if the value of x is the smallest int or long, the result is that same value
2214             // and the overflow is not reported.
2215             //
2216             // If the operand of the negation operator is of type uint, it is converted to type long, and the
2217             // type of the result is long. An exception is the rule that permits the int value -2147483648 (-2^31)
2218             // to be written as a decimal integer literal (9.4.4.2).
2219             //
2220             //  Negation of ulong is an error:
2221             //
2222             //   void operator -(ulong x);
2223             //
2224             // Selection of this operator by unary operator overload resolution (14.2.3) always results in a
2225             // compile-time error. Consequently, if the operand of the negation operator is of type ulong, a
2226             // compile-time error occurs. An exception is the rule that permits the long value
2227             // -9223372036854775808 (-2^63) to be written as a decimal integer literal (9.4.4.2).
2228 
2229 
2230             Debug.Assert(ptOp == PredefinedType.PT_INT || ptOp == PredefinedType.PT_UINT || ptOp == PredefinedType.PT_LONG || ptOp == PredefinedType.PT_ULONG);
2231             CType typeOp = GetPredefindType(ptOp);
2232             Debug.Assert(typeOp != null);
2233             Debug.Assert(op != null && op.Type == typeOp);
2234 
2235             if (ptOp == PredefinedType.PT_ULONG)
2236             {
2237                 throw BadOperatorTypesError(op, null);
2238             }
2239 
2240             if (ptOp == PredefinedType.PT_UINT && op.Type.fundType() == FUNDTYPE.FT_U4)
2241             {
2242                 op = mustConvertCore(op, GetPredefindType(PredefinedType.PT_LONG), CONVERTTYPE.NOUDC);
2243             }
2244 
2245             ExprOperator exprRes = GetExprFactory().CreateNeg(flags, op);
2246             Debug.Assert(0 == (exprRes.Flags & EXPRFLAG.EXF_LVALUE));
2247             return exprRes;
2248         }
2249 
2250         /*
2251           Bind an float/double operator: +, -, , /, %, <, >, <=, >=, ==, !=. If both operations are constants, the result
2252           will be a constant also. op2 can be null for a unary operator. The operands are assumed
2253           to be already converted to the correct type.
2254          */
bindFloatOp(ExpressionKind kind, Expr op1, Expr op2)2255         private ExprOperator bindFloatOp(ExpressionKind kind, Expr op1, Expr op2)
2256         {
2257             //Debug.Assert(kind.isRelational() || kind.isArithmetic());
2258             Debug.Assert(op2 == null || op1.Type == op2.Type);
2259             Debug.Assert(op1.Type.isPredefType(PredefinedType.PT_FLOAT) || op1.Type.isPredefType(PredefinedType.PT_DOUBLE));
2260 
2261             // Allocate the result expression.
2262             CType typeDest = kind.IsRelational() ? GetPredefindType(PredefinedType.PT_BOOL) : op1.Type;
2263 
2264             ExprOperator exprRes = GetExprFactory().CreateOperator(kind, typeDest, op1, op2);
2265             exprRes.Flags &= ~EXPRFLAG.EXF_CHECKOVERFLOW;
2266 
2267             return exprRes;
2268         }
2269 
bindStringConcat(Expr op1, Expr op2)2270         private ExprConcat bindStringConcat(Expr op1, Expr op2)
2271         {
2272             // If the concatenation consists solely of two constants then we must
2273             // realize the concatenation into a single constant node at this time.
2274             // Why?  Because we have to know whether
2275             //
2276             //  string x = "c" + "d";
2277             //
2278             // is legal or not.  We also need to be able to determine during flow
2279             // checking that
2280             //
2281             // switch("a" + "b"){ case "a": ++foo; break; }
2282             //
2283             // contains unreachable code.
2284             //
2285             // However we can defer further merging of concatenation trees until
2286             // the optimization pass after flow checking.
2287 
2288             Debug.Assert(op1 != null);
2289             Debug.Assert(op2 != null);
2290             return GetExprFactory().CreateConcat(op1, op2);
2291         }
2292 
2293         /*
2294           Report an ambiguous operator types error.
2295          */
AmbiguousOperatorError(ExpressionKind ek, Expr op1, Expr op2)2296         private RuntimeBinderException AmbiguousOperatorError(ExpressionKind ek, Expr op1, Expr op2)
2297         {
2298             Debug.Assert(op1 != null);
2299 
2300             // This is exactly the same "hack" that BadOperatorError uses. The first operand contains the
2301             // name of the operator in its errorString.
2302             string strOp = op1.ErrorString;
2303 
2304             // Bad arg types - report error to user.
2305             return op2 != null
2306                 ? GetErrorContext().Error(ErrorCode.ERR_AmbigBinaryOps, strOp, op1.Type, op2.Type)
2307                 : GetErrorContext().Error(ErrorCode.ERR_AmbigUnaryOp, strOp, op1.Type);
2308         }
2309 
BindUserBoolOp(ExpressionKind kind, ExprCall pCall)2310         private Expr BindUserBoolOp(ExpressionKind kind, ExprCall pCall)
2311         {
2312             Debug.Assert(pCall != null);
2313             Debug.Assert(pCall.MethWithInst.Meth() != null);
2314             Debug.Assert(pCall.OptionalArguments != null);
2315             Debug.Assert(kind == ExpressionKind.LogicalAnd || kind == ExpressionKind.LogicalOr);
2316 
2317             CType typeRet = pCall.Type;
2318 
2319             Debug.Assert(pCall.MethWithInst.Meth().Params.Count == 2);
2320             if (!GetTypes().SubstEqualTypes(typeRet, pCall.MethWithInst.Meth().Params[0], typeRet) ||
2321                 !GetTypes().SubstEqualTypes(typeRet, pCall.MethWithInst.Meth().Params[1], typeRet))
2322             {
2323                 throw GetErrorContext().Error(ErrorCode.ERR_BadBoolOp, pCall.MethWithInst);
2324             }
2325 
2326             ExprList list = (ExprList)pCall.OptionalArguments;
2327             Debug.Assert(list != null);
2328 
2329             Expr pExpr = list.OptionalElement;
2330             ExprWrap pExprWrap = WrapShortLivedExpression(pExpr);
2331             list.OptionalElement = pExprWrap;
2332 
2333             // Reflection load the true and false methods.
2334             SymbolLoader.RuntimeBinderSymbolTable.PopulateSymbolTableWithName(SpecialNames.CLR_True, null, pExprWrap.Type.AssociatedSystemType);
2335             SymbolLoader.RuntimeBinderSymbolTable.PopulateSymbolTableWithName(SpecialNames.CLR_False, null, pExprWrap.Type.AssociatedSystemType);
2336 
2337             Expr pCallT = bindUDUnop(ExpressionKind.True, pExprWrap);
2338             Expr pCallF = bindUDUnop(ExpressionKind.False, pExprWrap);
2339 
2340             if (pCallT == null || pCallF == null)
2341             {
2342                 throw GetErrorContext().Error(ErrorCode.ERR_MustHaveOpTF, typeRet);
2343             }
2344 
2345             pCallT = mustConvert(pCallT, GetPredefindType(PredefinedType.PT_BOOL));
2346             pCallF = mustConvert(pCallF, GetPredefindType(PredefinedType.PT_BOOL));
2347             return GetExprFactory().CreateUserLogOp(typeRet, kind == ExpressionKind.LogicalAnd ? pCallF : pCallT, pCall);
2348         }
2349 
GetUserDefinedBinopArgumentType(CType type)2350         private AggregateType GetUserDefinedBinopArgumentType(CType type)
2351         {
2352             for (; ;)
2353             {
2354                 switch (type.GetTypeKind())
2355                 {
2356                     case TypeKind.TK_NullableType:
2357                         type = type.StripNubs();
2358                         break;
2359                     case TypeKind.TK_AggregateType:
2360                         AggregateType ats = (AggregateType)type;
2361                         if ((ats.isClassType() || ats.isStructType()) && !ats.getAggregate().IsSkipUDOps())
2362                         {
2363                             return ats;
2364                         }
2365                         return null;
2366                     default:
2367                         return null;
2368                 }
2369             }
2370         }
2371 
GetUserDefinedBinopArgumentTypes(CType type1, CType type2, AggregateType[] rgats)2372         private int GetUserDefinedBinopArgumentTypes(CType type1, CType type2, AggregateType[] rgats)
2373         {
2374             int cats = 0;
2375             rgats[0] = GetUserDefinedBinopArgumentType(type1);
2376             if (rgats[0] != null)
2377             {
2378                 ++cats;
2379             }
2380             rgats[cats] = GetUserDefinedBinopArgumentType(type2);
2381             if (rgats[cats] != null)
2382             {
2383                 ++cats;
2384             }
2385             if (cats == 2 && rgats[0] == rgats[1])
2386             {
2387                 // Common case: they're the same.
2388                 cats = 1;
2389             }
2390             return cats;
2391         }
2392 
UserDefinedBinaryOperatorCanBeLifted(ExpressionKind ek, MethodSymbol method, AggregateType ats, TypeArray Params)2393         private bool UserDefinedBinaryOperatorCanBeLifted(ExpressionKind ek, MethodSymbol method, AggregateType ats,
2394             TypeArray Params)
2395         {
2396             if (!Params[0].IsNonNubValType())
2397             {
2398                 return false;
2399             }
2400             if (!Params[1].IsNonNubValType())
2401             {
2402                 return false;
2403             }
2404             CType typeRet = GetTypes().SubstType(method.RetType, ats);
2405             if (!typeRet.IsNonNubValType())
2406             {
2407                 return false;
2408             }
2409             switch (ek)
2410             {
2411                 case ExpressionKind.Eq:
2412                 case ExpressionKind.NotEq:
2413                     if (!typeRet.isPredefType(PredefinedType.PT_BOOL))
2414                     {
2415                         return false;
2416                     }
2417                     if (Params[0] != Params[1])
2418                     {
2419                         return false;
2420                     }
2421                     return true;
2422                 case ExpressionKind.GreaterThan:
2423                 case ExpressionKind.GreaterThanOrEqual:
2424                 case ExpressionKind.LessThan:
2425                 case ExpressionKind.LessThanOrEqual:
2426                     if (!typeRet.isPredefType(PredefinedType.PT_BOOL))
2427                     {
2428                         return false;
2429                     }
2430                     return true;
2431                 default:
2432                     return true;
2433             }
2434         }
2435 
2436         // If the operator is applicable in either its regular or lifted forms,
2437         // add it to the candidate set and return true, otherwise return false.
UserDefinedBinaryOperatorIsApplicable(List<CandidateFunctionMember> candidateList, ExpressionKind ek, MethodSymbol method, AggregateType ats, Expr arg1, Expr arg2, bool fDontLift)2438         private bool UserDefinedBinaryOperatorIsApplicable(List<CandidateFunctionMember> candidateList,
2439             ExpressionKind ek, MethodSymbol method, AggregateType ats, Expr arg1, Expr arg2, bool fDontLift)
2440         {
2441             if (!method.isOperator || method.Params.Count != 2)
2442             {
2443                 return false;
2444             }
2445             Debug.Assert(method.typeVars.Count == 0);
2446             TypeArray paramsCur = GetTypes().SubstTypeArray(method.Params, ats);
2447             if (canConvert(arg1, paramsCur[0]) && canConvert(arg2, paramsCur[1]))
2448             {
2449                 candidateList.Add(new CandidateFunctionMember(
2450                     new MethPropWithInst(method, ats, BSYMMGR.EmptyTypeArray()),
2451                     paramsCur,
2452                     0, // No lifted arguments
2453                     false));
2454                 return true;
2455             }
2456             if (fDontLift || !UserDefinedBinaryOperatorCanBeLifted(ek, method, ats, paramsCur))
2457             {
2458                 return false;
2459             }
2460             CType[] rgtype = new CType[2];
2461             rgtype[0] = GetTypes().GetNullable(paramsCur[0]);
2462             rgtype[1] = GetTypes().GetNullable(paramsCur[1]);
2463             if (!canConvert(arg1, rgtype[0]) || !canConvert(arg2, rgtype[1]))
2464             {
2465                 return false;
2466             }
2467             candidateList.Add(new CandidateFunctionMember(
2468                 new MethPropWithInst(method, ats, BSYMMGR.EmptyTypeArray()),
2469                 GetGlobalSymbols().AllocParams(2, rgtype),
2470                 2, // two lifted arguments
2471                 false));
2472             return true;
2473         }
2474 
GetApplicableUserDefinedBinaryOperatorCandidates( List<CandidateFunctionMember> candidateList, ExpressionKind ek, AggregateType type, Expr arg1, Expr arg2, bool fDontLift)2475         private bool GetApplicableUserDefinedBinaryOperatorCandidates(
2476             List<CandidateFunctionMember> candidateList, ExpressionKind ek, AggregateType type,
2477             Expr arg1, Expr arg2, bool fDontLift)
2478         {
2479             Name name = ekName(ek);
2480             Debug.Assert(name != null);
2481             bool foundSome = false;
2482             for (MethodSymbol methCur = GetSymbolLoader().LookupAggMember(name, type.getAggregate(), symbmask_t.MASK_MethodSymbol) as MethodSymbol;
2483                 methCur != null;
2484                 methCur = SymbolLoader.LookupNextSym(methCur, type.getAggregate(), symbmask_t.MASK_MethodSymbol) as MethodSymbol)
2485             {
2486                 if (UserDefinedBinaryOperatorIsApplicable(candidateList, ek, methCur, type, arg1, arg2, fDontLift))
2487                 {
2488                     foundSome = true;
2489                 }
2490             }
2491             return foundSome;
2492         }
2493 
GetApplicableUserDefinedBinaryOperatorCandidatesInBaseTypes( List<CandidateFunctionMember> candidateList, ExpressionKind ek, AggregateType type, Expr arg1, Expr arg2, bool fDontLift, AggregateType atsStop)2494         private AggregateType GetApplicableUserDefinedBinaryOperatorCandidatesInBaseTypes(
2495             List<CandidateFunctionMember> candidateList, ExpressionKind ek, AggregateType type,
2496             Expr arg1, Expr arg2, bool fDontLift, AggregateType atsStop)
2497         {
2498             for (AggregateType atsCur = type; atsCur != null && atsCur != atsStop; atsCur = atsCur.GetBaseClass())
2499             {
2500                 if (GetApplicableUserDefinedBinaryOperatorCandidates(candidateList, ek, atsCur, arg1, arg2, fDontLift))
2501                 {
2502                     return atsCur;
2503                 }
2504             }
2505             return null;
2506         }
2507 
BindUDBinop(ExpressionKind ek, Expr arg1, Expr arg2, bool fDontLift, out MethPropWithInst ppmpwi)2508         private ExprCall BindUDBinop(ExpressionKind ek, Expr arg1, Expr arg2, bool fDontLift, out MethPropWithInst ppmpwi)
2509         {
2510             List<CandidateFunctionMember> methFirst = new List<CandidateFunctionMember>();
2511 
2512             ppmpwi = null;
2513 
2514             AggregateType[] rgats = { null, null };
2515             int cats = GetUserDefinedBinopArgumentTypes(arg1.Type, arg2.Type, rgats);
2516             if (cats == 0)
2517             {
2518                 return null;
2519             }
2520             else if (cats == 1)
2521             {
2522                 GetApplicableUserDefinedBinaryOperatorCandidatesInBaseTypes(methFirst, ek,
2523                     rgats[0], arg1, arg2, fDontLift, null);
2524             }
2525             else
2526             {
2527                 Debug.Assert(cats == 2);
2528                 AggregateType atsStop = GetApplicableUserDefinedBinaryOperatorCandidatesInBaseTypes(methFirst, ek,
2529                     rgats[0], arg1, arg2, fDontLift, null);
2530                 GetApplicableUserDefinedBinaryOperatorCandidatesInBaseTypes(methFirst, ek,
2531                     rgats[1], arg1, arg2, fDontLift, atsStop);
2532             }
2533             if (methFirst.IsEmpty())
2534             {
2535                 return null;
2536             }
2537 
2538             ExprList args = GetExprFactory().CreateList(arg1, arg2);
2539             ArgInfos info = new ArgInfos();
2540             info.carg = 2;
2541             FillInArgInfoFromArgList(info, args);
2542             CandidateFunctionMember pmethAmbig1;
2543             CandidateFunctionMember pmethAmbig2;
2544             CandidateFunctionMember pmethBest = FindBestMethod(methFirst, null, info, out pmethAmbig1, out pmethAmbig2);
2545 
2546             if (pmethBest == null)
2547             {
2548                 // No winner, so its an ambiguous call...
2549                 throw GetErrorContext().Error(ErrorCode.ERR_AmbigCall, pmethAmbig1.mpwi, pmethAmbig2.mpwi);
2550             }
2551 
2552             ppmpwi = pmethBest.mpwi;
2553 
2554             if (pmethBest.ctypeLift != 0)
2555             {
2556                 Debug.Assert(pmethBest.ctypeLift == 2);
2557 
2558                 return BindLiftedUDBinop(ek, arg1, arg2, pmethBest.@params, pmethBest.mpwi);
2559             }
2560 
2561             CType typeRetRaw = GetTypes().SubstType(pmethBest.mpwi.Meth().RetType, pmethBest.mpwi.GetType());
2562 
2563             return BindUDBinopCall(arg1, arg2, pmethBest.@params, typeRetRaw, pmethBest.mpwi);
2564         }
2565 
BindUDBinopCall(Expr arg1, Expr arg2, TypeArray Params, CType typeRet, MethPropWithInst mpwi)2566         private ExprCall BindUDBinopCall(Expr arg1, Expr arg2, TypeArray Params, CType typeRet, MethPropWithInst mpwi)
2567         {
2568             arg1 = mustConvert(arg1, Params[0]);
2569             arg2 = mustConvert(arg2, Params[1]);
2570             ExprList args = GetExprFactory().CreateList(arg1, arg2);
2571 
2572             checkUnsafe(arg1.Type); // added to the binder so we don't bind to pointer ops
2573             checkUnsafe(arg2.Type); // added to the binder so we don't bind to pointer ops
2574             checkUnsafe(typeRet); // added to the binder so we don't bind to pointer ops
2575 
2576 
2577             ExprMemberGroup pMemGroup = GetExprFactory().CreateMemGroup(null, mpwi);
2578             ExprCall call = GetExprFactory().CreateCall(0, typeRet, args, pMemGroup, null);
2579             call.MethWithInst = new MethWithInst(mpwi);
2580             verifyMethodArgs(call, mpwi.GetType());
2581             return call;
2582         }
2583 
BindLiftedUDBinop(ExpressionKind ek, Expr arg1, Expr arg2, TypeArray Params, MethPropWithInst mpwi)2584         private ExprCall BindLiftedUDBinop(ExpressionKind ek, Expr arg1, Expr arg2, TypeArray Params, MethPropWithInst mpwi)
2585         {
2586             Expr exprVal1 = arg1;
2587             Expr exprVal2 = arg2;
2588             CType typeRet;
2589             CType typeRetRaw = GetTypes().SubstType(mpwi.Meth().RetType, mpwi.GetType());
2590 
2591             // This is a lifted user defined operator.  We know that both arguments
2592             // go to the nullable formal parameter types, and that at least one
2593             // of the arguments does not go to the non-nullable formal parameter type.
2594             // (If both went to the non-nullable types then we would not be lifting.)
2595             // We also know that the non-nullable type of the argument goes to the
2596             // non-nullable type of formal parameter.  However, if it does so only via
2597             // a user-defined conversion then we should bind the conversion from the
2598             // argument to the nullable formal parameter type first, before we then
2599             // do the cast for the non-nullable call.
2600 
2601             TypeArray paramsRaw = GetTypes().SubstTypeArray(mpwi.Meth().Params, mpwi.GetType());
2602             Debug.Assert(Params != paramsRaw);
2603             Debug.Assert(paramsRaw[0] == Params[0].GetBaseOrParameterOrElementType());
2604             Debug.Assert(paramsRaw[1] == Params[1].GetBaseOrParameterOrElementType());
2605 
2606             if (!canConvert(arg1.Type.StripNubs(), paramsRaw[0], CONVERTTYPE.NOUDC))
2607             {
2608                 exprVal1 = mustConvert(arg1, Params[0]);
2609             }
2610             if (!canConvert(arg2.Type.StripNubs(), paramsRaw[1], CONVERTTYPE.NOUDC))
2611             {
2612                 exprVal2 = mustConvert(arg2, Params[1]);
2613             }
2614             Expr nonLiftedArg1 = mustCast(exprVal1, paramsRaw[0]);
2615             Expr nonLiftedArg2 = mustCast(exprVal2, paramsRaw[1]);
2616             switch (ek)
2617             {
2618                 default:
2619                     typeRet = GetTypes().GetNullable(typeRetRaw);
2620                     break;
2621                 case ExpressionKind.Eq:
2622                 case ExpressionKind.NotEq:
2623                     Debug.Assert(paramsRaw[0] == paramsRaw[1]);
2624                     Debug.Assert(typeRetRaw.isPredefType(PredefinedType.PT_BOOL));
2625                     // These ones don't lift the return type. Instead, if either side is null, the result is false.
2626                     typeRet = typeRetRaw;
2627                     break;
2628                 case ExpressionKind.GreaterThan:
2629                 case ExpressionKind.GreaterThanOrEqual:
2630                 case ExpressionKind.LessThan:
2631                 case ExpressionKind.LessThanOrEqual:
2632                     Debug.Assert(typeRetRaw.isPredefType(PredefinedType.PT_BOOL));
2633                     // These ones don't lift the return type. Instead, if either side is null, the result is false.
2634                     typeRet = typeRetRaw;
2635                     break;
2636             }
2637 
2638             // Now get the result for the pre-lifted call.
2639 
2640             Debug.Assert(!(ek == ExpressionKind.Eq || ek == ExpressionKind.NotEq) || nonLiftedArg1.Type == nonLiftedArg2.Type);
2641 
2642             ExprCall nonLiftedResult = BindUDBinopCall(nonLiftedArg1, nonLiftedArg2, paramsRaw, typeRetRaw, mpwi);
2643 
2644             ExprList args = GetExprFactory().CreateList(exprVal1, exprVal2);
2645             ExprMemberGroup pMemGroup = GetExprFactory().CreateMemGroup(null, mpwi);
2646             ExprCall call = GetExprFactory().CreateCall(0, typeRet, args, pMemGroup, null);
2647             call.MethWithInst = new MethWithInst(mpwi);
2648 
2649             switch (ek)
2650             {
2651                 case ExpressionKind.Eq:
2652                     call.NullableCallLiftKind = NullableCallLiftKind.EqualityOperator;
2653                     break;
2654 
2655                 case ExpressionKind.NotEq:
2656                     call.NullableCallLiftKind = NullableCallLiftKind.InequalityOperator;
2657                     break;
2658 
2659                 default:
2660                     call.NullableCallLiftKind = NullableCallLiftKind.Operator;
2661                     break;
2662             }
2663 
2664             call.CastOfNonLiftedResultToLiftedType = mustCast(nonLiftedResult, typeRet, 0);
2665             return call;
2666         }
2667 
GetEnumBinOpType(ExpressionKind ek, CType argType1, CType argType2, out AggregateType ppEnumType)2668         private AggregateType GetEnumBinOpType(ExpressionKind ek, CType argType1, CType argType2, out AggregateType ppEnumType)
2669         {
2670             Debug.Assert(argType1.isEnumType() || argType2.isEnumType());
2671 
2672             AggregateType type1 = argType1 as AggregateType;
2673             AggregateType type2 = argType2 as AggregateType;
2674 
2675             AggregateType typeEnum = type1.isEnumType() ? type1 : type2;
2676 
2677             Debug.Assert(type1 == typeEnum || type1 == typeEnum.underlyingEnumType());
2678             Debug.Assert(type2 == typeEnum || type2 == typeEnum.underlyingEnumType());
2679 
2680             AggregateType typeDst = typeEnum;
2681 
2682             switch (ek)
2683             {
2684                 case ExpressionKind.BitwiseAnd:
2685                 case ExpressionKind.BitwiseOr:
2686                 case ExpressionKind.BitwiseExclusiveOr:
2687                     Debug.Assert(type1 == type2);
2688                     break;
2689 
2690                 case ExpressionKind.Add:
2691                     Debug.Assert(type1 != type2);
2692                     break;
2693 
2694                 case ExpressionKind.Subtract:
2695                     if (type1 == type2)
2696                         typeDst = typeEnum.underlyingEnumType();
2697                     break;
2698 
2699                 default:
2700                     Debug.Assert(ek.IsRelational());
2701                     typeDst = GetPredefindType(PredefinedType.PT_BOOL);
2702                     break;
2703             }
2704 
2705             ppEnumType = typeEnum;
2706             return typeDst;
2707         }
2708 
CreateBinopForPredefMethodCall(ExpressionKind ek, PREDEFMETH predefMeth, CType RetType, Expr arg1, Expr arg2)2709         private ExprBinOp CreateBinopForPredefMethodCall(ExpressionKind ek, PREDEFMETH predefMeth, CType RetType, Expr arg1, Expr arg2)
2710         {
2711             MethodSymbol methSym = GetSymbolLoader().getPredefinedMembers().GetMethod(predefMeth);
2712             Debug.Assert(methSym != null);
2713             ExprBinOp binop = GetExprFactory().CreateBinop(ek, RetType, arg1, arg2);
2714 
2715             // Set the predefined method to call.
2716             AggregateSymbol agg = methSym.getClass();
2717             AggregateType callingType = GetTypes().GetAggregate(agg, BSYMMGR.EmptyTypeArray());
2718             binop.PredefinedMethodToCall = new MethWithInst(methSym, callingType, null);
2719             binop.UserDefinedCallMethod = binop.PredefinedMethodToCall;
2720             return binop;
2721         }
2722 
CreateUnaryOpForPredefMethodCall(ExpressionKind ek, PREDEFMETH predefMeth, CType pRetType, Expr pArg)2723         private ExprUnaryOp CreateUnaryOpForPredefMethodCall(ExpressionKind ek, PREDEFMETH predefMeth, CType pRetType, Expr pArg)
2724         {
2725             MethodSymbol methSym = GetSymbolLoader().getPredefinedMembers().GetMethod(predefMeth);
2726             Debug.Assert(methSym != null);
2727             ExprUnaryOp pUnaryOp = GetExprFactory().CreateUnaryOp(ek, pRetType, pArg);
2728 
2729             // Set the predefined method to call.
2730             AggregateSymbol pAgg = methSym.getClass();
2731             AggregateType pCallingType = GetTypes().GetAggregate(pAgg, BSYMMGR.EmptyTypeArray());
2732             pUnaryOp.PredefinedMethodToCall = new MethWithInst(methSym, pCallingType, null);
2733             pUnaryOp.UserDefinedCallMethod = pUnaryOp.PredefinedMethodToCall;
2734             return pUnaryOp;
2735         }
2736     }
2737 }
2738