You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/09/25 09:56:49 UTC

[groovy] branch master updated: Tweak compact constructor support

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 73bbb0b  Tweak compact constructor support
73bbb0b is described below

commit 73bbb0bf859e681996e6379735f727be3e11d54e
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Sep 25 17:43:08 2021 +0800

    Tweak compact constructor support
---
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 38 +++++++++++++++++--
 .../core/RecordDeclaration_10x.groovy              | 41 +++++++++++++++++++++
 .../core/RecordDeclaration_11x.groovy              | 42 +++++++++++++++++++++
 .../core/RecordDeclaration_12x.groovy              | 43 ++++++++++++++++++++++
 .../fail/RecordDeclaration_09x.groovy              | 25 +++++++++++++
 .../groovy/parser/antlr4/GroovyParserTest.groovy   |  3 ++
 .../groovy/parser/antlr4/SyntaxErrorTest.groovy    |  1 +
 7 files changed, 189 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index fcb04fc..684c7e5 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -360,6 +360,7 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.VAR;
 import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureAST;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
@@ -1962,13 +1963,35 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         if (!methodName.equals(className)) {
             createParsingFailedException("Compact constructor should have the same name with record: " + className, ctx.methodName());
         }
-        ClassNode returnType = ClassHelper.VOID_TYPE;
+        ClassNode returnType = ClassHelper.MAP_TYPE.getPlainNodeReference();
 
         Parameter[] header = classNode.getNodeMetaData(RECORD_HEADER);
         Objects.requireNonNull(classNode, "record header should not be null");
-        Parameter[] parameters = cloneParams(header);
+
+        final Parameter[] parameters = cloneParams(header);
         Statement code = this.visitMethodBody(ctx.methodBody());
-        MethodNode methodNode = classNode.addSyntheticMethod(RECORD_COMPACT_CONSTRUCTOR_NAME, ACC_PRIVATE, returnType, parameters, ClassNode.EMPTY_ARRAY, code);
+        code.visit(new CodeVisitorSupport() {
+            @Override
+            public void visitPropertyExpression(PropertyExpression expression) {
+                final String propertyName = expression.getPropertyAsString();
+                if (THIS_STR.equals(expression.getObjectExpression().getText()) && Arrays.stream(parameters).anyMatch(p -> p.getName().equals(propertyName))) {
+                    createParsingFailedException("Cannot assign a value to final variable `" + propertyName + "`", expression.getProperty());
+                }
+                super.visitPropertyExpression(expression);
+            }
+        });
+
+        final String closureVarName = "$c" + System.nanoTime();
+        List<Expression> argExpressionList = Arrays.stream(parameters).flatMap(p -> {
+            String parameterName = p.getName();
+            return Stream.of(constX(parameterName), varX(parameterName));
+        }).collect(Collectors.toList());
+        Statement block = block(
+                declS(localVarX(closureVarName), closureX(code)),
+                stmt(callX(varX(closureVarName), "call")),
+                returnS(callX(ClassHelper.makeCached(Maps.class), "of", args(argExpressionList)))
+        );
+        MethodNode methodNode = classNode.addSyntheticMethod(RECORD_COMPACT_CONSTRUCTOR_NAME, ACC_PRIVATE, returnType, parameters, ClassNode.EMPTY_ARRAY, block);
 
         modifierManager.attachAnnotations(methodNode);
         attachMapConstructorAnnotationToRecord(classNode, parameters);
@@ -1993,7 +2016,14 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
                 Arrays.stream(parameters)
                         .map(mapper::apply)
                         .collect(Collectors.toList());
-        tupleConstructorAnnotationNode.setMember("pre", closureX(block(stmt(callX(varX("this"), RECORD_COMPACT_CONSTRUCTOR_NAME, args(argExpressionList))))));
+        final String resultVarName = "$r" + System.nanoTime();
+        tupleConstructorAnnotationNode.setMember("pre", closureX(block(
+                declS(localVarX(resultVarName), castX(ClassHelper.MAP_TYPE.getPlainNodeReference(), callX(varX("this"), RECORD_COMPACT_CONSTRUCTOR_NAME, args(argExpressionList)))),
+                assignS(
+                        new TupleExpression(Arrays.stream(parameters).map(p -> varX(p.getName())).collect(Collectors.toList())),
+                        listX(Arrays.stream(parameters).map(p -> castX(p.getOriginType(), callX(varX(resultVarName), "get", args(constX(p.getName()))))).collect(Collectors.toList()))
+                )
+        )));
         classNode.addAnnotation(tupleConstructorAnnotationNode);
     }
 
diff --git a/src/test-resources/core/RecordDeclaration_10x.groovy b/src/test-resources/core/RecordDeclaration_10x.groovy
new file mode 100644
index 0000000..f1de04b
--- /dev/null
+++ b/src/test-resources/core/RecordDeclaration_10x.groovy
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package core
+
+record Point(int x, int y, String color) {
+    public Point {
+        x = -x;
+        Objects.requireNonNull(color);
+        color = color.toUpperCase();
+    }
+
+    public Point(int x, int y) {
+        this(x, y, "Blue");
+    }
+}
+
+def p1 = new Point(5, 10, "Green")
+assert -5 == p1.x()
+assert 10 == p1.y()
+assert 'GREEN' == p1.color()
+
+def p2 = new Point(0, 20)
+assert 0 == p2.x()
+assert 20 == p2.y()
+assert 'BLUE' == p2.color()
diff --git a/src/test-resources/core/RecordDeclaration_11x.groovy b/src/test-resources/core/RecordDeclaration_11x.groovy
new file mode 100644
index 0000000..961cdee
--- /dev/null
+++ b/src/test-resources/core/RecordDeclaration_11x.groovy
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package core
+
+@groovy.transform.CompileStatic
+record Point(int x, int y, String color) {
+    public Point {
+        x = -x;
+        Objects.requireNonNull(color);
+        color = color.toUpperCase();
+    }
+
+    public Point(int x, int y) {
+        this(x, y, "Blue");
+    }
+}
+
+def p1 = new Point(5, 10, "Green")
+assert -5 == p1.x()
+assert 10 == p1.y()
+assert 'GREEN' == p1.color()
+
+def p2 = new Point(0, 20)
+assert 0 == p2.x()
+assert 20 == p2.y()
+assert 'BLUE' == p2.color()
diff --git a/src/test-resources/core/RecordDeclaration_12x.groovy b/src/test-resources/core/RecordDeclaration_12x.groovy
new file mode 100644
index 0000000..2f54939
--- /dev/null
+++ b/src/test-resources/core/RecordDeclaration_12x.groovy
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package core
+
+@groovy.transform.CompileStatic
+record Point(int x, int y, String color) {
+    public Point {
+        x = -x;
+        if (x < 0) return
+        Objects.requireNonNull(color);
+        color = color.toUpperCase();
+    }
+
+    public Point(int x, int y) {
+        this(x, y, "Blue");
+    }
+}
+
+def p1 = new Point(5, 10, "Green")
+assert -5 == p1.x()
+assert 10 == p1.y()
+assert 'Green' == p1.color()
+
+def p2 = new Point(0, 20)
+assert 0 == p2.x()
+assert 20 == p2.y()
+assert 'BLUE' == p2.color()
diff --git a/src/test-resources/fail/RecordDeclaration_09x.groovy b/src/test-resources/fail/RecordDeclaration_09x.groovy
new file mode 100644
index 0000000..95f7c3b
--- /dev/null
+++ b/src/test-resources/fail/RecordDeclaration_09x.groovy
@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package core
+
+record Point(int x, int y, String color) {
+    public Point {
+        this.x = -x;
+    }
+}
diff --git a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
index e1dabc4..de1e3d4 100644
--- a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
@@ -367,6 +367,9 @@ final class GroovyParserTest extends GroovyTestCase {
         doRunAndTestAntlr4('core/RecordDeclaration_07x.groovy')
         doRunAndTestAntlr4('core/RecordDeclaration_08x.groovy')
         doRunAndTestAntlr4('core/RecordDeclaration_09x.groovy')
+        doRunAndTestAntlr4('core/RecordDeclaration_10x.groovy')
+        doRunAndTestAntlr4('core/RecordDeclaration_11x.groovy')
+        doRunAndTestAntlr4('core/RecordDeclaration_12x.groovy')
     }
 
     void "test groovy core - AnnotationDeclaration"() {
diff --git a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
index 3a660ee..9e10086 100644
--- a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
@@ -441,6 +441,7 @@ final class SyntaxErrorTest extends GroovyTestCase {
         TestUtils.doRunAndShouldFail('fail/RecordDeclaration_06x.groovy')
         TestUtils.doRunAndShouldFail('fail/RecordDeclaration_07x.groovy')
         TestUtils.doRunAndShouldFail('fail/RecordDeclaration_08x.groovy')
+        TestUtils.doRunAndShouldFail('fail/RecordDeclaration_09x.groovy')
     }
 
     void 'test groovy core - Array'() {