You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/09/02 13:13:33 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10744: STC: assignment of primitives to wrapper-type interfaces

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

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


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new c9462cb261 GROOVY-10744: STC: assignment of primitives to wrapper-type interfaces
c9462cb261 is described below

commit c9462cb2610f5ad92964ca92bdb2d18fdaa05ee6
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Sep 2 07:37:58 2022 -0500

    GROOVY-10744: STC: assignment of primitives to wrapper-type interfaces
---
 .../transform/stc/StaticTypeCheckingSupport.java   |   5 +-
 .../transform/stc/StaticTypeCheckingVisitor.java   |  23 ++-
 .../groovy/transform/stc/STCAssignmentTest.groovy  | 201 +++++++++++++--------
 3 files changed, 140 insertions(+), 89 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index b63fc940bb..d450822884 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -751,8 +751,9 @@ public abstract class StaticTypeCheckingSupport {
             return true;
         }
 
-        // simple sub-type check
-        if (!left.isInterface() ? right.isDerivedFrom(left) : GeneralUtils.isOrImplements(right, left)) return true;
+        if (implementsInterfaceOrSubclassOf(getWrapper(right), left)) {
+            return true;
+        }
 
         if (right.isDerivedFrom(CLOSURE_TYPE) && isSAMType(left)) {
             return true;
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 2712d4213e..f8ea757d47 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1270,13 +1270,13 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         checkGroovyConstructorMap(leftExpression, leftRedirect, mapExpression);
     }
 
-    private void checkTypeGenerics(final ClassNode leftExpressionType, final ClassNode wrappedRHS, final Expression rightExpression) {
+    private void checkTypeGenerics(final ClassNode leftExpressionType, final ClassNode rightExpressionType, final Expression rightExpression) {
         if (leftExpressionType.isUsingGenerics()
-                && !isNullConstant(rightExpression)
+                && !missesGenericsTypes(rightExpressionType)
                 && !(rightExpression instanceof ClosureExpression) // GROOVY-10277
-                && !UNKNOWN_PARAMETER_TYPE.equals(wrappedRHS) && !missesGenericsTypes(wrappedRHS)
-                && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrappedRHS))
-            addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(wrappedRHS) + " to: " + prettyPrintType(leftExpressionType), rightExpression);
+                && !isNullConstant(rightExpression) && !UNKNOWN_PARAMETER_TYPE.equals(rightExpressionType)
+                && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrapTypeIfNecessary(rightExpressionType)))
+            addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(rightExpressionType) + " to: " + prettyPrintType(leftExpressionType), rightExpression);
     }
 
     private boolean hasGStringStringError(final ClassNode leftExpressionType, final ClassNode wrappedRHS, final Expression rightExpression) {
@@ -1307,23 +1307,22 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         // TODO: need errors for write-only too!
         if (addedReadOnlyPropertyError(leftExpression)) return;
 
-        ClassNode rTypeWrapped = adjustTypeForSpreading(rightExpressionType, leftExpression);
+        ClassNode rType = adjustTypeForSpreading(rightExpressionType, leftExpression);
 
-        if (!checkCompatibleAssignmentTypes(leftExpressionType, rTypeWrapped, rightExpression)) {
-            if (!extension.handleIncompatibleAssignment(leftExpressionType, rightExpressionType, assignmentExpression)) {
+        if (!checkCompatibleAssignmentTypes(leftExpressionType, rType, rightExpression)) {
+            if (!extension.handleIncompatibleAssignment(leftExpressionType, rType, assignmentExpression)) {
                 addAssignmentError(leftExpressionType, rightExpressionType, rightExpression);
             }
         } else {
             ClassNode lTypeRedirect = leftExpressionType.redirect();
-            addPrecisionErrors(lTypeRedirect, leftExpressionType, rightExpressionType, rightExpression);
+            addPrecisionErrors(lTypeRedirect, leftExpressionType, rType, rightExpression);
             if (rightExpression instanceof ListExpression) {
                 addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rightExpressionType, rightExpression, assignmentExpression);
             } else if (rightExpression instanceof MapExpression) {
                 addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, rightExpression);
             }
-            if (!hasGStringStringError(leftExpressionType, rTypeWrapped, rightExpression)
-                    && !isConstructorAbbreviation(leftExpressionType, rightExpression)) {
-                checkTypeGenerics(leftExpressionType, rTypeWrapped, rightExpression);
+            if (!hasGStringStringError(leftExpressionType, rType, rightExpression) && !isConstructorAbbreviation(leftExpressionType, rightExpression)) {
+                checkTypeGenerics(leftExpressionType, rType, rightExpression);
             }
         }
     }
diff --git a/src/test/groovy/transform/stc/STCAssignmentTest.groovy b/src/test/groovy/transform/stc/STCAssignmentTest.groovy
index c573582bcc..38c7e21169 100644
--- a/src/test/groovy/transform/stc/STCAssignmentTest.groovy
+++ b/src/test/groovy/transform/stc/STCAssignmentTest.groovy
@@ -23,43 +23,49 @@ package groovy.transform.stc
  */
 class STCAssignmentTest extends StaticTypeCheckingTestCase {
 
-    void testAssignmentFailure() {
+    void testAssignmentFailure1() {
         shouldFailWithMessages '''
             int x = new Object()
-        ''', 'Cannot assign value of type java.lang.Object to variable of type int'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type int'
     }
 
     void testAssignmentFailure2() {
         shouldFailWithMessages '''
             Set set = new Object()
-        ''', 'Cannot assign value of type java.lang.Object to variable of type java.util.Set'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type java.util.Set'
     }
 
     void testAssignmentFailure3() {
         shouldFailWithMessages '''
             Set set = new Integer(2)
-        ''', 'Cannot assign value of type java.lang.Integer to variable of type java.util.Set'
+        ''',
+        'Cannot assign value of type java.lang.Integer to variable of type java.util.Set'
     }
 
-    void testIndirectAssignment() {
+    void testIndirectAssignment1() {
         shouldFailWithMessages '''
             def o = new Object()
             int x = o
-        ''', 'Cannot assign value of type java.lang.Object to variable of type int'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type int'
     }
 
     void testIndirectAssignment2() {
         shouldFailWithMessages '''
             def o = new Object()
             Set set = o
-        ''', 'Cannot assign value of type java.lang.Object to variable of type java.util.Set'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type java.util.Set'
     }
 
     void testIndirectAssignment3() {
         shouldFailWithMessages '''
             int x = 2
             Set set = x
-        ''', 'Cannot assign value of type int to variable of type java.util.Set'
+        ''',
+        'Cannot assign value of type int to variable of type java.util.Set'
     }
 
     void testAssignmentToEnum() {
@@ -69,14 +75,18 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
             e = 'a' // string to enum is implicit
             e = "${'a'}" // gstring to enum is implicit too
         '''
-    }
-
-    void testAssignmentToEnumFailure() {
         shouldFailWithMessages '''
             enum MyEnum { a, b, c }
             MyEnum e = MyEnum.a
             e = 1
-        ''', 'Cannot assign value of type int to variable of type MyEnum'
+        ''',
+        'Cannot assign value of type int to variable of type MyEnum'
+    }
+
+    void testAssignmentToClass() {
+        assertScript '''
+            Class test = 'java.lang.String'
+        '''
     }
 
     void testAssignmentToString() {
@@ -97,9 +107,23 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    void testAssignmentToClass() {
+    // GROOVY-10744
+    void testAssignmentToSerializable() {
+        // Number implements Serializable
         assertScript '''
-            Class test = 'java.lang.String'
+            Serializable x = 0
+            Serializable y = 1.2
+            Serializable z = Math.PI
+            assert x.class.name == 'java.lang.Integer'
+            assert y.class.name == 'java.math.BigDecimal'
+            assert z.class.name == 'java.lang.Double'
+        '''
+        // Boolean implements Serializable and Comparable<Boolean>
+        assertScript '''
+            Serializable x = true
+            Comparable<Boolean> y = false
+            assert x.class.name == 'java.lang.Boolean'
+            assert y.class.name == 'java.lang.Boolean'
         '''
     }
 
@@ -121,14 +145,16 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             int i = 0
             i += new Object()
-        ''', 'Cannot find matching method int#plus(java.lang.Object)'
+        ''',
+        'Cannot find matching method int#plus(java.lang.Object)'
     }
 
     void testIntMinusEqualsObject() {
         shouldFailWithMessages '''
             int i = 0
             i -= new Object()
-        ''', 'Cannot find matching method int#minus(java.lang.Object)'
+        ''',
+        'Cannot find matching method int#minus(java.lang.Object)'
     }
 
     void testStringPlusEqualsString() {
@@ -214,65 +240,69 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    void testPossibleLooseOfPrecision() {
+    void testPossibleLossOfPrecision1() {
         shouldFailWithMessages '''
             long a = Long.MAX_VALUE
             int b = a
-        ''', 'Possible loss of precision from long to int'
+        ''',
+        'Possible loss of precision from long to int'
     }
 
-    void testPossibleLooseOfPrecision2() {
+    void testPossibleLossOfPrecision2() {
         assertScript '''
             int b = 0L
         '''
     }
 
-    void testPossibleLooseOfPrecision3() {
+    void testPossibleLossOfPrecision3() {
         assertScript '''
             byte b = 127
         '''
     }
 
-    void testPossibleLooseOfPrecision4() {
+    void testPossibleLossOfPrecision4() {
         shouldFailWithMessages '''
             byte b = 128 // will not fit in a byte
-        ''', 'Possible loss of precision from int to byte'
+        ''',
+        'Possible loss of precision from int to byte'
     }
 
-    void testPossibleLooseOfPrecision5() {
+    void testPossibleLossOfPrecision5() {
         assertScript '''
             short b = 128
         '''
     }
 
-    void testPossibleLooseOfPrecision6() {
+    void testPossibleLossOfPrecision6() {
         shouldFailWithMessages '''
             short b = 32768 // will not fit in a short
-        ''', 'Possible loss of precision from int to short'
+        ''',
+        'Possible loss of precision from int to short'
     }
 
-    void testPossibleLooseOfPrecision7() {
+    void testPossibleLossOfPrecision7() {
         assertScript '''
             int b = 32768L // mark it as a long, but it fits into an int
         '''
     }
 
-    void testPossibleLooseOfPrecision8() {
+    void testPossibleLossOfPrecision8() {
         assertScript '''
             int b = 32768.0f // mark it as a float, but it fits into an int
         '''
     }
 
-    void testPossibleLooseOfPrecision9() {
+    void testPossibleLossOfPrecision9() {
         assertScript '''
             int b = 32768.0d // mark it as a double, but it fits into an int
         '''
     }
 
-    void testPossibleLooseOfPrecision10() {
+    void testPossibleLossOfPrecision10() {
         shouldFailWithMessages '''
             int b = 32768.1d
-        ''', 'Possible loss of precision from double to int'
+        ''',
+        'Possible loss of precision from double to int'
     }
 
     void testCastIntToShort() {
@@ -289,8 +319,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
 
     void testCompatibleTypeCast() {
         assertScript '''
-        String s = 'Hello'
-        ((CharSequence) s)
+            String s = 'Hello'
+            ((CharSequence) s)
         '''
     }
 
@@ -298,7 +328,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             String s = 'Hello'
             ((Set) s)
-        ''', 'Inconvertible types: cannot cast java.lang.String to java.util.Set'
+        ''',
+        'Inconvertible types: cannot cast java.lang.String to java.util.Set'
     }
 
     void testIncompatibleTypeCastWithAsType() {
@@ -314,7 +345,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
             def s = 'Hello'
             s = 1
             ((Set) s)
-        ''', 'Inconvertible types: cannot cast int to java.util.Set'
+        ''',
+        'Inconvertible types: cannot cast int to java.util.Set'
     }
 
     void testArrayLength() {
@@ -347,7 +379,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
             List x
             List y
             (x,y) = [1,2]
-        ''', 'Cannot assign value of type int to variable of type java.util.List'
+        ''',
+        'Cannot assign value of type int to variable of type java.util.List'
     }
 
     void testMultipleAssignmentWithoutEnoughArgs() {
@@ -355,7 +388,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
             int x
             int y
             (x,y) = [1]
-        ''', 'Incorrect number of values. Expected:2 Was:1'
+        ''',
+        'Incorrect number of values. Expected:2 Was:1'
     }
 
     void testMultipleAssignmentTooManyArgs() {
@@ -372,7 +406,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             def list = [1,2,3]
             def (x,y) = list
-        ''', 'Multiple assignments without list or tuple on the right-hand side are unsupported in static type checking mode'
+        ''',
+        'Multiple assignments without list or tuple on the right-hand side are unsupported in static type checking mode'
     }
 
     // GROOVY-8223, GROOVY-8887, GROOVY-10063
@@ -477,20 +512,20 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
 
     void testAssignmentToInterface() {
         assertScript '''
-            Serializable ser = 'Hello'
+            Comparable<String> x = 'x'
+            CharSequence y = 'y'
         '''
-    }
-
-    void testAssignmentToIncompatibleInterface() {
         shouldFailWithMessages '''
-            Collection ser = 'Hello'
-        ''', 'Cannot assign value of type java.lang.String to variable of type java.util.Collection'
+            Collection z = 'z'
+        ''',
+        'Cannot assign value of type java.lang.String to variable of type java.util.Collection'
     }
 
     void testTernaryOperatorAssignmentShouldFailBecauseOfIncompatibleGenericTypes() {
         shouldFailWithMessages '''
             List<Integer> foo = true?new LinkedList<String>():new LinkedList<Integer>();
-        ''', 'Incompatible generic argument types. Cannot assign java.util.LinkedList<? extends java.io.Serializable<? extends java.io.Serializable<java.lang.String>>> to: java.util.List<java.lang.Integer>'
+        ''',
+        'Incompatible generic argument types. Cannot assign java.util.LinkedList<? extends java.io.Serializable<? extends java.io.Serializable<java.lang.String>>> to: java.util.List<java.lang.Integer>'
     }
 
     void testCastStringToChar() {
@@ -502,13 +537,15 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
     void testCastStringLongerThan1CharToChar() {
         shouldFailWithMessages '''
             char c = 'aa'
-        ''','Cannot assign value of type java.lang.String to variable of type char'
+        ''',
+        'Cannot assign value of type java.lang.String to variable of type char'
     }
 
     void testCastNullToChar() {
         shouldFailWithMessages '''
             char c = null
-        ''', 'Cannot assign value of type java.lang.Object to variable of type char'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type char'
     }
 
     // GROOVY-6577
@@ -536,7 +573,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
     void testCastStringLongerThan1CharToCharacter() {
         shouldFailWithMessages '''
             Character c = 'aa'
-        ''','Cannot assign value of type java.lang.String to variable of type java.lang.Character'
+        ''',
+        'Cannot assign value of type java.lang.String to variable of type java.lang.Character'
     }
 
     void testAssignNullToCharacter() {
@@ -570,13 +608,15 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
     void testCastStringLongerThan1ToCharWithCast() {
         shouldFailWithMessages '''
             def c = (char) 'aa'
-        ''', 'Inconvertible types: cannot cast java.lang.String to char'
+        ''',
+        'Inconvertible types: cannot cast java.lang.String to char'
     }
 
     void testCastNullToCharWithCast() {
         shouldFailWithMessages '''
             def c = (char) null
-        ''', 'Inconvertible types: cannot cast java.lang.Object to char'
+        ''',
+        'Inconvertible types: cannot cast java.lang.Object to char'
     }
 
     void testCastStringToCharacterWithCast() {
@@ -588,7 +628,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
     void testCastStringLongerThan1ToCharacterWithCast() {
         shouldFailWithMessages '''
             def c = (Character) 'aa'
-        ''', 'Inconvertible types: cannot cast java.lang.String to java.lang.Character'
+        ''',
+        'Inconvertible types: cannot cast java.lang.String to java.lang.Character'
     }
 
     void testCastNullToCharacterWithCast() {
@@ -600,10 +641,7 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
     void testCastObjectToSubclass() {
         assertScript '''
             Object o = null
-            try {
-                ((Integer)o).intValue()
-            } catch (NullPointerException e) {
-            }
+            ((Integer) o)?.intValue()
         '''
     }
 
@@ -617,7 +655,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
                 x = '123'
             }
             x.toInteger()
-        ''', 'Cannot find matching method java.io.Serializable#toInteger()'
+        ''',
+        'Cannot find matching method java.io.Serializable#toInteger()'
     }
 
     void testIfElseBranchParameter() {
@@ -632,7 +671,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
                 x.toInteger()
             }
             foo('bar')
-        ''', 'Cannot find matching method java.lang.Object#toInteger()'
+        ''',
+        'Cannot find matching method java.lang.Object#toInteger()'
     }
 
     void testIfOnly() {
@@ -643,7 +683,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
                 x = new HashSet()
             }
             x.toInteger()
-        ''', 'Cannot find matching method java.io.Serializable#toInteger()'
+        ''',
+        'Cannot find matching method java.io.Serializable#toInteger()'
     }
 
     void testIfOnlyParameter() {
@@ -657,7 +698,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
                 x.toInteger()
             }
             foo('123')
-        ''', 'Cannot find matching method java.lang.Object#toInteger()'
+        ''',
+        'Cannot find matching method java.lang.Object#toInteger()'
     }
 
     void testIfWithCommonInterface() {
@@ -921,7 +963,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
             BigInteger a = 333
             double b = 2d
             BigInteger c = a * b
-        ''', 'Cannot assign value of type java.math.BigDecimal to variable of type java.math.BigInteger'
+        ''',
+        'Cannot assign value of type java.math.BigDecimal to variable of type java.math.BigInteger'
     }
 
     void testBigIntegerMultInteger() {
@@ -988,18 +1031,21 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
                 void setInteger(Character c) { integer = (c as int) }
             }
             new Pogo().integer++
-        ''', 'Cannot assign value of type java.lang.Integer to variable of type java.lang.Character'
+        ''',
+        'Cannot assign value of type java.lang.Integer to variable of type java.lang.Character'
     }
 
     void testPostfixOnObject() {
         shouldFailWithMessages '''
             Object o = new Object()
             o++
-        ''', 'Cannot find matching method java.lang.Object#next()'
+        ''',
+        'Cannot find matching method java.lang.Object#next()'
         shouldFailWithMessages '''
             Object o = new Object()
             o--
-        ''', 'Cannot find matching method java.lang.Object#previous()'
+        ''',
+        'Cannot find matching method java.lang.Object#previous()'
     }
 
     void testPrefixOnInt() {
@@ -1028,11 +1074,13 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             Object o = new Object()
             ++o
-        ''', 'Cannot find matching method java.lang.Object#next()'
+        ''',
+        'Cannot find matching method java.lang.Object#next()'
         shouldFailWithMessages '''
             Object o = new Object()
             --o
-        ''', 'Cannot find matching method java.lang.Object#previous()'
+        ''',
+        'Cannot find matching method java.lang.Object#previous()'
     }
 
     void testAssignArray() {
@@ -1053,13 +1101,15 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             String[] src = ['a','b','c']
             (Set[]) src
-        ''', 'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
+        ''',
+        'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
     }
 
     void testIncompatibleToArray() {
         shouldFailWithMessages '''
             (Set[]) ['a','b','c'].toArray(new String[3])
-        ''', 'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
+        ''',
+        'Inconvertible types: cannot cast java.lang.String[] to java.util.Set[]'
     }
 
     // GROOVY-5535, GROOVY-10623
@@ -1135,7 +1185,8 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             Object o
             int[] array = o
-        ''', 'Cannot assign value of type java.lang.Object to variable of type int[]'
+        ''',
+        'Cannot assign value of type java.lang.Object to variable of type int[]'
     }
 
     // GROOVY-7015
@@ -1175,19 +1226,19 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
 
     void testIntegerArraySmartType() {
         assertScript '''
-        def m() {
-            def a  = 1
-            Integer[] b = [a]
-        }
+            def m() {
+                def a  = 1
+                Integer[] b = [a]
+            }
         '''
     }
 
     void testIntegerSecondDimArraySmartType() {
         assertScript '''
-        def m() {
-            def a = new int[5]
-            int[][] b = [a]
-        }
+            def m() {
+                def a = new int[5]
+                int[][] b = [a]
+            }
         '''
     }