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