From 005624218dd68d6e1944a538ffce960573431ef0 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Tue, 22 Oct 2024 12:05:02 -0500 Subject: [PATCH] Record.equals must accept null without an error (#10016) Fixes #10015 --- .../dev/jjs/impl/ImplementRecordComponents.java | 17 +++++++++++++---- .../com/google/gwt/dev/jjs/test/Java17Test.java | 4 ++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java index ff4a5c41ea..19c9335d74 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java @@ -154,9 +154,7 @@ private void implementEquals(JRecordType type, JMethod method, SourceInfo info) JMethodBody body = new JMethodBody(info); JParameter otherParam = method.getParams().get(0); - // Equals is built from first == check between this and other, as a fast path for the same - // object and also to ensure that other isn't null. Then MyRecord.class == other.getClass(), - // and now we know they're the same type and can cast safely to access fields for the rest. + // if (this == other) return true; JBinaryOperation eq = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, new JThisRef(info, type), @@ -164,11 +162,22 @@ private void implementEquals(JRecordType type, JMethod method, SourceInfo info) body.getBlock().addStmt(new JIfStatement(info, eq, JBooleanLiteral.TRUE.makeReturnStatement(), null)); + // other == null + JBinaryOperation nonNullCheck = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, + otherParam.createRef(info), JNullLiteral.INSTANCE); + // MyRecordType.class != other.getClass() JBinaryOperation sameTypeCheck = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.NEQ, new JClassLiteral(info, type), new JMethodCall(info, otherParam.createRef(info), getClassMethod)); - body.getBlock().addStmt(new JIfStatement(info, sameTypeCheck, + // other == null || MyRecordType.class != other.getClass() + JBinaryOperation nullAndTypeCheck = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.OR, + nonNullCheck, sameTypeCheck); + + // if (other == null || MyRecordType.class != other.getClass()) return false; + body.getBlock().addStmt(new JIfStatement(info, nullAndTypeCheck, JBooleanLiteral.FALSE.makeReturnStatement(), null)); // Create a local to assign to and compare each component diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java index 6930b55250..fc238a981b 100644 --- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java @@ -171,6 +171,8 @@ public String toString() { assertFalse(0 == new TopLevelRecord("Pear", 0).hashCode()); assertFalse(new InnerRecord().equals(new LocalRecord())); + assertFalse(new InnerRecord().equals(null)); + assertFalse(new LocalRecord().equals(null)); RecordWithReferenceType sameA = new RecordWithReferenceType(new TopLevelRecord("a", 1)); RecordWithReferenceType sameB = new RecordWithReferenceType(new TopLevelRecord("a", 1)); @@ -184,6 +186,8 @@ public String toString() { assertFalse(sameA.equals(different)); assertFalse(sameA.hashCode() == different.hashCode()); + + assertFalse(sameA.equals(null)); } /**