diff --git a/src/compiler.ts b/src/compiler.ts index 2567805aff..d5c67bcbb0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -3946,7 +3946,10 @@ export class Compiler extends DiagnosticEmitter { case Token.Equals_Equals_Equals: case Token.Equals_Equals: { - leftExpr = this.compileExpression(left, contextualType); + commonType = this.resolver.resolveCompareExpressionCommonType(expression, this.currentFlow); + if (!commonType) return module.unreachable(); + + leftExpr = this.compileExpression(left, commonType); leftType = this.currentType; // check operator overload @@ -3959,17 +3962,9 @@ export class Compiler extends DiagnosticEmitter { } } - rightExpr = this.compileExpression(right, leftType); + rightExpr = this.compileExpression(right, commonType); rightType = this.currentType; - commonType = Type.commonType(leftType, rightType, contextualType); - if (!commonType) { - this.error( - DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, - expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString() - ); - this.currentType = contextualType; - return module.unreachable(); - } + if (commonType.isFloatValue) { if ( isConstExpressionNaN(module, rightExpr) || @@ -3998,9 +3993,13 @@ export class Compiler extends DiagnosticEmitter { } case Token.Exclamation_Equals_Equals: case Token.Exclamation_Equals: { - leftExpr = this.compileExpression(left, contextualType); + commonType = this.resolver.resolveCompareExpressionCommonType(expression, this.currentFlow); + if (!commonType) return module.unreachable(); + + leftExpr = this.compileExpression(left, commonType); leftType = this.currentType; + // check operator overload let classReference = leftType.getClass(); if (classReference) { @@ -4011,17 +4010,9 @@ export class Compiler extends DiagnosticEmitter { } } - rightExpr = this.compileExpression(right, leftType); + rightExpr = this.compileExpression(right, commonType); rightType = this.currentType; - commonType = Type.commonType(leftType, rightType, contextualType); - if (!commonType) { - this.error( - DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, - expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString() - ); - this.currentType = contextualType; - return module.unreachable(); - } + if (commonType.isFloatValue) { if ( isConstExpressionNaN(module, rightExpr) || diff --git a/src/resolver.ts b/src/resolver.ts index ca05a3424b..fbffb223bf 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1061,6 +1061,37 @@ export class Resolver extends DiagnosticEmitter { return null; } + resolveCompareExpressionCommonType( + /** The expression to resolve. */ + expression: BinaryExpression, + /** Contextual flow. */ + ctxFlow: Flow + ): Type | null { + const left = expression.left; + const right = expression.right; + + const leftType = this.resolveExpression(left, ctxFlow, Type.auto, ReportMode.Report); + if (!leftType) return null; + const rightType = this.resolveExpression(right, ctxFlow, Type.auto, ReportMode.Report); + if (!rightType) return null; + + if (left.kind == NodeKind.Null && rightType.isReference) { + return rightType.asNullable(); + } + if (right.kind == NodeKind.Null && leftType.isReference) { + return leftType.asNullable(); + } + + const commonType = Type.commonType(leftType, rightType, Type.auto); + if (!commonType) { + this.error( + DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, + expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString() + ); + } + return commonType; + } + /** resolving expressions */ private resolvingExpressions: Set = new Set(); diff --git a/tests/compiler/compare-null.debug.wat b/tests/compiler/compare-null.debug.wat new file mode 100644 index 0000000000..7948b27abe --- /dev/null +++ b/tests/compiler/compare-null.debug.wat @@ -0,0 +1,510 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_none (func (param i32))) + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (type $i32_i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32 i32) (result i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/shared/runtime/Runtime.Stub i32 (i32.const 0)) + (global $~lib/shared/runtime/Runtime.Minimal i32 (i32.const 1)) + (global $~lib/shared/runtime/Runtime.Incremental i32 (i32.const 2)) + (global $~lib/native/ASC_SHRINK_LEVEL i32 (i32.const 0)) + (global $~lib/memory/__data_end i32 (i32.const 44)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 32812)) + (global $~lib/memory/__heap_base i32 (i32.const 32812)) + (memory $0 1) + (data $0 (i32.const 12) "\1c\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\04\00\00\00a\00a\00\00\00\00\00\00\00\00\00") + (table $0 1 1 funcref) + (elem $0 (i32.const 1)) + (export "compare_null_with_null" (func $compare-null/compare_null_with_null)) + (export "compare_null_with_integer" (func $compare-null/compare_null_with_integer)) + (export "compare_null_with_operator_overload" (func $compare-null/compare_null_with_operator_overload)) + (export "memory" (memory $0)) + (export "compare_null_with_nullable_class" (func $export:compare-null/compare_null_with_nullable_class)) + (export "compare_null_with_class" (func $export:compare-null/compare_null_with_class)) + (start $~start) + (func $compare-null/compare_null_with_null + i32.const 0 + i32.const 0 + i32.eq + drop + i32.const 0 + i32.const 0 + i32.ne + i32.eqz + drop + ) + (func $compare-null/compare_null_with_integer + i32.const 0 + i32.const 0 + i32.eq + drop + i32.const 0 + i32.const 0 + i32.ne + i32.eqz + drop + i32.const 0 + i32.const 10 + i32.eq + i32.eqz + drop + i32.const 0 + i32.const 10 + i32.ne + drop + ) + (func $start:compare-null + call $compare-null/compare_null_with_null + call $compare-null/compare_null_with_integer + ) + (func $~lib/rt/common/OBJECT#get:rtSize (param $this i32) (result i32) + local.get $this + i32.load $0 offset=16 + ) + (func $~lib/string/String#get:length (param $this i32) (result i32) + local.get $this + i32.const 20 + i32.sub + call $~lib/rt/common/OBJECT#get:rtSize + i32.const 1 + i32.shr_u + return + ) + (func $~lib/util/string/compareImpl (param $str1 i32) (param $index1 i32) (param $str2 i32) (param $index2 i32) (param $len i32) (result i32) + (local $ptr1 i32) + (local $ptr2 i32) + (local $7 i32) + (local $a i32) + (local $b i32) + local.get $str1 + local.get $index1 + i32.const 1 + i32.shl + i32.add + local.set $ptr1 + local.get $str2 + local.get $index2 + i32.const 1 + i32.shl + i32.add + local.set $ptr2 + i32.const 0 + i32.const 2 + i32.lt_s + drop + local.get $len + i32.const 4 + i32.ge_u + if (result i32) + local.get $ptr1 + i32.const 7 + i32.and + local.get $ptr2 + i32.const 7 + i32.and + i32.or + i32.eqz + else + i32.const 0 + end + if + block $do-break|0 + loop $do-loop|0 + local.get $ptr1 + i64.load $0 + local.get $ptr2 + i64.load $0 + i64.ne + if + br $do-break|0 + end + local.get $ptr1 + i32.const 8 + i32.add + local.set $ptr1 + local.get $ptr2 + i32.const 8 + i32.add + local.set $ptr2 + local.get $len + i32.const 4 + i32.sub + local.set $len + local.get $len + i32.const 4 + i32.ge_u + br_if $do-loop|0 + end + end + end + loop $while-continue|1 + local.get $len + local.tee $7 + i32.const 1 + i32.sub + local.set $len + local.get $7 + if + local.get $ptr1 + i32.load16_u $0 + local.set $a + local.get $ptr2 + i32.load16_u $0 + local.set $b + local.get $a + local.get $b + i32.ne + if + local.get $a + local.get $b + i32.sub + return + end + local.get $ptr1 + i32.const 2 + i32.add + local.set $ptr1 + local.get $ptr2 + i32.const 2 + i32.add + local.set $ptr2 + br $while-continue|1 + end + end + i32.const 0 + return + ) + (func $compare-null/compare_null_with_nullable_class (param $a i32) + i32.const 0 + local.get $a + i32.eq + drop + i32.const 0 + local.get $a + i32.ne + drop + local.get $a + i32.const 0 + i32.eq + drop + local.get $a + i32.const 0 + i32.ne + drop + i32.const 0 + local.get $a + i32.eq + drop + i32.const 0 + local.get $a + i32.ne + drop + local.get $a + i32.const 0 + i32.eq + drop + local.get $a + i32.const 0 + i32.ne + drop + ) + (func $compare-null/compare_null_with_class (param $a i32) + i32.const 0 + local.get $a + i32.eq + drop + i32.const 0 + local.get $a + i32.ne + drop + local.get $a + i32.const 0 + i32.eq + drop + local.get $a + i32.const 0 + i32.ne + drop + i32.const 0 + local.get $a + i32.eq + drop + i32.const 0 + local.get $a + i32.ne + drop + local.get $a + i32.const 0 + i32.eq + drop + local.get $a + i32.const 0 + i32.ne + drop + ) + (func $~start + call $start:compare-null + ) + (func $~stack_check + global.get $~lib/memory/__stack_pointer + global.get $~lib/memory/__data_end + i32.lt_s + if + i32.const 32832 + i32.const 32880 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + ) + (func $~lib/string/String.__eq (param $left i32) (param $right i32) (result i32) + (local $leftLength i32) + (local $3 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i64.const 0 + i64.store $0 + local.get $left + local.get $right + i32.eq + if + i32.const 1 + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + return + end + local.get $left + i32.const 0 + i32.eq + if (result i32) + i32.const 1 + else + local.get $right + i32.const 0 + i32.eq + end + if + i32.const 0 + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + return + end + local.get $left + local.set $3 + global.get $~lib/memory/__stack_pointer + local.get $3 + i32.store $0 + local.get $3 + call $~lib/string/String#get:length + local.set $leftLength + local.get $leftLength + local.get $right + local.set $3 + global.get $~lib/memory/__stack_pointer + local.get $3 + i32.store $0 + local.get $3 + call $~lib/string/String#get:length + i32.ne + if + i32.const 0 + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + return + end + local.get $left + local.set $3 + global.get $~lib/memory/__stack_pointer + local.get $3 + i32.store $0 + local.get $3 + i32.const 0 + local.get $right + local.set $3 + global.get $~lib/memory/__stack_pointer + local.get $3 + i32.store $0 offset=4 + local.get $3 + i32.const 0 + local.get $leftLength + call $~lib/util/string/compareImpl + i32.eqz + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + return + ) + (func $~lib/string/String.__ne (param $left i32) (param $right i32) (result i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i64.const 0 + i64.store $0 + local.get $left + local.set $2 + global.get $~lib/memory/__stack_pointer + local.get $2 + i32.store $0 + local.get $2 + local.get $right + local.set $2 + global.get $~lib/memory/__stack_pointer + local.get $2 + i32.store $0 offset=4 + local.get $2 + call $~lib/string/String.__eq + i32.eqz + local.set $2 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $2 + return + ) + (func $compare-null/compare_null_with_operator_overload + (local $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store $0 + i32.const 0 + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $~lib/string/String.__eq + drop + i32.const 0 + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $~lib/string/String.__ne + drop + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + i32.const 0 + call $~lib/string/String.__eq + drop + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + i32.const 0 + call $~lib/string/String.__ne + drop + i32.const 0 + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $~lib/string/String.__eq + drop + i32.const 0 + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $~lib/string/String.__ne + drop + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + i32.const 0 + call $~lib/string/String.__eq + drop + i32.const 32 + local.set $0 + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + i32.const 0 + call $~lib/string/String.__ne + drop + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $export:compare-null/compare_null_with_nullable_class (param $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $compare-null/compare_null_with_nullable_class + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $export:compare-null/compare_null_with_class (param $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store $0 + local.get $0 + call $compare-null/compare_null_with_class + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) +) diff --git a/tests/compiler/compare-null.json b/tests/compiler/compare-null.json new file mode 100644 index 0000000000..ea57a955e1 --- /dev/null +++ b/tests/compiler/compare-null.json @@ -0,0 +1,3 @@ +{ + "asc_flags": [] +} diff --git a/tests/compiler/compare-null.release.wat b/tests/compiler/compare-null.release.wat new file mode 100644 index 0000000000..528fe477fe --- /dev/null +++ b/tests/compiler/compare-null.release.wat @@ -0,0 +1,301 @@ +(module + (type $none_=>_none (func)) + (type $i32_i32_=>_none (func (param i32 i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $i32_=>_none (func (param i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 33836)) + (memory $0 1) + (data $0 (i32.const 1036) "\1c") + (data $0.1 (i32.const 1048) "\02\00\00\00\04\00\00\00a\00a") + (export "compare_null_with_null" (func $compare-null/compare_null_with_null)) + (export "compare_null_with_integer" (func $compare-null/compare_null_with_null)) + (export "compare_null_with_operator_overload" (func $compare-null/compare_null_with_operator_overload)) + (export "memory" (memory $0)) + (export "compare_null_with_nullable_class" (func $export:compare-null/compare_null_with_nullable_class)) + (export "compare_null_with_class" (func $export:compare-null/compare_null_with_nullable_class)) + (func $compare-null/compare_null_with_null + nop + ) + (func $~lib/string/String.__eq (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i64.const 0 + i64.store $0 + local.get $0 + local.get $1 + i32.eq + if + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + return + end + block $folding-inner0 + local.get $1 + i32.eqz + local.get $0 + i32.eqz + i32.or + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $2 + local.get $0 + i32.store $0 + local.get $0 + i32.const 20 + i32.sub + i32.load $0 offset=16 + i32.const 1 + i32.shr_u + local.set $3 + local.get $2 + local.get $1 + i32.store $0 + local.get $3 + local.get $1 + i32.const 20 + i32.sub + i32.load $0 offset=16 + i32.const 1 + i32.shr_u + i32.ne + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + local.tee $2 + local.get $0 + i32.store $0 + local.get $2 + local.get $1 + i32.store $0 offset=4 + local.get $0 + local.tee $2 + i32.const 7 + i32.and + local.get $1 + i32.const 7 + i32.and + i32.or + i32.eqz + local.get $3 + local.tee $0 + i32.const 4 + i32.ge_u + i32.and + if + loop $do-loop|0 + local.get $2 + i64.load $0 + local.get $1 + i64.load $0 + i64.eq + if + local.get $2 + i32.const 8 + i32.add + local.set $2 + local.get $1 + i32.const 8 + i32.add + local.set $1 + local.get $0 + i32.const 4 + i32.sub + local.tee $0 + i32.const 4 + i32.ge_u + br_if $do-loop|0 + end + end + end + loop $while-continue|1 + block $__inlined_func$~lib/util/string/compareImpl + local.get $0 + local.tee $3 + i32.const 1 + i32.sub + local.set $0 + local.get $3 + if + local.get $1 + i32.load16_u $0 + local.get $2 + i32.load16_u $0 + i32.ne + br_if $__inlined_func$~lib/util/string/compareImpl + local.get $2 + i32.const 2 + i32.add + local.set $2 + local.get $1 + i32.const 2 + i32.add + local.set $1 + br $while-continue|1 + end + end + end + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + return + end + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $~lib/string/String.__ne (param $0 i32) (param $1 i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + local.tee $2 + i64.const 0 + i64.store $0 + local.get $2 + local.get $0 + i32.store $0 + local.get $2 + local.get $1 + i32.store $0 offset=4 + local.get $0 + local.get $1 + call $~lib/string/String.__eq + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $compare-null/compare_null_with_operator_overload + (local $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + local.tee $0 + i32.const 0 + i32.store $0 + local.get $0 + i32.const 1056 + i32.store $0 + i32.const 0 + i32.const 1056 + call $~lib/string/String.__eq + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 0 + i32.const 1056 + call $~lib/string/String.__ne + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 1056 + i32.const 0 + call $~lib/string/String.__eq + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 1056 + i32.const 0 + call $~lib/string/String.__ne + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 0 + i32.const 1056 + call $~lib/string/String.__eq + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 0 + i32.const 1056 + call $~lib/string/String.__ne + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 1056 + i32.const 0 + call $~lib/string/String.__eq + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store $0 + i32.const 1056 + i32.const 0 + call $~lib/string/String.__ne + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $export:compare-null/compare_null_with_nullable_class (param $0 i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + local.tee $1 + local.get $0 + i32.store $0 + local.get $1 + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) +) diff --git a/tests/compiler/compare-null.ts b/tests/compiler/compare-null.ts new file mode 100644 index 0000000000..0d3a49e5bf --- /dev/null +++ b/tests/compiler/compare-null.ts @@ -0,0 +1,51 @@ +class CompareNullA {} + +export function compare_null_with_null(): void { + assert(null == null); + assert(!(null != null)); +} +compare_null_with_null(); + +export function compare_null_with_integer(): void { + assert(null == 0); + assert(!(null != 0)); + assert(!(null == 10)); + assert(null != 10); +} +compare_null_with_integer(); + +export function compare_null_with_operator_overload(): void { + null == "aa"; + null != "aa"; + "aa" == null; + "aa" != null; + + null === "aa"; + null !== "aa"; + "aa" === null; + "aa" !== null; +} + +export function compare_null_with_nullable_class(a: CompareNullA | null): void { + null == a; + null != a; + a == null; + a != null; + + null === a; + null !== a; + a === null; + a !== null; +} + +export function compare_null_with_class(a: CompareNullA): void { + null == a; + null != a; + a == null; + a != null; + + null === a; + null !== a; + a === null; + a !== null; +}