@@ -384,7 +384,16 @@ sealed interface TsBinaryOperator {
384384 rhs : UHeapRef ,
385385 scope : TsStepScope ,
386386 ): UBoolExpr {
387- return mkEq(lhs, rhs)
387+ // Note: in JavaScript, `null == undefined`
388+ val lhsIsNull = mkEq(lhs, mkTsNullValue())
389+ val rhsIsNull = mkEq(rhs, mkTsNullValue())
390+ val lhsIsUndefined = mkEq(lhs, mkUndefinedValue())
391+ val rhsIsUndefined = mkEq(rhs, mkUndefinedValue())
392+ return mkOr(
393+ mkAnd(lhsIsUndefined, rhsIsNull),
394+ mkAnd(lhsIsNull, rhsIsUndefined),
395+ mkHeapRefEq(lhs, rhs)
396+ )
388397 }
389398
390399 override fun TsContext.resolveFakeObject (
@@ -412,42 +421,35 @@ sealed interface TsBinaryOperator {
412421 if (lhs.sort == boolSort && rhs.sort == boolSort) {
413422 val lhs = lhs.asExpr(boolSort)
414423 val rhs = rhs.asExpr(boolSort)
415- return mkEq (lhs, rhs)
424+ return onBool (lhs, rhs, scope )
416425 }
417426
418427 // fp == fp
419428 if (lhs.sort == fp64Sort && rhs.sort == fp64Sort) {
420429 val lhs = lhs.asExpr(fp64Sort)
421430 val rhs = rhs.asExpr(fp64Sort)
422- return mkFpEqualExpr (lhs, rhs)
431+ return onFp (lhs, rhs, scope )
423432 }
424433
425434 // bool == fp
426435 if (lhs.sort == boolSort && rhs.sort == fp64Sort) {
427436 val lhs = lhs.asExpr(boolSort)
428437 val rhs = rhs.asExpr(fp64Sort)
429- return mkFpEqualExpr (boolToFp(lhs), rhs)
438+ return onFp (boolToFp(lhs), rhs, scope )
430439 }
431440
432441 // fp == bool
433442 if (lhs.sort == fp64Sort && rhs.sort == boolSort) {
434443 val lhs = lhs.asExpr(fp64Sort)
435444 val rhs = rhs.asExpr(boolSort)
436- return mkFpEqualExpr (lhs, boolToFp(rhs))
445+ return onFp (lhs, boolToFp(rhs), scope )
437446 }
438447
439448 // ref == ref
440449 if (lhs.sort == addressSort && rhs.sort == addressSort) {
441- // Note:
442- // undefined == null
443- // null == undefined
444450 val lhs = lhs.asExpr(addressSort)
445451 val rhs = rhs.asExpr(addressSort)
446- return mkOr(
447- mkEq(lhs, rhs),
448- mkEq(lhs, mkUndefinedValue()) and mkEq(rhs, mkTsNullValue()),
449- mkEq(lhs, mkTsNullValue()) and mkEq(rhs, mkUndefinedValue()),
450- )
452+ return onRef(lhs, rhs, scope)
451453 }
452454
453455 // bool == ref
@@ -576,7 +578,7 @@ sealed interface TsBinaryOperator {
576578 lhsType.boolTypeExpr eq rhsType.boolTypeExpr,
577579 lhsType.fpTypeExpr eq rhsType.fpTypeExpr,
578580 // TODO support type equality
579- lhsType.refTypeExpr eq rhsType.refTypeExpr
581+ lhsType.refTypeExpr eq rhsType.refTypeExpr,
580582 )
581583 }
582584
@@ -631,11 +633,27 @@ sealed interface TsBinaryOperator {
631633 }
632634 }
633635
634- val loosyEqualityConstraint = with (Eq ) {
636+ check(! lhsValue.isFakeObject()) { " Nested fake objects are not supported" }
637+ check(! rhsValue.isFakeObject()) { " Nested fake objects are not supported" }
638+
639+ // Note: this is the case 'ref === ref',
640+ // which should be `true` only if both have the same reference.
641+ // It is not correct to delegate to `Eq.resolve` in this case,
642+ // since `==` treats `null == undefined`, while `null !== undefined`.
643+ if (lhsValue.sort == addressSort && rhsValue.sort == addressSort) {
644+ val left = lhsValue.asExpr(addressSort)
645+ val right = rhsValue.asExpr(addressSort)
646+ return mkAnd(
647+ typeConstraint,
648+ mkHeapRefEq(left, right)
649+ )
650+ }
651+
652+ val looseEqualityConstraint = with (Eq ) {
635653 resolve(lhsValue, rhsValue, scope)?.asExpr(boolSort) ? : error(" Should not be encountered" )
636654 }
637655
638- return mkAnd(typeConstraint, loosyEqualityConstraint )
656+ return mkAnd(typeConstraint, looseEqualityConstraint )
639657 }
640658
641659 override fun TsContext.internalResolve (
0 commit comments